1 JNIはじめに
Androidのフレームワークでは、特定のタスクを完了するために互いに協調するように、媒体またはJava有機層(上層)及びC / C ++層(下層)を結ぶブリッジを提供することが必要です。そして、この中には、Javaネイティブインタフェース(JNI、Javaネイティブ・インタフェース)として機能することです。
JNIの列は、(これらの言語がローカル言語として知られており、JNIに)ローカルに編集等、モジュールをアプリケーションプログラムをJavaクラスとC / C ++言語を可能にするインタフェースを提供し、ライブラリーは、相互運用します。たとえば、C言語でのJavaクラスまたはJavaクラスライブラリでのC言語の関数ライブラリを使用して、我々は、ヘルプJNIを必要としています。
AndroidのNDKは、開発ツールのセットです迅速C / C ++ DLLを開発するためのツールの範囲を提供し、自動的にAPK一緒の.so / .dllファイルやJavaアプリケーションにパッケージ化することができます。
NDKは、C / C ++を呼び出すためにJNIを容易にするためのツールを提供するだけでなく、クロスコンパイラは、特定のCPUプラットフォームをの.mk修正するDLLファイルを生成することができます提供し、そうAPKとJavaアプリケーションを一緒にパッケージすることができ、それは単にJavaとJNIに責任があります各操作のためのC / C ++、NDKツールJNIのAndroidプラットフォームを使用して簡単に。
2つのJNIの使用シナリオ
JNIは、通常、次の使用シナリオがあります。
処理速度に焦点を当て▨:
バックにゆっくりとネイティブコード(C / C ++など)、Javaコードの実行速度と比較すると。あなたが特定の手順の実行速度の高い要求を持っている場合は、C / C ++コードを使用することをお勧めします。それまでC / Cに一部基づい++ JavaのJNIで書かれて呼び出します。、良好な性能をC / C ++言語とより高い効率を対応する他のローカルモジュールを使用して、より高いCPU処理速度のようなプログラムや信号処理要件を処理する画像を現像します。
▨ ハードウェア制御:
ハードウェアのより良い制御するために、ハードウェア制御コードは、通常、C言語で記述されています。
▨ 既存のC / C ++コードの再利用:
プログラミングの間、頻繁に書き込み、効率を向上させ、プログラムのセキュリティと堅牢性を確保するために、いくつかすでに書かれてC / C ++コードを使用して、サードパーティのライブラリのこのタイプでは、より一般的な、多くのサードパーティのライブラリは今ありますこのようffmpegのよう書いてC / C ++ライブラリは、あります。
▨ コード保護:
JavaレベルのコードAPKので簡単に逆コンパイル、およびC / C ++ライブラリは、非常に困難なコンパイル。
▨ プラットフォーム間でアプリケーションを移植します。
実際、Androidのアプリケーション開発では、開発者は通常、Javaプログラムを開発するには、Android SDKを使用しています。高パフォーマンスの要件については、多くの場合、C / C ++のネイティブライブラリで提供NDK(ネイティブ開発キット)Androidの開発を使用していました。そして、JNI Javaプログラムを介して一緒にC / C ++プログラムに統合されます。NDKは、開発者がすぐにC / C ++ DLLの開発を支援する一連のツールを提供します。
図書館で3つのJavaの呼び出しC関数
3.1のAndroid Studioの新規プロジェクト
最初のステップは:我々は1つがNDKで、1はCMakeのある、2つの重要なツールをダウンロードする必要があります。
ステップ2:新規プロジェクト
以下に示すように、一度、作成しました:
C / C ++ファイルは、通常のcppディレクトリに保存されます。次は、プロファイルbuild.gradleを見て:
CMakeLists.txt(コード3.1-1):
1 cmake_minimum_required(VERSION 3.4.1) # Android Studio最低要求版本 2 3 add_library( # 当前库名称. 4 native-lib 5 6 # 将库设置为共享库。 7 SHARED 8 9 # 加载该库里的文件. 10 native-lib.cpp)
3.2 多目录,多层次目录时的配置问题
这时,cpp.CMakeLists.txt 设置如下所示(代码 3.2-1):
1 cmake_minimum_required(VERSION 3.4.1) #指定编译器版本 2 3 #指定子文件夹 4 add_subdirectory(first) 5 add_subdirectory(second)
cpp.first.CMakeLists.txt(代码 3.2-2)
1 set(LIBRARY first-lib) # 定义库名称 LIBRARY = first-lib 2 3 file(GLOB_RECURSE cpp_first "./*.cpp") # first目录下的所有 .cpp 文件 4 5 add_library( # 设置库的名称。 6 ${LIBRARY} 7 8 # 将库设置为共享库。 9 SHARED 10 11 # 提供源文件的相对路径。 12 ${cpp_first} 13 ) 14 15 find_library( #设置路径变量的名称。 16 log-lib 17 18 # 指定您希望CMake定位的NDK库的名称。 19 log) 20 21 target_link_libraries( #指定目标库。 22 ${LIBRARY} 23 24 # 将目标库链接到NDK中包含的日志库。 25 ${log-lib})
3.3 下面看看 java 代码是如何实现的
以类 dinn.cappjni.HelloJNI.java 为例(代码 3.3-1):
1 package dinn.cappjni; 2 3 public class HelloJNI { 4 5 static { 6 System.loadLibrary("first-lib"); // 加载本地库“first-lib”,即 代码 3.2-2 7 } 8 9 public native void printHello(); // ① 使用【native】关键字申明本地方法,该方法与用C++编写的JNI本地函数相对应。 10 11 public native String printString(String str); 12 }
那么C++代码如何写呢?我们可通过命令(javah -jni xxx)生成(注意目录要定位到 java 这层,也可通过 -classpath 重写定位位置。):
生成成功后,刷新工程目录,就可以看见生成的文件:
然后,我们可以将文件移动到cpp目录下。下面是 dinn_cappjni_HelloJNI.h 的内容(代码 3.3-2):
1 /* DO NOT EDIT THIS FILE - it is machine generated */ 2 #include <jni.h> 3 /* Header for class dinn_cappjni_HelloJNI */ 4 5 #ifndef _Included_dinn_cappjni_HelloJNI 6 #define _Included_dinn_cappjni_HelloJNI 7 #ifdef __cplusplus 8 extern "C" { // extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。 9 #endif 10 /* 11 * Class: dinn_cappjni_HelloJNI 12 * Method: printHello 13 * Signature: ()V 14 */ 15 JNIEXPORT void JNICALL Java_dinn_cappjni_HelloJNI_printHello (JNIEnv *, jobject); 16 17 /* 18 * Class: dinn_cappjni_HelloJNI 19 * Method: printString 20 * Signature: (Ljava/lang/String;)Ljava/lang/String; 21 */ 22 JNIEXPORT jstring JNICALL Java_dinn_cappjni_HelloJNI_printString (JNIEnv *, jobject, jstring); 23 24 #ifdef __cplusplus 25 } 26 #endif 27 #endif
接下来,我们实现 dinn_cappjni_HelloJNI.cpp 的内容(代码3.3-3):
1 #include <string> 2 #include "dinn_cappjni_HelloJNI.h" 3 4 extern "C" { 5 /* 6 * Class: dinn_appdemojni_HelloJNI 7 * Method: printhello 8 * Signature: ()V 9 */ 10 JNIEXPORT void JNICALL Java_dinn_cappjni_HelloJNI_printHello(JNIEnv *env, jobject obj) { 11 printf("Hello JNI!"); 12 return; 13 } 14 15 /* 16 * Class: dinn_appdemojni_HelloJNI 17 * Method: printString 18 * Signature: (Ljava/lang/String;)V 19 */ 20 JNIEXPORT jstring JNICALL 21 Java_dinn_cappjni_HelloJNI_printString(JNIEnv *env, jobject obj, jstring str) { 22 // 将String字符串转换成 C字符串 23 const char *chars = env->GetStringUTFChars(str, 0); 24 printf("%s! \n", chars); 25 26 std::string strOld = chars; 27 std::string strNew = "您输入的是:" + strOld; 28 return env->NewStringUTF(strNew.c_str()); 29 } 30 }
最后在 Java 中使用时其实很简单,直接调用类HelloJNI中的方法即可,如(代码 3.3-4)所示:
(new HelloJNI()).printHello();
至此,我们实现了 Java 调取本地 C/C++ 函数。那么 本地 C/C++ 库又是怎么调用 Java 的方法呢?
4 在 C 中调用 Java 方法
4.1 新建 Java文件 JniTest.java
1 public class JniTest { 2 3 private String content; 4 5 public JniTest(String content) { 6 this.content = content; 7 } 8 9 // 此方法由本地函数调用 10 public String getContent() { 11 return content; 12 } 13 }
4.2 在Java文件 HelloJNI.java 中新增获取对象JniTest的方法 createJniTestObject(), 注意要是静态方法:
1 public static native JniTest createJniTestObject();
4.3 生成该Java方法所对应的C++函数:
1 JNIEXPORT jobject JNICALL 2 Java_dinn_cappjni_HelloJNI_createJniTestObject(JNIEnv *, jclass);
此时我们注意到,生成的C++函数中的第二个参数为 jclass 类型,不再是 jobject。原因是什么呢?想 弄清楚这个,我们需要了解第二个参数的含义。前面的 jobject 类型变量用来保存调用本地方法的对象的引用。而此时的 java 方法为静态(static)的,而静态方法可以不用创建对象,可通过类名直接获取到,因此这里函数的第二个参数为 jclass 类型。
4.4 dinn_cappjni_HelloJNI.cpp
1 #include <string> 2 #include <android/log.h> // 引用日志的包 3 #include "dinn_cappjni_HelloJNI.h" 4 5 extern "C" { 6 #define TAG "日志【C】" 7 #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) 8 9 // ... 10 11 JNIEXPORT jobject JNICALL 12 Java_dinn_cappjni_HelloJNI_createJniTestObject(JNIEnv *env, jclass clazz) { 13 // 查找生成对象的类 14 jclass targetClass = env->FindClass("dinn/cappjni/JniTest"); // 这里要包含类的包名 15 16 // 查找构造方法 17 jmethodID mid = env->GetMethodID(targetClass, "<init>", "(Ljava/lang/String;)V"); 18 if (mid == NULL) return NULL; 19 20 // 生成 JniTest 对象(返回对象的引用) 21 jobject newObject = env->NewObject(targetClass, mid, env->NewStringUTF("【生成 JniTest 对象】")); 22 23 // 调用对象方法 getContent(); 24 mid = env->GetMethodID(targetClass, "getContent", "()Ljava/lang/String;"); 25 if (mid == NULL) return newObject; 26 jstring str = (jstring) env->CallObjectMethod(newObject, mid); 27 28 LOGI("【CPP】类JniTest中的变量content = %s\n", env->GetStringUTFChars(str, 0)); // 打印日志 29 return newObject; 30 } 31 32 }
说明:
第17行: env->GetMethodID(targetClass, "<init>", "(Ljava/lang/String;)V");
◆ 其中第二个参数<init>表示构造方法。如果是非构造方法,直接写方法名称,如第24行。
◆ 第三个参数表示Java变量/方法中的参数的签名。在调用某些JNI函数是,要求提供指定的成员变量或成员方法的签名。当然,开发者可以根据JNI规范中的Java签名生成规则,直接创建签名。但不建议这么做,java系统会为类的成员变量或成员方法生成签名。使用时,只需要使用javap 命令(Java反编译器),即可轻松获取指定的成员变量或成员方法的签名。
形式:javap [选项] '类名(.class后缀的文件,我们可通过javac编译得到,或者在Android Studio中的build\intermediates\...目录下找到)'
选项:-s 输出java签名
-p 输出所有类及成员
以JniTest.class为例,如下图所示:
红线处即是该成员方法的签名。
最后在Java中我们可以获取到 dinn_cappjni_HelloJNI.cpp 返回的JniTest对象。
1 JniTest jniTest = HelloJNI.createJniTestObject(); 2 if (jniTest != null) 3 Log.i("日志(Java)", jniTest.getContent()));