AndroidアプリのソースコードにJNIを組み込むには?
nativeメソッドの宣言
JNIを使用するには、「native」というキーワードを持つメソッドをクラス内に宣言します。今回は、以下の2つを宣言しました。
private native String hello();
private native void effect(int fireLevel, int width, int height, int[] image, int[] pallet, int[] seedparam, int[] color);
1つ目はお約束のHello Worldです。2つ目は、実際の炎のエフェクトの処理を行わせるJNIメソッドです。
ライブラリのロード
nativeメソッドの実際の処理が実装されているネイティブライブラリをSystem.loadLibrary(String)でロードする必要があります。
static { System.loadLibrary("FireEffect"); }
今回は、static節で読み込むことにしました。ライブラリの名前は「FireEffect」とします。
コラム 「Dalvik VMのクラスローダは要注意」
今回の高速化は、Java側にピクセルデータを持たせ、JNI側でピクセル操作を行う、という仕組みになっています。実は、JNI側でピクセルデータを持たせるようにすれば、もっと高速化できる見込みなのですが、以下の理由で実現できませんでした。
- JNI_OnUnload()が呼び出されない
C/C++では、メモリを確保したら解放しなければならないのですが、ライブラリが解放されると、呼び出されるはずのJNI_OnUnload()関数が呼び出されません。原因は分かっていません。 - クラスが何度も初期化される
もっと不可解なのが、static節が何度も呼び出されることです。例えば今回のサンプルのように、以下のコードのようにすると、不定期に何度もこのコードが実行されます。
static { System.loadLibrary("FireEffect"); }
結果として、JNI_OnLoad()が何度も呼び出されてしまうため、実は「JNI_OnLoad()やJNI_OnUnload()でメモリやリソースを管理する」という方法はAndroidでは破たんします。
Dalvik VMのクラスローダが特殊な仕組みになっているのが原因だと思いますが、原因は分かっていません。
どちらも、JavaやJNIの仕様に準拠してない動作です。特に、「クラスが何度もロードされる」というのは、JNIを使わない通常のAndroidアプリでも影響を受ける動作なので、頭の片隅に置いておくとよいでしょう。
javahコマンドでヘッダファイル生成
ここまで完了したら、コンパイルされたクラスファイルに対してJDK付属のjavahコマンドを使用します。
javah com.example.android.livewallpaper2.FireEffect
カレントディレクトリにcom_example_android_livewallpaper2_FireEffect.hというヘッダファイルが作成されるので、これをベースにC/C++の実装を行います。
次はNDK側の作業です。
Android NDKのセットアップ
2010年3月17日現在のNDKの指針版はr3ですが、2月28日の原稿執筆時時点での最新版は、1.6r1(r2)でした。各プラットフォーム向けのバイナリが提供されていて、今回のサンプルでは、1.6r1のWindows版を使用しました。
ダウンロード・展開
ダウンロードしたzipファイルを任意のフォルダに展開します。今回は、C:\ android-ndk-1.6_r1(以降、「NDKホームディレクトリ」)に展開したものとして話を進めます。
Cygwinのセットアップ
Windows版のAndroid NDKを使用するには、別途Cygwinが必要です。Cygwinはデフォルトのインストールオプションに加え、makeが必要です。
初回セットアップ
Cygwinを起動し、NDKホームディレクトリに移動し、以下のコマンドを入力します。
bash ./build/host-setup.sh
これで、NDKを使用する準備が整いました。
Android NDKの中身はどうなっている?
Android NDKのビルド対象は、NDKホームディレクトリ以下の「apps」ディレクトリです。ここにADTで作成したプロジェクト(または、そのシンボリックリンク)があると非常に便利です。
Windows Vista以降であれば、mklinkコマンド(またはエクスプローラの機能)を、Windows XPであれば「Windows Server 2003 Resource Kit Tools」などに含まれるlinkdを使用してシンボリックリンク(または、ジャンクション)を作成するとよいでしょう。
NDKを使用するには、「Application.mk」「Android.mk」という名前の設定ファイルを用意する必要があります。
NDKホームディレクトリ + apps + <プロジェクトディレクトリ> + Application.mk + jni + Android.mk
1つはプロジェクトディレクトリ直下、もう1つはプロジェクトディレクトリ配下に作成した「jni」ディレクトリ内です。
アプリの定義を記す「Application.mk」
Application.mkはアプリケーションの定義を行います。
定義 | 説明 |
---|---|
APP_MODULES | モジュール名を指定(必須) |
APP_PROJECT_PATH | プロジェクトパスを指定(必須) |
APP_OPTIM | debugかreleaseを指定(releaseがデフォルト) |
APP_CFLAGS | Cソースコンパイルフラグを指定 |
APP_CXXFLAGS | C++ソースコンパイルフラグを指定 |
APP_CPPFLAGS | CとC++で共通のコンパイルフラグを指定 |
APP_BUILD_SCRIPT | Android.mkのパスを指定($( APP_PROJECT_PATH)/jni/Android.mkがデフォルト) |
表4 Application.mkの定義 |
今回は、以下のように定義しています。
APP_PROJECT_PATH := $(call my-dir) APP_MODULES := FireEffect
APP_MODULESは、Javaの「System.loadLibrary(String)」と合わせる必要があります。APP_PROJECT_PATHの$(call my-dir)は、NDKが提供するマクロで、この場合は、そのファイルの存在する場所に置き換えられます。次ページでは、「Android.mk」にファイルやライブラリの情報を定義し、コンパイルやパッケージングを行ってみましょう。最後に、JNIとNDKの注意点もお話しします。
コラム 「Android NDKが備える5つのマクロ」
NDKでは、$(call my-dir)のように使用できるマクロが、my-dir以外にも用意されています。
- my-dir
そのファイルが存在するディレクトリパスを返します。 - all-subdir-makefiles
このAndroid.mkファイルの存在するディレクトリのサブディレクトリにあるAndroid.mkを探します。例えば、ディレクトリとAndroid.mkが以下のような構成の場合です。
jni/foo/Android.mk jni/foo/lib1/Android.mk jni/foo/lib2/Android.mk
jni/foo/Android.mkに以下の行が含まれていると、「jni/foo/lib1/Android.mk」「jni/foo/lib2/Android.mk」も自動的にビルドに含まれます。
include $(call all-subdir-makefiles)
この定義は、サブディレクトリのサブディレクトリまでは探さないことに注意してください。
- this-makefile
現在のファイルパスを返します。 - parent-makefile
呼び出し元のファイルパスを返します。 - grand-parent-makefile
呼び出し元の呼び出し元のファイルパスを返します。
Copyright © ITmedia, Inc. All Rights Reserved.