1. JNI開發(fā)流程
創(chuàng)建Native C++工程,這部分可用參考[Android JNI開發(fā)詳解(2)-開發(fā)環(huán)境搭建](Android JNI開發(fā)工具篇(1)-開發(fā)環(huán)境搭建.md)
創(chuàng)建Java層本地接口調(diào)用類,并定義好相應的本地函數(shù)。
將Java源代碼編譯成class字節(jié)碼文件(Android studio會自動生成)。
創(chuàng)建對應的本地函數(shù)接口,并注冊本地函數(shù),Jni函數(shù)注冊有兩種方式,靜態(tài)注冊和動態(tài)注冊,靜態(tài)注冊使用
javah工具自動生成對應的本地接口函數(shù)定義,動態(tài)注冊則需要在JNI_OnLoad函數(shù)中調(diào)用RegisterNatives來完成注冊。實現(xiàn)本地函數(shù)接口函數(shù)的具體功能實現(xiàn)。
修改
CMakeLists.txt或Android.mk。編譯測試。

2. JNI函數(shù)注冊
2.1 JVM查找native方法有兩種方式
JNI技術是Java世界與Native世界的通信橋梁,具體到代碼,Java層的代碼如何同Native層的代碼進行調(diào)用的呢?我們都知道,在調(diào)用native方法之前,首先要調(diào)用System.loadLibrary接口加載一個實現(xiàn)了native方法的動態(tài)庫才能正常訪問,否則就會拋出java.lang.UnsatisfiedLinkError異常 。 那么,在Java中調(diào)用某個native方法時,JVM是通過什么方式,能正確的找到動態(tài)庫中C/C++實現(xiàn)的那個native函數(shù)呢?
JVM查找native方法有兩種方式:
按照JNI規(guī)范的命名規(guī)則。
調(diào)用JNI提供的RegisterNatives函數(shù),將本地函數(shù)注冊到JVM中。
第一種方式,可用使用javah工具按照Java類中定義的native方法,按照JNI規(guī)范的命名規(guī)則的方式自動生成Jni本地C/C++頭文件。第二種方式則需要在本地庫的JNI_OnLoad函數(shù)中調(diào)用RegisterNatives來動態(tài)注冊。
JNI函數(shù)注冊是將Java層聲明的Native方法同實際的Native函數(shù)綁定起來的實現(xiàn)方式,也就是說,只要通過JNI函數(shù)注冊機制注冊了本地方,Java層就可以直接調(diào)用定義的這些本地方法了。對應上述JVM查找native方法的兩種方式,JNI函數(shù)注冊方式一般分為靜態(tài)注冊和動態(tài)注冊兩種方式。
2.2 javah工具和javap工具
為了方便我們進行完成注冊操作,我們經(jīng)常還會用到javah和javap兩個命令行工具。
-
javah工具用于生產(chǎn)本地方法頭文件
在JDK1.7中,在src目錄(android studio工程在src/main/java目錄)下執(zhí)行javah 全類名
在JDK1.6中,在bin/classes目錄下執(zhí)行
執(zhí)行前需要先編譯通過(編譯為class文件),大多數(shù)IDE會自動執(zhí)行編譯,因此不需要在手動進行編譯
javap工具用于打印方法簽名
2.3 靜態(tài)注冊
靜態(tài)注冊實際十分簡單,就是使用上面提到的javah工具為我們生產(chǎn)本地方法頭文件,然后在根據(jù)生成的頭文件完成相應的函數(shù)即可。靜態(tài)注冊即本地函數(shù)按照特定的命名規(guī)則命名本地方法,使得這些本地方法與Java類中定義的本地方法一一對應,在執(zhí)行JNI調(diào)用時,只需要根據(jù)命名規(guī)則去調(diào)用so庫中對應的方法即可。
下面時一個使用靜態(tài)注冊的例子,Test類定義了Java中的本地方法。
package cc.ccbu.jnitest;
public class Test {
static {
System.loadLibrary("native-lib");
}
public native String textFromJni();
}
使用javah生成對應的本地方法頭文件。
#include <jni.h>
/* Header for class cc_ccbu_jnitest_Test */
#ifndef _Included_cc_ccbu_jnitest_Test
#define _Included_cc_ccbu_jnitest_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: cc_ccbu_jnitest_Test
* Method: textFromJni
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_cc_ccbu_jnitest_Test_textFromJni
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
我們只用在我們的.c或cpp文件實現(xiàn)Java_cc_ccbu_jnitest_Test_textFromJni方法即可。
2.4 動態(tài)注冊
對應與上面的靜態(tài)注冊方法,還有一種動態(tài)注冊JNI函數(shù)的方式,即動態(tài)注冊。動態(tài)注冊是當Java層調(diào)用System.loadLibrary方法加載so庫后,本地庫的JNI_OnLoad函數(shù)會被調(diào)用,在JNI_OnLoad函數(shù)中通過調(diào)用RegisterNatives函數(shù)來完成本地方法的注冊。
本地so庫加載和卸載時,會調(diào)用JNI_OnLoad和JNI_OnUnload函數(shù),函數(shù)原型如下:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved);
動態(tài)注冊本地方法的函數(shù)RegisterNatives原型如下:
jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);
其中JNINativeMethod結(jié)構體用來描述本地方法結(jié)構,其定義如下:
typedef struct {
const char* name; // Java方法名
const char* signature; // Java方法簽名
void* fnPtr; // jni本地方法對應的函數(shù)指針
} JNINativeMethod;
下面通過一個例子來展示jni動態(tài)注冊本地方法的過程。還是以上面靜態(tài)注冊的Test類為例
-
在Java文件中定義本地方法,加載本地so庫
package cc.ccbu.jnitest; public class Test { static { System.loadLibrary("native-lib"); } public native String textFromJni(); } -
在JNI_OnLoad函數(shù)中注冊本地方法
jstring textFromJni(JNIEnv* env, jobject thiz) { return env->NewStringUTF("text from jni"); } static JNINativeMethod gMethods[] = { {"textFromJni", "()Ljava/lang/String;", (void*)textFromJni} }; int registerMethod(JNIEnv *env) { jclass test = env->FindClass("cc/ccbu/jnitest/Test"); return env->RegisterNatives(test, gMethods, sizeof(gMethods)/ sizeof(gMethods[0])); } JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } if (registerMethod(env) != JNI_OK) { return JNI_ERR; } return JNI_VERSION_1_6; }