Android-JNI解析

目錄

JNI概述

MediaRecorder框架中的JNI

Java Framework層的MediaRecorder

JNI層的MediaRecorder

Native方法注冊(cè)

數(shù)據(jù)類型的轉(zhuǎn)換

方法簽名

解析JNIEnv

參考《Android進(jìn)階解密》

JNI概述

JNI(Java Native Interface拉背,Java本地接口)幻赚,是Java與其他語言通信的橋梁欺栗。這不是Android系統(tǒng)所獨(dú)有的娃弓,而是Java所有烂完,當(dāng)出現(xiàn)一些用Java語言無法處理的任務(wù)時(shí),就可以使用JNI技術(shù)來實(shí)現(xiàn)徐绑。

JNI不只是應(yīng)用于Android開發(fā)诫咱,它有著非常廣泛的應(yīng)用場(chǎng)景。JNI在Android中的應(yīng)用主要有:音視頻開發(fā)刃唤、熱修復(fù)隔心、插件話、逆向開發(fā)尚胞、系統(tǒng)源碼調(diào)用等等济炎。為了方便使用JNI技術(shù),Android提供了NDK這個(gè)工具集合辐真,NDK開發(fā)是基于JNI的须尚,它和JNI開發(fā)本質(zhì)上并沒有區(qū)別,理解JNI原理侍咱,NDK開發(fā)也會(huì)很容易掌握耐床。

Android系統(tǒng)按語言來劃分的話分為兩個(gè)層面:分別是Java層Native層。通過JNI楔脯,Java層可以訪問Native層撩轰,同樣的Native層也可以訪問Java層。下面以MediaRecorder框架中的JNI舉例來理解系統(tǒng)中的JNI

MediaRecorder框架中的JNI

MediaRecorder是Android系統(tǒng)提供給我們用于錄音和錄像的框架昧廷。Java Framework層對(duì)應(yīng)的是MediaRecorder.java堪嫂,也就是我們平時(shí)開發(fā)在應(yīng)用中直接調(diào)用的類。JNI層對(duì)應(yīng)的是libmedia_jni.so木柬,它是JNI的一個(gè)動(dòng)態(tài)庫(kù)皆串。Native層對(duì)應(yīng)的是libmedia.so,這個(gè)動(dòng)態(tài)庫(kù)完成來實(shí)際的功能眉枕。

Java Framework層的MediaRecorder

我們先看一下MediaRecorder.java的源碼:

frameworks/base/media/java/android/media/MediaRecorder.java

public class MediaRecorder{
    static {
        //加載名字為media_jni的動(dòng)態(tài)庫(kù)
        System.loadLibrary("media_jni");
        native_init();
    }
    ......
    //JNI注冊(cè)
    private static native final void native_init();
    ......
    public native void start() throws IllegalStateException;
    ......
}

上述代碼指截取部分JNI相關(guān)的代碼:

  • 在靜態(tài)代碼塊中首先加載名字為media_jni的動(dòng)態(tài)庫(kù)恶复,也就是libmedia_jni.so怜森。
  • 然后接著調(diào)用了native_init ()方法,該方法會(huì)調(diào)用Native層方法谤牡,用來完成JNI的注冊(cè)副硅。
  • start()方法也是一個(gè)Native方法。

對(duì)于Java Framework層來說只需要加載對(duì)應(yīng)的JNI庫(kù)翅萤,接著聲明native方法就可以了恐疲,剩下的工作由JNI層來完成。

JNI層的MediaRecorder

MediaRecorderJNI層是由android_media_MediaRecorder.cpp實(shí)現(xiàn)套么,native方法:native_initstart的代碼實(shí)現(xiàn)如下:

frameworks/base/media/jni/android_media_MediaRecorder.cpp

static void
android_media_MediaRecorder_native_init(JNIEnv *env)
{
    jclass clazz;
    clazz = env->FindClass("android/media/MediaRecorder");
    if (clazz == NULL) {
        return;
    }
    ......
    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) {
        return;
    }
}

static void
android_media_MediaRecorder_start(JNIEnv *env, jobject thiz)
{
    ALOGV("start");
    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
    if (mr == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return;
    }
    process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed.");
}

