オムレツ工房

androidとかiPhoneとかの開発雑記

AndroidでJNIを利用する

今更感満載だけど、前に仕事でやった時は既に環境が出来上がった状態からのアサインだったので改めてまとめてみる。

動機としては、前回の投稿で書いた音声認識をオフラインでやりたくて、Cライブラリを探してみたところ、Juliusというオープンソースのエンジンがあったので試してみたかったから。
誰かAndroid用にポーティングしてくれないだろうか…。

まぁともかく、Androidからネイティブコードを実行する方法については以下。(ちなみに僕の開発環境はMacOSX 10.7.3)

1.Android NDKを導入する。
2.ネイティブコード呼び出しAndroidプロジェクトを実装する。
3.自前ネイティブライブラル(C言語)を実装する。
4.自前ネイティブライブラリをビルドする。
5.動かす

1.Android NDKを導入する。

下記から最新版のNDKをダウンロードする。
http://developer.android.com/sdk/ndk/index.html

適当なところに展開し、パスを通す。
(例)

export PATH=$PATH:/Users/xxxx/develop/android-ndk-r7c

パス通ったか確認。

$ which ndk-build
/Users/xxxx/develop/android-ndk-r7c/ndk-build

2.ネイティブコード呼び出しAndroidプロジェクトを実装する。

作るのは普通のAndroidプロジェクトと同様。ただし、ネイティブコードを実行したいActivityは以下のような格好で実装する。

今回は自作するライブラリの名前を「sampleJNI」として、呼びだすと文字列を返すgetNativeString
メソッドをネイティブで実装することにした。

package net.omu;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;

public class SampleJNIActivity extends Activity {
	// ライブラリのロード。
	static {
		System.loadLibrary("sampleJNI");
	}

	// ネイティブメソッドの宣言
	public native String getNativeString();

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		// ネイティブメソッド実行
		String nativeStr = getNativeString();

		Toast.makeText(this, nativeStr, Toast.LENGTH_SHORT).show();
	}
}

3.自前ネイティブライブラル(C言語)を実装する。

ヘッダファイル作成

連携用のヘッダファイルを作成する。
2.で作成したプロジェクトのディレクトリ直下で、以下のコマンドを実行する。

javah -classpath bin/classes -d jni net.omu.SampleJNIActivity

※最後のクラス名はネイティブコードを実行するクラスを指定する。

javahが、クラス中のnative指定子を元にヘッダファイルを作成してくれる。

実行後、jniディレクトリが作成され、配下に「net_omu_SampleJNIActivity.h」
というヘッダファイルが自動生成される。
このヘッダファイルに書かれた関数プロトタイプを元に、ネイティブライブラリを実装する。

ネイティブライブラリ実装

とはいうものの、一から書くのは面倒なので、NDKのサンプルを拝借する。
NDKのディレクトリ配下にサンプルプロジェクトがいくつかあるので一番簡単そうなのをパクりましょう。
(ndkのルート)/samples/hello-jni/jni
から、Android.mkとhello-jni.cを、自作プロジェクトのjniディレクトリにコピーする。
hello-jni.cは、適当にsampleJNI.cとかにリネーム。

で、出来上がったCのソースが以下。

#include <string.h>
#include <jni.h>

jstring 
Java_net_omu_SampleJNIActivity_getNativeString(JNIEnv* env, jobject this)
{
	return (*env)->NewStringUTF(env, "へろうわあるど");
}

4.自前ネイティブライブラリをビルドする。

makefile作成

ビルドする前に、makefileを編集する。
3.でコピーしたAndroid.mkを以下の通り修正。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := sampleJNI
LOCAL_SRC_FILES := sampleJNI.c

include $(BUILD_SHARED_LIBRARY)

LOCAL_MODULE ビルド後に出来上がるライブラリ名。Java側で実行しているSystem.loadLibraryの引数がこれ。
LOCAL_SRC_FILES ビルド対象のソース。

ビルド!!

現時点で、jniディレクトリ配下はこんな感じ。

$ ls
Android.mk			sampleJNI.c
net_omu_SampleJNIActivity.h

ここで、ndk-buildを実行する。

$ ndk-build 
Compile thumb  : SampleJNI_C <= sampleJNI.c
SharedLibrary  : libSampleJNI_C.so
Install        : libSampleJNI_C.so => libs/armeabi/libSampleJNI_C.so

正常にビルドが完了していれば、libs/armeabi配下にライブラリファイルが作成される。

$ ls ../libs/armeabi/
libSampleJNI_C.so

これでビルド完了。

5.動かす

あとは、Androidのプロジェクトをクリーンビルドして(eclipse上でF5とかrefleshをしないと
ライブラリが反映されないことがあるみたいなので注意)実行すればOK。

libs/armeabi配下のファイルも、apkに含まれるので、特にアプリのインストールに特別な手順は不要。

以下、実行結果。
f:id:omulette:20120418203818p:plain
ちゃんと出た!!ふしぎ!!
以上!

まとめ

実は5.の手順で「そんなライブラリねーよ」エラーが出て全然うまく行かず、2時間近くドン嵌りしてました。
原因は、エミュレータにあったみたいで、最初実行用のエミュレータがx86版のものを起動していたのでダメだったようだ。
ARM版のエミュレータ(通常はこれ)に変えたらあっさり動いた。実機でも動いた。
当然といえば当然なんだけど、全然気づかなくてマジまいっちんぐ。

ビルドするときの指定でアーキテクチャの指定とかがあるのかな?