AndroidでJavaからネイティブコードを呼び出す

AndroidでJavaコードからネイティブ関数を呼び出す方法についてまとめました。com.example.android.MainActivityというJavaコードと、jni/hello/hello.cというネイティブコードを使います。

ネイティブからJavaを呼び出す場合はこちらを参照してください。

 

1 ネイティブ関数の命名規則

com.example.androidパッケージのMainActivityクラスから呼び出すhelloWorld関数は以下の名前になります。

package com.example.android;
<snip>
public class MainActivity extends Activity
{
  private native void helloWorld();
<snip>
}
Java_com_example_android_MainActivity_helloWorld
Java_[.が_に置き換えられたパッケージ名]_[クラス名]_[関数名]

1.1 関数名にアンダースコアがついている場合

ネイティブコードの関数名にアンダースコアが含まれる場合は注意が必要です。

ネイティブコード側で_を_1にする必要があります。

 

hello_worldという関数名を使う場合、Java側は以下のようになります。

package com.example.android;
<snip>
public class MainActivity extends Activity
{
  private native void hello_world();
<snip>
}

ネイティブコード側は以下のようになります。

パッケージ名、クラス名の後の名前がhello_1worldになっています。

JNIEXPORT void JNICALL Java_com_example_android_MainActivity_hello_1world
(JNIEnv *, jobject);

ネイティブコードの関数名はスナークケースではなく、キャメルケースで命名した方が良いでしょう。

2 javahでネイティブコードのヘッダファイルを作成

javahでネイティブコードの関数を定義したヘッダファイルが生成されます。

classpathオプションに、Androidプロジェクトが生成するbin/classesディレクトリとandroid.jarを指定し、ネイティブコードの関数を呼び出すJavaクラス名を指定します。

$ javah -classpath ~/bin/sdk/platforms/android-17/android.jar:./bin/classes \
com.example.android.MainActivity

以下のヘッダファイルが生成されました。

$ cat com_example_android_MainActivity.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_android_MainActivity */

