Android Studio NDK開發-JNI調用Java方法

相對于NDK來說SDK里面有更多API可以調用,有時候我們在做NDK開發的時候,需要在 JNI 直接Java中的方法和變量,比如 callback ,系統信息等....

如何在 JNI 中調用Java方法呢?就需要先了解 FindClass 和 GetMethodID 了。

FindClass和GetMethodID

在JNI中可以通過 FindClass 可以找到Java類,得到 jclass ,例如:

jclass clz=(*env)->FindClass(env,"com/jjz/JniHandle");

FindClass 的第二個參數需要傳入類的完整包名。

使用 GetMethodID 可以獲取類的方法,得到jmethodID,例如:

jmethodID getStringFromJava=(*env)->GetMethodID(env,class,"getStringForJava","()V");

如果調用的是靜態方法需要使用 GetStaticMethodID 獲取。通過 FindeClass 可以在JNI中找到需要調用的類, GetMethodID 可以找到對應的方法,這樣就可以在JNI中調用Java的方法了。

在GetMethodID中,第四個參數是 ()V ,這個是方法簽名。那么方法簽名的規則又是怎么樣呢?

方法簽名

在 GetMethodID 中第四個參數 ()V 就是方法簽名,Java是支持重載的,所以需要標明方法的傳參和返回值,這就是方法的簽名。它是用來保證方法的唯一性。其中 () 代表不傳參數, V 代表返回值為 void

方法簽名對于Java的類型都有一一對應的值。方法簽名中用大寫的字母對應了java的基本數據類型:

  • Z -> boolean
  • B -> byte
  • C -> char
  • S -> short
  • I -> int
  • J -> long
  • F -> float
  • D -> double

其實就是有兩個比較特殊的: boolean 對應的是Z, long 對應的J,其他的都是首個字母的大寫即可。

