連載
» 2010年03月17日 00時00分 公開

Androidで動く携帯Javaアプリ作成入門(15):Android NDKでJNIを使用してアプリを高速化するには (2/3)

[緒方聡,株式会社イーフロー]

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版を使用しました。

図1 Live Wallpaperの設定ボタン 図 1.6r1(r2)のダウンロードページ(2月28日)

ダウンロード・展開

 ダウンロードした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.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。