#ifndef _Included_com_example_android_MainActivity
#define _Included_com_example_android_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
#undef com_example_android_MainActivity_MODE_PRIVATE
#define com_example_android_MainActivity_MODE_PRIVATE 0L
<snip>
/*
 * Class:     com_example_android_MainActivity
 * Method:    nativeHelloWorld
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_com_example_android_MainActivity_nativeHelloWorld
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

ネイティブコードを以下のように定義します。

#include <com_example_android_MainActivity.h>
#include <android/log.h>

#define JNI_METHOD_NAME(name) \
  Java_com_example_android_MainActivity_##name
#define JNI_DEFINE_METHOD(type, name, args...)              \
  JNIEXPORT type JNICALL JNI_METHOD_NAME(name)(JNIEnv *env, \
                                               jobject object, ##args)
#define JNI_LOG(args...) \
  __android_log_print(ANDROID_LOG_INFO, "hello", ##args)

JNI_DEFINE_METHOD(void, helloWorld)
{
  JNI_LOG("Hello, World\n");
}

2.1 custom_rules.xmlでjavah

android create projectでAndroidプロジェクトを作成した場合にant用のbuild.xmlが作成されます。

custom_rules.xmlはbuild.xmlから呼ばれるant用のファイルです。

こちらにユーザ固有の処理を追加することができます。

 

antはjavahにも対応しているので、今回はjavahをcustom_rules.xmlで実行します。

destdirにヘッダファイルの出力先(ネイティブコードのディレクトリ)、classpathにbin/classesとandroid.jar、classにMainActivityを指定します。

<?xml version="1.0" encoding="UTF-8"?>
<project>
  <target name="-post-compile">
    <javah destdir="jni/hello"
           classpath="bin/classes:${sdk.dir}/platforms/android-17/android.jar"
           class="com.example.android.MainActivity"/>
    <exec executable="ndk-build" failonerror="true"/>
  </target>
</project>

targetディレクティブのnameによって、どの段階でユーザ固有処理を実行するかを決定します。

Javaコードは-compileの段階でコンパイルされる為、"-post-compile"を指定します。

また、javahでヘッダファイルが生成される為、それをincludeするネイティブコードをndk-buildでコンパイルします。

 

antコマンドを実行すると以下のログが得られます。

-compile:
Compiling 3 source files to /Users/hiroom2/src/AndroidJniFromJava/bin/classes

-post-compile:
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
[armeabi-v7a] Compile thumb  : hello <= hello.c
[armeabi-v7a] SharedLibrary  : libhello.so
[armeabi-v7a] Install        : libhello.so => libs/armeabi-v7a/libhello.so

私の環境ではexport JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF-8を指定している為、javah実行時に"Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8"と表示されています。

3 サンプルコード

jcharやGetCharArrayElement等のJNIで使用する型や関数は以下で定義されています。

各関数の詳細はOracleのマニュアルに記載されています。

ndk/platforms/android-17/arch-arm/usr/include/jni.h

サンプルコードではJNIEnv *envのメンバ関数ポインタに対して、以下のマクロを使用して関数呼び出しを実行します。

#define JNI_CALL(name, args...) (*env)->name(env, ##args)

3.1 Javaの配列をネイティブで表示する

Java側でchar配列を確保して、ネイティブ関数に渡します。

printArray(new char [] { 'H', 'e', 'l', 'l', 'o' });

ネイティブ側ではjcharArray型でchar配列を受け取ります。

JNI_DEFINE_METHOD(void, printArray, jcharArray array)
{
  jboolean isCopy;
  jchar *p = JNI_CALL(GetCharArrayElements, array, &isCopy);
  int i, size = JNI_CALL(GetArrayLength, array);

  if (!isCopy) {
    JNI_LOG("GetCharArrayElements error");
    return;
  }

  for (i = 0; i < size; i++)
    JNI_LOG("array[%d] = %c(%02x)\n", i, p[i], p[i]);

  JNI_CALL(ReleaseCharArrayElements, array, p, 0);
}

GetCharArrayElementsでjcharArrayにアクセスする為のjcharポインタを取得します。

jbooleanのisCopyに成功したかどうかが格納されます。

GetArrayLengthで配列の長さを取得します。

ReleaseCharArrayElementsはGetCharArrayElementsで確保したjcharポインタを解放します。 

3.2 ネイティブで確保した配列をJavaで表示する

ネイティブで確保した配列をJava側で表示します。

char [] array = getArray();
for (int i = 0; i < array.length; ++i)
  Log.d(gTAG, "array[" + i + "] = " + array[i]);

ネイティブ側でJavaのchar配列を確保し、"world"という内容にしてからJava側へ返します。

JNI_DEFINE_METHOD(jcharArray, getArray)
{
  jcharArray array = JNI_CALL(NewCharArray, 6);
  jboolean isCopy;
  jchar *p = JNI_CALL(GetCharArrayElements, array, &isCopy);

  if (!isCopy) {
    JNI_LOG("GetCharArrayElements error");
    return;
  }

  p[0] = 'w';
  p[1] = 'o';
  p[2] = 'r';
  p[3] = 'l';
  p[4] = 'd';
  p[5] = '\0';
  JNI_CALL(ReleaseCharArrayElements, array, p, 0);

  return array;
}

NewCharArrayでJavaのchar配列を確保します。p[0] = 'w'を実行されてもarrayには反映されません。ReleaseCharArrayElementsが実行されてから、変更内容が反映されます。

3.3 実行結果

logcatに以下のログが出力されます。

06-10 20:56:49.902: I/hello(17184): Hello, World
06-10 20:56:49.902: I/hello(17184): array[0] = H(48)
06-10 20:56:49.902: I/hello(17184): array[1] = e(65)
06-10 20:56:49.902: I/hello(17184): array[2] = l(6c)
06-10 20:56:49.902: I/hello(17184): array[3] = l(6c)
06-10 20:56:49.902: I/hello(17184): array[4] = o(6f)
06-10 20:56:49.902: D/hello(17184): array[0] = w
06-10 20:56:49.902: D/hello(17184): array[1] = o
06-10 20:56:49.902: D/hello(17184): array[2] = r
06-10 20:56:49.902: D/hello(17184): array[3] = l
06-10 20:56:49.902: D/hello(17184): array[4] = d
06-10 20:56:49.902: D/hello(17184): array[5] = ��
ダウンロード
AndroidでJavaからネイティブコードを呼び出すサンプルプログラム
AndroidJniFromJava.tar.gz
GNU tar 37.1 KB