1、Jni基礎知識
JNI是Java Native Interface的縮寫,意思是Java的本地接口,這個本地接口主要指Java可以通過本地接口去和其他的編程語言通信,有時在開發某個功能時想使用之前的技術積累或封裝好的模塊,但不幸的是之前不是用Java開發的,那對於此中情況該如何處理呢?對於經過時間驗證的可靠程序不可能輕易重寫和修改,所以就需要JNI作爲程序的中轉樞紐;
- Jni使用場景
- 需要調用Java語言不支持的依賴時,
- 整合非Java語言開發的系統,如C、C++
- 節省運行時間提高運行效率,如:音視頻等
- Jni類型和Java類型的映射關係
既然Jni是Java和其他語言的溝通橋樑,那麼它既必須有一套基礎協議作爲與Java代碼溝通的基礎,這個基礎就是類型的映射和簽名,類型映射就是在Java類型和Jni中的類型建立一一對應關係,從而實現二者的類型可讀性和唯一性,簽名指Java中類型、方法、屬性等在Java中的展現形式,根據最終的簽名查找方法的對應關係;
- native方法與Jni映射實例
public static native String action(short s , int i, long l, float f, double d, char c,
boolean z, byte b, String str, Object obj, ArrayList<String> array,
int[] arr, Action action);
//生成的Jni對應的代碼
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_action
(JNIEnv *, jclass, jshort, jint, jlong, jfloat, jdouble, jchar, jboolean, jbyte, jstring, jobject, jobject, jintArray, jobject);
- 基本數據映射關係
- 引用類型關係映射表
- Jni方法名:Java_類全路徑_方法名
//native
public native void test();
//jni
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_test (JNIEnv *, jobject);
上面是Java代碼中聲明的test()轉換後的Jni方法,此方法名稱在編譯javah文件時生成,在實現的C文件中重寫並實現即可,方法的命名規則:
- Java:表示C++實現Java方法的前綴
- com_alex_kotlin_jni_JniTest:JniTest的類名全路徑
- test:native方法名稱
- 參數規則
- JNIEnv *:每個native函數的入口參數,執行JVM函數表的指針,JNIEnv即可在Native環境中使用Java資源
- jobject:調用java中native方法的實例或class對象,如果native方法是普通方法,則該參數是jobject,如果是靜態方法,則是jclass
- 剩餘參數爲native方法的傳入參數,此處爲JNI中的映射類型(參照介紹映射關係)
- Jni簽名
- 數據類型簽名:見上面對照表
- 方法簽名:將參數簽名和返回值類型簽名組合一起作爲方法簽名
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_setTest
(JNIEnv *env, jobject cls, jstring j_str) {
}
方法簽名:(Ljava/lang/Object,Ljava/lang/String)Lava/lang/String
1.1、 JNI 函數註冊
- 靜態註冊
靜態註冊JNI方法很簡單,我們在Java中聲明native方法後,會執行Java命令編譯和生成Jni方法:
javac ***
javah ***
在執行javah的命令後,系統會在之間文件處創建.h文件,當我們在Java層調用native方法時,系統就會根據JNI方法命名規則,按照JNI方法名尋找對應的方法,在找到JNI方法後保存JNI指針建立方法間的聯繫,下次調用時直接使用函數指針就可以,不過靜態註冊在初次調用時需要查找再建立關聯,影響效率,與靜態註冊對應的就是動態註冊,不需要編譯和查找關聯;
- 動態註冊
- 在C++文件中實現native方法,此時方法名並沒有嚴格要求
JNIEXPORT jstring JNICALL native_method(JNIEnv *env, jobject) {
return env->NewStringUTF("Register method in Jni");
};
- 創建註冊的方法數組,在數組中建立Java方法和Jni方法的對應關係
static JNINativeMethod methods[] = {
//參數:1、Java聲明的native方法名;2、方法簽名;3、c中實現的方法名
{"method", "()Ljava/lang/String;", (void *) native_method},
};
- 在重寫的JNI_OnLoad()中調用註冊方法實現註冊
// 聲明動態註冊對應的Java類的路徑
static const char *const PACKAGE_NAME = "com/alex/kotlin/jni/JniTest";
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
JNIEnv *env; //獲取JNIEnv
if (JNI_OK != vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6)) {
return JNI_ERR;
}
jclass jclass1 = env->FindClass(PACKAGE_NAME); //根據類名獲取jclass
if (jclass1 == NULL) {
return JNI_ERR;
}
jclassGlobal = static_cast<jclass>(env->NewWeakGlobalRef(jclass1)); //創建全局緩存jclass
env->DeleteLocalRef(jclass1); //釋放局部變量
if (JNI_OK != env->RegisterNatives(jclassGlobal, method, 1)) { //註冊方法
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
在創建的C++文件中重寫Jni.h中的JNI_OnLoad()方法,在JNI_OnLoad中首先根據報名獲取Java類的jclass對象,然後全局緩存jclass對象,最後調用RegisterNatives()方法傳入jclass和關係數組實現方法的註冊
- 在UnLoad()中解除方法註解
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return;
}
env->UnregisterNatives(jclassGlobal); //解除註冊
env->DeleteGlobalRef(jclassGlobal); //釋放全局變量
}
2、Jni基本使用
在介紹完JNI基礎知識後,一起來學習下JNI在開發中的基本使用,也就是Jni的基本語法,其實在上面動態註冊時已經使用到了一些,這裏根據使用頻率介紹下最常用的方法;
2.1、字符串使用
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_setTest
(JNIEnv *env, jobject cls, jstring j_str) {
const char *c_str = NULL;
char buff[128] = {};
jboolean copy;
c_str = env->GetStringUTFChars(j_str, ©); // 字符串訪問
if(c_str == NULL){
return NULL;
}
sprintf(buff, "Jni %s", c_str); //字符串輸出
env->ReleaseStringUTFChars(j_str, c_str); //字符串釋放
return env->NewStringUTF(buff); // 字符串創建
}
- 訪問字符串:GetStringUTFChars(j_str, ©)
- j_str:訪問的本地字符串,這裏爲參數傳入
- copy:表示引用是否拷貝,如果設置true則拷貝一份使用,false指向源字符串指針
- 對字符串的拷貝可能會因爲內存問題而失敗,在讀取本地字符串之後,一定要檢查字符串是否爲NULL再使用,爲空則返回
- ReleaseStringUTFChars釋放字符串資源,GetStringUTFChars/ReleaseStringUTFChars處理UTF編碼的字符串
- 釋放引用的資源:ReleaseStringUTFChars(j_str, c_str);
- 在C語言中獲取字符串,再使用完畢後需要釋放資源,否則造成內存溢出
- 一般Get***和Release***成對使用,針對不容的編碼選擇不同的方法
- 創建字符串:NewStringUTF()
env->NewStringUTF(“this is string !");
- 其餘字符串方法
- GetStringChars和ReleaseStringChars:用於創建和釋放Unicode格式編碼的字符串
const jchar *c_str = NULL;
c_str = env->GetStringChars(j_str, ©);
if (c_str == NULL) {
return NULL;
}
sprintf(buff,"Jni %s",c_str); //將字符串緩存到buff中
env->ReleaseStringChars(j_str, c_str);
- GetStringLength:獲取Unicode編碼的字符串的長度
jsize lenUtf = env->GetStringLength(j_str);
- GetStringUTFLength:獲取UTF編碼的字符串的長度
jsize lenUtf = env->GetStringUTFLength(j_str);
- GetStringCritical和ReleaseStringCritical:提高JVM返回源字符串直接指針的可能性,前面的獲取字符串會拷貝並分配內存,此方法直接讀取字符串無需分配內存,但不能在中間臨界區間調用Jni方法和線程阻塞程序
const jchar *c_str = NULL;
char buff[128] = {};
jboolean copy;
c_str = env->GetStringCritical(j_str, ©); //讀取字符串
if (c_str == NULL) {
return NULL;
}
sprintf(buff,"Jni %s",c_str);
env->ReleaseStringCritical(j_str, c_str); //釋放字符串
return env->NewStringUTF(buff);
- GetStringRegion和GetStringUTFRegion:截取UTF和Unicode格式字符串的部分內容,會將源字符串拷貝到緩存區中
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_setTest(JNIEnv *env, jobject cls, jstring j_str) {
char buff[128] = {};
jsize lenUtf = env->GetStringUTFLength(j_str); //讀取字符串長度
env->GetStringUTFRegion(j_str, 0, 4, buff); //截取字符串0~4緩存到buff中
return env->NewStringUTF(buff); //創建字符串
}
//輸入 "From Native !” ,輸出結果:“From”
2.2、數組操作
- 訪問Java傳入的數組
Get<PrimitiveType>ArrayElements(ArrayType array, jboolean *isCopy)
- 參數isCopy表示是否拷貝返回副本,如果返回的指針指向Java數組地址而非副本,此時會阻止Java對數組的回收,但創建副本時可能因內存問題創建失敗,使用前應檢查NULL
- 釋放數組使用:ReleaseArrayElements
- 獲取數組和釋放數組必須配對使用
- Release中的最後參數mode針對拷貝副本時可設置三種形式,可根據是否需要回寫數組進行選擇
* 0:將 elems 內容回寫到 Java 數組並釋放 elems 佔用的空間,回寫的意思是會去修改原Java中的數組
* JNI_COMMIT:將 elems 內容回寫到 Java 數組,但不釋放 elems 的空間;
* JNI_ABORT:不回寫 elems 內容到 Java 數組,釋放 elems 的空間。
- 使用實例
jint *array ;
jboolean jboolean1;
array = env->GetIntArrayElements(jintArray1, &jboolean1); //獲取集合
if (array == NULL){
return;
}
array[2] = 5; //修改集合中的參數
env->ReleaseIntArrayElements(jintArray1,array,0); //釋放array集合並寫入Java集合
//在Java中調用Jni
int[] number = new int[]{1,2,3,4,5};
Log.e("Before Jni=====",number[2] + "");
test.getAction(number); //調用Jni方法
Log.e("After Jni=====",number[2] + "");
2019-04-29 22:09:07.698 15989-15989/com.alex.kotlin.jni E/Before Jni=====: 3
2019-04-29 22:09:07.698 15989-15989/com.alex.kotlin.jni E/After Jni=====: 5 //3改變程5
//使用JNI_ABORT
2019-04-29 22:11:55.485 16108-16108/com.alex.kotlin.jni E/Before Jni=====: 3
2019-04-29 22:11:55.485 16108-16108/com.alex.kotlin.jni E/After Jni=====: 3 //不變
由上面的運行結果知道,在ReleaseIntArrayElements()傳入0時,Java層傳入的數組在執行方法後被改變,從而驗證了將數據回寫到Java的結論,在使用JNI_ABORT時,Java中數據並未發生改變;
- GetArrayLength(j_array):獲取Array的長度
- malloc(len):申請len長度的緩存區
- memset (c_array,0,len):初始化長度爲len的array集合
- memcpy (buffer, data, len):將數組中指定長度的數據拷貝到buff數組中
jbyte* data = env->GetByteArrayElements(javaArray, NULL);
if (data != NULL) {
memcpy(buffer, data, len);
env->ReleaseByteArrayElements(javaArray, data, JNI_ABORT);
}
- GetIntArrayRegion(env,j_array,0,arr_len,c_array):複製源集合中指定長度的數據到目標集合中,和上面塊拷貝功能一致
env->GetByteArrayRegion(javaArray, 0, len, buffer);
- 訪問對象數組
- FindClass(env,"[I”):獲取Int數據引用類型
- NewObjectArray(env,size,clsIntArray,NULL):創建一個數組對象
- NewIntArray(env,size):創建一個Int數組
3、C/C++與Java的訪問
- C訪問Java中靜態方法,實現步驟
- 根據訪問的類路徑調用FindClass()獲取Class對象
- 根據class、方法名、參數、返回值等條件獲取Java方法MethodId
- 跟據class、methodId執行Java方法
public class Action {
public static void actionStatic(){
Log.e("=======","Static method from action in Java ") ;
}
}
//Jni方法
JNIEXPORT void JNICALL Java_com_alex_kotlin_jni_JniTest_test
(JNIEnv *env, jobject) {
jclass cls = NULL;
jmethodID method_Id = NULL;
cls = env->FindClass("com/alex/kotlin/jni/Action”); //查找Action類文件
method_Id = env->GetStaticMethodID(cls, "actionStatic", "()V”); //根據類、方法名、參數條件獲取MethodId
env->CallStaticVoidMethod(cls,method_Id); //調用靜態方法
env->DeleteGlobalRef(cls); //釋放class
}
2019-04-27 17:25:27.580 10961-10961/com.alex.kotlin.jni E/=======: Static method from action in Java
- 訪問Java成員方法,訪問步驟:
- 根據類的全路徑查找class
- 根據方法簽名,查找構造函數的方法methodId
- 執行構造函數方,創建類的實例
- 根據方法名、參數、返回值查找調用方法的id
- 使用創建的實例調用相應的方法
public class Action {
public void action(){
Log.e("=======","Method from action in Java ") ;
}
}
//Jni中代碼
jclass clss = NULL;
jmethodID method_Id = NULL;
jmethodID construct_Id = NULL;
jobject obj = NULL;
clss = env->FindClass("com/alex/kotlin/jni/Action”); //查找Action類
construct_Id = env->GetMethodID(clss, "<init>", "()V”); //獲取構造函數方法的Id
obj = env->NewObject(clss, construct_Id); //創建構造Action實例
method_Id = env->GetMethodID(clss, "action", "()V”); //創建action()方法的Id
env->CallVoidMethod(obj,method_Id); //調用action()方法
2019-04-27 17:42:31.774 11880-11880/com.alex.kotlin.jni E/=======: Method from action in Java
- 訪問Java靜態實例,訪問步驟:
- 查找類的class
- 根據屬性名稱、屬性的數據類型獲取fieldId
- 根據fieldId訪問屬性
public class Action {
private static int number = 100;
public int getNumber() {
return number;
}
public void setNumber(int number) {
Action.number = number;
}
}
- 聲明訪問變量的native方法
public native void getStaticInstance();
- 在Jni方法中訪問靜態變量
cls = env->FindClass("com/alex/kotlin/jni/Action”); //獲取功能類
field_id = env->GetStaticFieldID(cls, "number", "I”); //獲取靜態變量Id
number = env->GetStaticIntField(cls, field_id); //從類中獲取靜態變量
jint num = 555;
env->SetStaticIntField(cls, field_id, num); //爲靜態變量賦值
- 調用和執行方法
Action action = new Action();
action.setMessage("Message in Java");
action.setNumber(123);
Log.e("Before======", action.getNumber() + "");
test.getStaticInstance();
Log.e("After Jni======", action.getNumber() + "");
2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/Before======: 123
2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/After Jni======: 555
- 訪問Java成員實例(需要傳入實例)
- 根據傳入的object獲取類的class
- 根據參數名、參數類型獲取fieldId
- 根據fieldId和object訪問屬性
public class Action {
private String message = null;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
- 聲明訪問變量的native方法
public native void getInstance(Action action);
- Jni中讀取成員變量
cls = env->GetObjectClass(obj); //從參數object中獲取class
field_id = env->GetFieldID(cls, "message", "Ljava/lang/String;”); //根據參數名和類型獲取Field_Id
str = static_cast<jstring>(env->GetObjectField(obj, field_id)); //根據field_Id從obj中獲取實例
new_str = env->NewStringUTF("Message in Jni”); //創建新的String
env->SetObjectField(obj, field_id, new_str); //設置Obj中的數值
- 訪問靜態實例
Log.e("Before======", action.getMessage() + "");
test.getInstance(action);
Log.e("After Jni======", action.getMessage() + "");
2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/Before======: Message in Java
2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/After Jni======: Message in Jni
- 訪問構造函數:由上面的實例知道訪問構造函數分三步
- FindClass()查找類的jclass
- GetMethodID(clss, “”, "()V”):獲取構造函數的MethosId
- NewObject(clss, construct_Id):創建實例
- 訪問父類的方法
- FindClass()查找類的jclass
- GetMethodID(clss, “”, "()V”):獲取構造函數的MethosId
- NewObject(clss, construct_Id):創建類的實例
- 使用FindClas s查找父類的jclass對象
- 獲取父類方法的MethodId
- 調用父類方法,此處需要同時傳入子類和父類的對象
public class Action {
public void action(){
Log.e("==========","Action in Action");
}
}
public class ChildAction extends Action {
@Override
public void action() {
Log.e("==========","Action in ChildAction");
}
}
cls = env->FindClass("com.alex.kotlin.jni.Action”); //1、
jmethodID1 = env->GetMethodID(cls, "<init>", "()V”); //2、
jobject1 = env->NewObject(cls, jmethodID1); //3、
cls_parent = env->FindClass("com.alex.kotlin.jni.ChildAction”); //4、
jmethodID2 = env->GetMethodID(cls_parent, "action()", "()V”); //5、
env->CallNonvirtualVoidMethod(jobject1, cls_parent, jmethodID2); //6、
4、引用類型
- 局部引用
- 局部引用不能跨方法和跨線程使用,相當於Java中的局部變量
- 會阻止GC的回收,在方法結束返回Java後對象沒被引用會自動釋放,如果被引用則阻止回收
- 在方法中使用基本方法創建的都是局部變量,可以使用DeleteLocalRef釋放
- 局部變量使用完後要及時釋放資源,否則當變量個數超過512個時拋出異常
- 推薦使用Push/PopLocalFrame(創建一個局部變量的引用棧),在方法的入口使用PushLocalFrame,在每個返回的地方調用PopLocalFrame,這樣在函數中創建的任何變量都會被釋放;
- 全局引用
- 可以跨方法、跨線程使用,相當於Java中的成員屬性
- 只能通過NewGlobalRef函數創建
- 會組織對象被GC回收,只有手動調用DeleteGlobalRef釋放
- 弱全局引用
- 使用NewGlobalWeakRef創建,使用DeleteGlobalWeakRef釋放
- 可以跨方法、跨線程使用
- 不會阻止GC回收,當內存不足時會被回收,相當於Java中的若引用
- 在使用時需要檢查引用的對象是否被回收,所以每次引用之前需要做非空判斷
- 引用比較
- IsSameObject(env, obj1, obj2):如果obj1和obj2指向相同的對象,則返回JNI_TRUE(或者1),否則返回JNI_FALSE(或者0)
- 局部引用和全局引用使用IsSameObject與NULL比較判斷是否爲null
- IsSameObject用於弱全局引用與NULL比較時,返回值的意義是不同於局部引用和全局引用,此時判斷對象是否被回收
- 管理引用規則
- 不要造成全局引用和弱引用的增加
- 不要在函數軌跡上遺漏任何的局部引用
- 在方法對外返回實例時,要註釋清楚返回的是全局、局部變量
5、其他知識
5.1、緩存
- 使用時緩存
- 使用靜態字段緩存數據,在第一次加載初始化,之後直接使用緩存數據
- 不能緩存局部引用,局部引用釋放後容易造成緩存的空指針
- 靜態初始化時緩存
- 在引入庫時直接調用native方法initIDs
- 在c文件中實現initIds方法,在其中緩存資源
static {
System.loadLibrary("AccessCache");
initIDs();
}
5.2、Jni異常處理
- Jni中沒有像try…catch()異常處理機制
- Jni中拋出異常程序不會立刻執行,此時爲了停止程序調用ThrowNew()手動拋出異常後調用return結束函數
- Jni中捕獲異常的方法
- ExceptionCheck():檢查Java是否拋出異常
- ExceptionDescribe():打印Java異常堆棧信息
- ExceptionClear():清除異常堆棧信息
- ThrowNew():手動拋出一個java.lang.Exception異常
//Java 拋出異常代碼
public void actionException() throws Exception {
throw new Exception("Java 拋出異常");
}
//C文件中調用
jmethodID1 = env->GetMethodID(cls, "actionException", "()V");
env->CallVoidMethod(obj, jmethodID1);
if (env->ExceptionCheck()) { //檢查並拋出異常
env->ExceptionDescribe();
env->ExceptionClear();
env->ThrowNew(env->FindClass("java/lang/Exception"), "Jni 拋出異常");
}
//運行結果
2019-04-29 19:12:54.919 32350-32350/com.alex.kotlin.jni W/System.err: java.lang.Exception: Java 拋出異常
2019-04-29 19:12:54.920 32350-32350/com.alex.kotlin.jni E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.alex.kotlin.jni, PID: 32350
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.alex.kotlin.jni/com.alex.kotlin.jni.MainActivity}: java.lang.Exception: Jni 拋出異常
- 使用ExceptionOccurred捕獲異常
env->CallVoidMethod(obj, jmethodID1);
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
env->ExceptionClear();
env->ThrowNew(env->FindClass("java/lang/Exception"), "Jni 拋出異常");
}
- 異常發生後釋放資源
if ((*env)->ExceptionCheck(env)) { /* 異常檢查 */
(*env)->ReleaseStringChars(env, jstr, cstr); // 發生異常後釋放前面所分配的內存
return;
}
6、使用Jni生成So庫文件
對於我們正常開發來說,直接使用Jni的場景很少,一般Jni的方法都會封裝在So庫中供Java層調用,現在就根據上面的Jni知識利用AS生成一個So庫。
6.1、Android Studio 配置Jni
- 下載NDK和構建工具
- 在Android Studio的SDK Manager中選擇LLDB、CMake、NDK下載
- 配置ndk-build
- 在local.properties文件中配置ndk文件路徑
ndk.dir=/Users/wuliangliang/Library/Android/sdk/ndk-bundle
- 創建C++項目
- 創建新的項目選擇C++工程
- 創建完成後,項目中首先會有一個cpp文件夾,其中已配置好Jni開發的Demo(可以作爲參考)
- 在main文件夾下創建jni文件夾
- 配置CMakeList.txt文件,C代碼生成so庫的配置文件
- 創建text或複製cpp文件夾下的CMakeList.txt
cmake_minimum_required(VERSION 3.4.1) :配置So庫的最小版本
add_library( //每個C文件具備一個add_library
jni-lib // 配置So庫名稱
SHARED //設置So庫設置爲Shared共享庫
test.cpp) // 源C文件的相對路徑
find_library( //,
log-lib
log)
target_link_libraries(
jni-lib // 指定目標庫
${log-lib}) // 將目標庫連接到NDK的日誌庫
上面配置中:
- add_library()作用是創建並命名一個So庫,“jni-lib”就是最終生成So庫的名程,最終生成的So庫名稱爲“linjni-lib.so”文件
- test.cpp爲對應So庫的C代碼文件,系統在編譯時會自動定位和關聯此文件,
- 由於NDK已經是CMake搜索的一部分,所以在使用時只需要向NDK設置要使用庫的名稱,在配置文件中使用find_library()定位Ndk,並將其路徑存儲爲變量設,在之後構建腳本時使用此變量待指NDK,此處設置的log-lib就是NDK的變量
- 爲了保證原生庫可以在log中調用函數,需要使用target_link_libraries()命令關聯庫
- build.gradle中配置:關聯CMakeList的配置文件路徑和Jni文件路徑
android{
externalNativeBuild {
cmake {
path "src/main/jni/CMakeLists.txt” //配置CMakeList的文件路徑
}
}
sourceSets { main { jni.srcDirs = ['src/main/jni', 'src/main/jni/'] } } //配置Jni文件輸出路徑
}
6.2、創建Native代碼,生成並使用So庫
- 創建Java文件並聲明native方法
public class JniTest {
static {
System.loadLibrary("jni-lib”); //引入so庫
}
public static native String test(); //配置native方法
}
- 在jni文件夾下生成編譯後的.h文件
- 創建cpp文件實現native方法
JNIEXPORT void JNICALL Java_com_alex_kotlin_jni_JniTest_test
(JNIEnv *env, jobject) {
jclass cls = NULL;
jmethodID method_Id = NULL;
cls = env->FindClass("com/alex/kotlin/jni/Action");
if (cls == NULL) {
return;
}
method_Id = env->GetStaticMethodID(cls, "test", "()V");
env->CallStaticVoidMethod(cls, method_Id);
env->DeleteGlobalRef(cls);
}
這裏調用FindClass()根據類名獲取Class對象,然後使用全局變量保存Class對象,然後查找並調用actionStatic()
- 根據so庫名稱配置CMakeList.txt後執行Make Project,系統會自動在build文件夾下創建so庫
- 調用native方法
JniTest test = new JniTest();
tv.setText(test.test());
關於Jni的基本知識點和用法基本介紹完了,在一般的開發中可能使用的不多,但想做進一步的功能或優化時就經常會使用到,所以Jni也成爲Android高級開發這必備基礎知識,希望此篇文章的總結對需要的同學有所幫助;