數組的表示方法,以 [ 為標志,一個 [ 標識一維數組, [[ 表示二維數組,例如:

  • byte[] -> [B
  • int[][] -> [[I

引用類型的表示方法,需要以 L 開頭,以 ; 結束,中間對應類型的包名加類名,例如:

  • String -> Ljava/lang/String;
  • Object -> Ljava/lang/Object;

自定義類的表示方法,比如包名為 jjz.example ,類名為 JniHandle 的表示方法:

  • jjz.example.JniHandle ->Ljjz/example/JniHandle;

除了手動輸入類名和方法簽名以外,JDK還提供了直接生成方法簽名的工具 javap 。

在 build 之后可以在路徑 ../app/build/intermediates/classes/debug 下可以找到build之后生成的 .class 文件,運行命令:

javap -s com/jjz/JniHandle

就可以得到這個類的所有的方法簽名:

Compiled from "JniHandle.java"
public class com.jjz.JniHandle {
  public com.jjz.JniHandle();
    descriptor: ()V

  public static java.lang.String getStringFromStatic();
    descriptor: ()Ljava/lang/String;

  public java.lang.String getStringForJava();
    descriptor: ()Ljava/lang/String;
}

有了類的引用,和方法的簽名就可以直接在 JNI 中調用Java方法了,下面分別介紹下靜態方法和類方法的調用。

靜態方法的調用

調用類的靜態方法,首先要得到類的引用,再調用類的靜態方法。

先定義一個類和靜態方法用來提供給 JNI 調用:

public class JniHandle {

    public static String getStringFromStatic() {
        return "string from static method in java";
    }
}

在定義了一個 native 方法 com.jjz.NativeUtil.callJavaStaticMethodFromJni ,生成這個方法的 JNI 代碼,在 JNI 代碼中調用 JniHandle 類的靜態方法:

JNIEXPORT void JNICALL
Java_com_jjz_NativeUtil_callJavaStaticMethodFromJni(JNIEnv *env, jclass type) {

    jclass jniHandle = (*env)->FindClass(env, "com/jjz/JniHandle");
    if (NULL == jniHandle) {
        LOGW("can't find JniHandle");
        return;
    }
    jmethodID getStringFromStatic = (*env)->GetStaticMethodID(env, jniHandle, "getStringFromStatic",
                                                              "()Ljava/lang/String;");
    if (NULL == getStringFromStatic) {
        (*env)->DeleteLocalRef(env, jniHandle);
        LOGW("can't find method getStringFromStatic from JniHandle ");
        return;
    }
    jstring result = (*env)->CallStaticObjectMethod(env, jniHandle, getStringFromStatic);
    const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL);
    (*env)->DeleteLocalRef(env, jniHandle);
    (*env)->DeleteLocalRef(env, result);
    LOGW(resultChar);

}

在Java中調用 com.jjz.NativeUtil.callJavaStaticMethodFromJni 可以該方法可以在logcat中看到 string from static method in java ,這樣就完成了在 JNI 中調用了 Java 靜態方法。

類方法的調用

調用類方法要更加的復雜一些,調用步驟:

  1. 通過findClass找到類
  2. 通過GetMethodID得到構造函數
  3. 通過調用構造函數得到一個類的實例
  4. 通過GetMethodID得到需要調用的方法
  5. 使用類的實例調用方法

先定義一個類方法:

public class JniHandle {
    public String getStringForJava() {
        return "string from method in java";
    }
}

再定義一個 native 方法: com.jjz.NativeUtil.callJavaMethodFromJni ,生成該方法的 JNI 代碼,在 JMI 代碼中實現調用 JniHandle 的類方法 getStringForJava ,代碼如下:

JNIEXPORT void JNICALL
Java_com_jjz_NativeUtil_callJavaMethodFromJni(JNIEnv *env, jclass type) {

    jclass jniHandle = (*env)->FindClass(env, "com/jjz/JniHandle");
    if (NULL == jniHandle) {
        LOGW("can't find jniHandle");
        return;
    }
    jmethodID constructor = (*env)->GetMethodID(env, jniHandle, "<init>", "()V");
    if (NULL == constructor) {
        LOGW("can't constructor JniHandle");
        return;
    }
    jobject jniHandleObject = (*env)->NewObject(env, jniHandle, constructor);
    if (NULL == jniHandleObject) {
        LOGW("can't new JniHandle");
        return;
    }
    jmethodID getStringForJava = (*env)->GetMethodID(env, jniHandle, "getStringForJava",
                                                     "()Ljava/lang/String;");
    if (NULL == getStringForJava) {
        LOGW("can't find method of getStringForJava");
        (*env)->DeleteLocalRef(env, jniHandle);
        (*env)->DeleteLocalRef(env, jniHandleObject);
        return;
    }
    jstring result = (*env)->CallObjectMethod(env, jniHandleObject, getStringForJava);
    const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL);
    (*env)->DeleteLocalRef(env, jniHandle);
    (*env)->DeleteLocalRef(env, jniHandleObject);
    (*env)->DeleteLocalRef(env, result);
    LOGW(resultChar);

}

調用方法 com.jjz.NativeUtil.callJavaMethodFromJni 即可看到Java中的字符串傳遞給了 JNI 最后輸出到了 Logcat 上。

在上面的代碼中有一個方法叫做 DeleteLocalRef ,它的意思是釋放局部引用, Android VM 釋放局部引用有兩種方法:

  • 本地方法執行完畢之后 VM 自動釋放
  • 通過調用 DeleteLocalRef 手動釋放

既然上面說了 VM 會自動釋放引用為什么還需要手動釋放呢?

其實某些局部變量會阻止它所引用的對象被GC回收,它所引用的對象無法被GC回收,自己本身也就無法被自動釋放,因此需要使用 DeleteLocalRef 。而這里使用了JNI Local Reference,在JNI中引用了Java對象,如果不使用 DeleteLocalRef 釋放的話,引用無法回收,就會造成內存泄露。

 

來自:https://juejin.im/post/58dedf5a8d6d810061454e7b

 

 本文由用戶 kthb5315 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!