android_media_MediaRecorder_native_init方法native_init方法JNI層的實(shí)現(xiàn)流纹;android_media_MediaRecorder_start方法start方法JNI層的實(shí)現(xiàn)。那么它們是如何找到對(duì)應(yīng)的方法的呢违诗?下面我們首先了解一下JNI方法注冊(cè)的知識(shí)。

Native方法注冊(cè)

Native方法注冊(cè)分為動(dòng)態(tài)注冊(cè)靜態(tài)注冊(cè)疮蹦,其中靜態(tài)注冊(cè)多用于NDK開發(fā)诸迟,而動(dòng)態(tài)注冊(cè)多用于Framework開發(fā)。下面分別來看一下這兩種注冊(cè)方式愕乎。

靜態(tài)注冊(cè)

在Android Studio中新建一個(gè)Java Library阵苇,命名為media,仿照系統(tǒng)的MediaRecorder.java感论,代碼如下:


public class MediaRecorder {
    static{
        System.loadLibrary("media_jni");
        native_init();
    }
    private static native final void native_init();
    public native void start() throws IllegalStateException;
}

編寫完成后绅项,對(duì)MediaRecorder.java進(jìn)行編譯和生成JNI方法:進(jìn)入項(xiàng)目的media/src/main/java目錄中,執(zhí)行以下命令:

javac com/example/media/MediaRecorder.java //編譯
javah com.example.media.MediaRecorder //生成頭文件

第二個(gè)命令會(huì)生成com_example_media_MediaRecorder.h文件比肄,內(nèi)容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_media_MediaRecorder */
#ifndef _Included_com_example_media_MediaRecorder
#define _Included_com_example_media_MediaRecorder
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_media_MediaRecorder
 * Method:    native_init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_media_MediaRecorder_native_1init
  (JNIEnv *, jclass);
/*
 * Class:     com_example_media_MediaRecorder
 * Method:    start
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_media_MediaRecorder_start
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

在Java中的native_init()方法被聲明為Java_com_example_media_MediaRecorder_native_1init方法快耿,以“Java”開頭說明是在Java平臺(tái)中調(diào)用JNI方法的,后面的com_example_media_MediaRecorder_native_1init指的是包名 + 類名 + 方法名的格式芳绩。我們會(huì)發(fā)現(xiàn)還多了一個(gè)1 ,這是因?yàn)镴ava中的native_init方法包含了"_"掀亥,轉(zhuǎn)換成JNI方法后變成了“_1”

此外方法還多了幾個(gè)參數(shù):

  • JNIEnv:是Native層中Java環(huán)境的代表妥色,通過該類型的指針就可以在Native層中訪問Java層的代碼搪花,它只在創(chuàng)建它的線程中有效,不能跨線程傳遞嘹害。
  • jclass:是JNI的屬性類型撮竿,對(duì)應(yīng)Java的java.lang.Class實(shí)例。
  • jobject:同樣也是JNI屬性類型笔呀,對(duì)應(yīng)Java的Object幢踏。

當(dāng)我們?cè)贘ava中調(diào)用native_init()方法時(shí),就會(huì)從JNI中尋找Java_com_example_media_MediaRecorder_native_1init函數(shù)许师,如果沒有就會(huì)報(bào)錯(cuò)惑折,如果有就會(huì)為native_initJava_com_example_media_MediaRecorder_native_1init建立關(guān)聯(lián)授账,其實(shí)就是報(bào)錯(cuò)JNI函數(shù)指針。這樣再次調(diào)用的時(shí)候直接使用這個(gè)函數(shù)指針就可以了惨驶。

靜態(tài)注冊(cè)就是根據(jù)方法名白热,將Java方法和JNI函數(shù)建立關(guān)聯(lián),這樣會(huì)有一些缺點(diǎn):

  • JNI層函數(shù)名過長(zhǎng)粗卜。
  • 聲明native方法的類需要用javah生成頭文件屋确。
  • 初次調(diào)用native方法時(shí)需要建立關(guān)聯(lián),影響效率续扔。

動(dòng)態(tài)注冊(cè)

JNI中有一種結(jié)構(gòu)用來記錄Java的native方法和JNI方法的關(guān)聯(lián)關(guān)系攻臀,它就是JNINativeMethod,它在jni.h中被定義:

typedef struct {
    const char* name;//Java方法名字
    const char* signature;//Java方法的簽名
    void*       fnPtr;//JNI中對(duì)應(yīng)方法的指針
} JNINativeMethod;

系統(tǒng)的MediaRecorder采用的是動(dòng)態(tài)注冊(cè)纱昧,下面看一下它的JNI層是怎么做的:
frameworks/base/media/jni/android_media_MediaRecorder.cpp

//JNINativeMethod類型的數(shù)組刨啸,數(shù)組名字為gMethods
static const JNINativeMethod gMethods[] = {
    ......
    {"start",         "()V", (void *)android_media_MediaRecorder_start},
    {"stop",          "()V", (void *)android_media_MediaRecorder_stop},
    {"pause",         "()V", (void *)android_media_MediaRecorder_pause},
    {"resume",        "()V", (void *)android_media_MediaRecorder_resume},
    {"native_reset",  "()V", (void *)android_media_MediaRecorder_native_reset},
    {"release",       "()V", (void *)android_media_MediaRecorder_release},
    {"native_init",   "()V", (void *)android_media_MediaRecorder_native_init},
    ......
};

上面定義了一個(gè)JNINativeMethod類型的數(shù)組,數(shù)組的名字是gMethods识脆,里面存儲(chǔ)的是native方法于JNI層函數(shù)的對(duì)應(yīng)關(guān)系设联。只定義是沒有用的,還需要注冊(cè)它灼捂,注冊(cè)的函數(shù)為:register_android_media_MediaRecorder:

// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaRecorder(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaRecorder", gMethods, NELEM(gMethods));
}

通過該方法的注釋我們知道該方法是在JNI_OnLoad函數(shù)中調(diào)用的离例。這個(gè)函數(shù)會(huì)在System.loadLibrary函數(shù)后調(diào)用,因?yàn)槎嗝襟w框架中很多框架都要進(jìn)行JNINativeMethod類型的數(shù)組注冊(cè)悉稠,因此函數(shù)注冊(cè)被統(tǒng)一定義在android_media_MediaPlayer.cppJNI_OnLoad函數(shù)中宫蛆,該函數(shù)的代碼如下:
frameworks/base/media/jni/android_media_MediaPlayer.cpp

jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);
    ......
    if (register_android_media_MediaPlayer(env) < 0) {
        ALOGE("ERROR: MediaPlayer native registration failed\n");
        goto bail;
    }

    if (register_android_media_MediaRecorder(env) < 0) {
        ALOGE("ERROR: MediaRecorder native registration failed\n");
        goto bail;
    }
    ......
    /* success -- return valid version number */
    result = JNI_VERSION_1_4;

bail:
    return result;
}

register_android_media_MediaRecorder方法中返回了AndroidRuntime::registerNativeMethods函數(shù),該函數(shù)的代碼如下:
frameworks/base/core/jni/AndroidRuntime.cpp

/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

在該方法中又返回了jniRegisterNativeMethods函數(shù)的猛,該函數(shù)被定義在JNI的幫助類JNIHelp.cpp中:
libnativehelper/JNIHelp.cpp

extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    ......
    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
        char* tmp;
        const char* msg;
        if (asprintf(&tmp, "RegisterNatives failed for '%s'; aborting...", className) == -1) {
            // Allocation failed, print default warning.
            msg = "RegisterNatives failed; aborting...";
        } else {
            msg = tmp;
        }
        e->FatalError(msg);
    }

    return 0;
}

從上面代碼可以看出最終調(diào)用的是JNIEnvRegisterNatives函數(shù)來完成JNI注冊(cè)的耀盗。
動(dòng)態(tài)注冊(cè)要比靜態(tài)注冊(cè)復(fù)雜一些。但是它解決來靜態(tài)注冊(cè)的缺點(diǎn)卦尊,可以說一勞永逸袍冷。

動(dòng)態(tài)注冊(cè)是直接存儲(chǔ)Java的native方法與它對(duì)應(yīng)的JNI中的函數(shù)指針。

數(shù)據(jù)類型的轉(zhuǎn)換

Java層的數(shù)據(jù)類型到來JNI層就需要轉(zhuǎn)換為JNI層的數(shù)據(jù)類型猫牡。Java的數(shù)據(jù)類型分為基本數(shù)據(jù)類型引用數(shù)據(jù)類型胡诗,JNI層對(duì)于這兩種數(shù)據(jù)類型也做來區(qū)分,下面就分別來看一下淌友。

基本數(shù)據(jù)類型的轉(zhuǎn)換

Java Native Signature
byte jbyte B
char jchar C
double jdouble D
float jfloat F
int jint I
short jshort S
long jlong J
boolean jboolean Z
void void V

除了最后一個(gè)void煌恢,其他的數(shù)據(jù)類型只需要在前面加上“j”就可以了。Signature表示的是簽名格式震庭。

引用數(shù)據(jù)類型轉(zhuǎn)換

Java Native Signature
Object jobject L + classname + ;
Class jclass Ljava/lang/Class;
String jstring Ljava/lang/String;
Throwable jthrowable Ljava/lang/Throwable;
object[] jobjectArray [L + classname + ;
byte[] jbyteArray [B
char[] jcharArray [C
double[] jdoubleArray [D
float[] jfloatArray [F
int[] jintArray [I
short[] jshortArray [S
long[] jlongArray [J
boolean[] jbooleanArray [Z
繼承關(guān)系

下面以MediaRecorder為例看一下類型的轉(zhuǎn)換:
frameworks/base/media/java/android/media/MediaRecorder.java

private native void _setOutputFile(FileDescriptor fd, long offset, long length)
    throws IllegalStateException, IOException;

_setOutputFile方法對(duì)應(yīng)的JNI層的方法為:
frameworks/base/media/jni/android_media_MediaRecorder.cpp

static void
android_media_MediaRecorder_setOutputFileFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
    ......
}

對(duì)比以上兩個(gè)方法可以看到** FileDescriptor被轉(zhuǎn)換成了 jobject瑰抵, long被轉(zhuǎn)換成了 jlong**。

方法簽名

方法簽名是由簽名格式組成的器联,上面在介紹數(shù)據(jù)類型轉(zhuǎn)換的時(shí)候每種數(shù)據(jù)類型都給出了對(duì)應(yīng)的簽名格式二汛。那么方法簽名有什么用呢婿崭?我們先看一下方法簽名是什么樣子的:
``

static const JNINativeMethod gMethods[] = {
    ......
    {"native_init",  "()V", (void *)android_media_MediaRecorder_native_init},
    {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V",
    ......
};

其中“()V”和"(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V"就是方法簽名。Java中的方法是有重載的肴颊,可以定義同名的方法氓栈,但參數(shù)不同。正因?yàn)槿绱诵鲎牛?strong>JNI中通過方法名是無法找到Java中對(duì)應(yīng)的具體方法的授瘦,JNI為了解決這一問題就將參數(shù)類型和返回值類型組合在一起作為方法簽名。通過方法簽名和方法名就可以找到對(duì)應(yīng)的Java方法竟宋。
JNI方法簽名的格式為:

(參數(shù)1簽名格式參數(shù)2簽名格式...)返回值簽名格式

native_setup函數(shù)為例提完,它在Java中的定義如下:

private native final void native_setup(Object mediarecorder_this,
            String clientName, String opPackageName) throws IllegalStateException;

它在JNI中的方法簽名為:

(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V

native_setup函數(shù)的第一個(gè)參數(shù)的簽名為:"Ljava/lang/Object;",第2和第3個(gè)參數(shù)的簽名為:“Ljava/lang/String;”丘侠,返回值的簽名格式為:“V”徒欣。通過參數(shù)的簽名格式我們可以找到j(luò)ava中對(duì)應(yīng)的參數(shù)類型。

通過Java提供的javap命令可以自動(dòng)生成方法簽名蜗字。

解析JNIEnv

JNIEnvNative層Java環(huán)境的代表打肝,通過JNIEnv *指針就可以在Native層中訪問Java層中的代碼。它只在創(chuàng)建它的線程中有效秽澳,不能跨線程傳遞,因此不同線程的JNIEnv是彼此獨(dú)立的戏羽。
JNIEnv的主要作用:

  • 調(diào)用Java的方法担神。
  • 操作Java中的變量和對(duì)象等。

JNIEnv的定義如下:
libnativehelper/include/nativehelper/jni.h

#if defined(__cplusplus)
//C++中JNIEnv類型
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
//C語言中JNIEnv類型
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

這里使用預(yù)定義宏__cplusplus來區(qū)分C語言和C++兩種代碼始花,如果定義了__cplusplus(編譯的是C++源文件),就使用C++代碼中的定義妄讯,否則就是C語言的定義。JavaVM:它是虛擬機(jī)在JNI層的代表酷宵,在一個(gè)虛擬機(jī)進(jìn)程中只有一個(gè)JavaVM亥贸,因此,該進(jìn)程的所有線程都共享這個(gè)JavaVM浇垦。通過JavaVMAttachCurrentThread函數(shù)可以獲取這個(gè)線程的JNIEnv炕置,這樣就可以在不同的線程中調(diào)用Java方法了。

在使用AttachCurrentThread函數(shù)的線程退出前男韧,務(wù)必要調(diào)用DetachCurrentThread函數(shù)來釋放資源朴摊。
在C++中JNIEnv的類型是_JNIEnv,下面我們看一下它是如何定義的:
libnativehelper/include/nativehelper/jni.h

struct _JNIEnv {
    const struct JNINativeInterface* functions;
    #if defined(__cplusplus)
    ......
     jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }
    ......
    jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    { return functions->GetMethodID(this, clazz, name, sig); }
    ......
    jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    { return functions->GetFieldID(this, clazz, name, sig); }
    ......
}

從上面代碼可以看到_JNIEnv是一個(gè)結(jié)構(gòu)體此虑,其中內(nèi)部又包含了JNINativeInterface甚纲。在_JNIEnv中定義了很多的函數(shù)。這里只貼出了比較常用的3個(gè)函數(shù)朦前,FindClass函數(shù)用來找到Java中指定名稱的類介杆,GetMethodID函數(shù)用來獲取Java中的方法鹃操,GetFieldID函數(shù)用來獲取Java中的成員變量。這3個(gè)函數(shù)都調(diào)用了JNINativeInterface中定義的函數(shù)春哨,因此可以看出荆隘,無論是C語言還是C++,JNIEnv的類型都和JNINativeInterface有關(guān)系悲靴,下面看一下它的定義:
libnativehelper/include/nativehelper/jni.h

struct JNINativeInterface {
    ......
    jclass    (*FindClass)(JNIEnv*, const char*);
    ......
    jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    ......
    jfieldID  (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
    ......
}

JNINativeInterface同樣也是一個(gè)結(jié)構(gòu)體臭胜,在它里面定義了很多和JNIEnv結(jié)構(gòu)體對(duì)應(yīng)的函數(shù)指針。通過這些函數(shù)指針的定義癞尚,就能夠定義到虛擬機(jī)中的JNI函數(shù)表耸三,從而實(shí)現(xiàn)了JNI層在虛擬機(jī)中的函數(shù)調(diào)用,這樣JNI就可以調(diào)用Java層的方法了浇揩。

在C語法中仪壮,JNIEnv是一個(gè)結(jié)構(gòu)體指針:struct JNINativeInterface* JNIEnv

jfieldID和jmethodID

_JNIEnv結(jié)構(gòu)體中定義了很多的函數(shù)胳徽,這些函數(shù)都會(huì)有不同的返回值积锅,如下所示:

struct _JNIEnv {
    const struct JNINativeInterface* functions;
    #if defined(__cplusplus)
    ......
    jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    { return functions->GetMethodID(this, clazz, name, sig); }
    ......
    jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    { return functions->GetFieldID(this, clazz, name, sig); }
    ......
}

這個(gè)兩個(gè)函數(shù)的返回值分別為:jmethodIDjfieldID,分別用來代表Java類中的方法和成員變量养盗。jclass代表Java類缚陷,name:代表方法名或者成員變量的名字,sig:代表這個(gè)方法或者成員變量的簽名往核。接下來我們看一下這兩個(gè)函數(shù)在MediaRecorder框架中的使用:
frameworks/base/media/jni/android_media_MediaRecorder.cpp

static void
android_media_MediaRecorder_native_init(JNIEnv *env)
{
    jclass clazz;
    clazz = env->FindClass("android/media/MediaRecorder");
    if (clazz == NULL) {
        return;
    }
    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    if (fields.context == NULL) {
        return;
    }
    fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");
    if (fields.surface == NULL) {
        return;
    }
    jclass surface = env->FindClass("android/view/Surface");
    if (surface == NULL) {
        return;
    }
    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) {
        return;
    }
}

在上述函數(shù)的開始處箫爷,通過FindClass函數(shù)來找到Java層的MediaRecorder的Class對(duì)象,并賦值給jclass類型的變量clazz聂儒,所以虎锚,clazz就是Java層MediaRecorderJNI層的代表。緊接著找到Java層的MediaRecorder中名字為mNativeContextmSurface的成員變量衩婚,并分別賦值給fieldscontext窜护,surface,最后找到名字為postEventFromNative的靜態(tài)方法非春,并賦值給fieldspost_event柱徙。其中fields的定義如下:
frameworks/base/media/jni/android_media_MediaRecorder.cpp

struct fields_t {
    jfieldID    context;
    jfieldID    surface;
    jmethodID   post_event;
};
static fields_t fields;

android_media_MediaRecorder_native_init函數(shù)中將Java層中的成員變量和方法賦值給了jfieldIDjmethodID保存起來,這樣不用每次調(diào)用的時(shí)候都去查詢奇昙。下面看一下它們是如何使用的:
frameworks/base/media/jni/android_media_MediaRecorder.cpp

void JNIMediaRecorderListener::notify(int msg, int ext1, int ext2)
{
    ALOGV("JNIMediaRecorderListener::notify");
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, NULL);
}

調(diào)用CallStaticVoidMethod函數(shù)時(shí)傳入的參數(shù)就包含了fields.post_event坐搔,該參數(shù)代表的是Java層MediaRecorder的靜態(tài)方法postEventFromNative,下面看一下該方法的實(shí)現(xiàn):
frameworks/base/media/java/android/media/MediaRecorder.java

private static void postEventFromNative(Object mediarecorder_ref,
                                        int what, int arg1, int arg2, Object obj){
    MediaRecorder mr = (MediaRecorder)((WeakReference)mediarecorder_ref).get();
    if (mr == null) {
        return;
    }
    if (mr.mEventHandler != null) {
        Message m = mr.mEventHandler.obtainMessage(what, arg1, arg2, obj);
        mr.mEventHandler.sendMessage(m);
    }
}

在該方法中創(chuàng)建了一個(gè)消息敬矩,然后通過mEventHandler來發(fā)送處理概行,這樣就會(huì)切換到應(yīng)用程序的主線程中。該方法是通過JNIEnvCallStaticVoidMethod函數(shù)來調(diào)用的弧岳,也就是說通過它可以訪問Java層的靜態(tài)方法凳忙,同理业踏,通過CallVoidMethod函數(shù)可以訪問Java層的非靜態(tài)方法。

引用類型

和Java的引用類型一樣涧卵,JNI也有引用類型勤家,它們分別是本地引用(Local References)、全局引用(Global References)和弱全局引用(Weak Global References)柳恐。

本地引用

JNIEnv提供的函數(shù)所返回的引用基本上都是本地引用伐脖,因此本地引用也是JNI中最常見的引用類型。本地引用的特點(diǎn)主要有以下幾點(diǎn):

  • 當(dāng)native函數(shù)返回時(shí)乐设,這個(gè)本地引用就會(huì)被自動(dòng)釋放讼庇。
  • 只在創(chuàng)建它的線程有效,不能跨線程使用近尚。
  • 局部引用是JVM負(fù)責(zé)的引用類型蠕啄,受JVM管理。
    下面通過一個(gè)示例來說明:
    frameworks/base/media/jni/android_media_MediaRecorder.cpp
android_media_MediaRecorder_native_init(JNIEnv *env)
{
    jclass clazz;
    clazz = env->FindClass("android/media/MediaRecorder");
    if (clazz == NULL) {
        return;
    }
    ......
}

FindClass函數(shù)返回的clazz就是本地引用戈锻,它會(huì)在android_media_MediaRecorder_native_init函數(shù)調(diào)用返回后自動(dòng)釋放歼跟,我們也可以調(diào)用JNIEnvDeleteLocalRef函數(shù)來手動(dòng)刪除本地引用,該函數(shù)的應(yīng)用場(chǎng)景主要是在native函數(shù)返回之前占用了大量?jī)?nèi)存格遭,需要手動(dòng)刪除本地引用哈街。

全局引用

全局引用和本地引用幾乎是相反的,它主要有以下幾個(gè)特點(diǎn):

  • 在native函數(shù)返回時(shí)不會(huì)被自動(dòng)釋放拒迅,因此全局引用需要手動(dòng)進(jìn)行釋放骚秦,并且不會(huì)被GC回收。
  • 全局引用是可以跨線程使用的坪它。
  • 全局引用不受JVM管理骤竹。
    JNIEnvNewGlobalRef函數(shù)用來創(chuàng)建全局引用帝牡,調(diào)用DeleteLocalRef函數(shù)來釋放全局引用往毡。
    下面通過一個(gè)示例來看一下全局引用的使用:
    ``
JNIMediaRecorderListener::JNIMediaRecorderListener(JNIEnv* env, jobject thiz, jobject weak_thiz)
{
    jclass clazz = env->GetObjectClass(thiz);
    if (clazz == NULL) {
        ALOGE("Can't find android/media/MediaRecorder");
        jniThrowException(env, "java/lang/Exception", NULL);
        return;
    }
    mClass = (jclass)env->NewGlobalRef(clazz);
    mObject  = env->NewGlobalRef(weak_thiz);
}

clazz是本地引用,在下面通過NewGlobalRef函數(shù)將它變成了全局引用mClass靶溜,該全局引用是在JNIMediaRecorderListener析構(gòu)函數(shù)中釋放开瞭,這里就不貼出源碼了。

弱全局引用

弱全局引用是一種特殊的全局引用罩息,它和全局引用的特點(diǎn)相似嗤详,不同的是弱全局引用是可以被GC回收的,被回收后會(huì)指向NULL瓷炮。通過JNINewWeakGlobalRef函數(shù)來創(chuàng)建弱全局引用葱色,調(diào)用DeleteWeakGlobalRef函數(shù)來釋放弱全局引用,由于它可能被GC回收娘香,因此在使用之前要先判斷它是否被回收了苍狰,通過IsSameObject函數(shù)來判斷办龄。

Kotlin實(shí)戰(zhàn)

Flutter實(shí)戰(zhàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市淋昭,隨后出現(xiàn)的幾起案子俐填,更是在濱河造成了極大的恐慌,老刑警劉巖翔忽,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件英融,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡歇式,警方通過查閱死者的電腦和手機(jī)召调,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來带膀,“玉大人鹃共,你說我怎么就攤上這事〔蜚荆” “怎么了额获?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)恭应。 經(jīng)常有香客問我抄邀,道長(zhǎng),這世上最難降的妖魔是什么昼榛? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任境肾,我火速辦了婚禮,結(jié)果婚禮上胆屿,老公的妹妹穿的比我還像新娘奥喻。我一直安慰自己,他們只是感情好非迹,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布环鲤。 她就那樣靜靜地躺著,像睡著了一般憎兽。 火紅的嫁衣襯著肌膚如雪冷离。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天纯命,我揣著相機(jī)與錄音西剥,去河邊找鬼。 笑死亿汞,一個(gè)胖子當(dāng)著我的面吹牛瞭空,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼咆畏,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼图甜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鳖眼,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤黑毅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后钦讳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矿瘦,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年愿卒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缚去。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡琼开,死狀恐怖易结,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柜候,我是刑警寧澤搞动,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站渣刷,受9級(jí)特大地震影響鹦肿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辅柴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一箩溃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碌嘀,春花似錦涣旨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至魁瞪,卻和暖如春穆律,著一層夾襖步出監(jiān)牢的瞬間惠呼,已是汗流浹背导俘。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留剔蹋,地道東北人旅薄。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親少梁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子洛口,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360