目錄
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
MediaRecorder的JNI層是由android_media_MediaRecorder.cpp
實(shí)現(xiàn)套么,native方法:native_init
和start
的代碼實(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_init
和Java_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.cpp
的JNI_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)用的是JNIEnv的RegisterNatives
函數(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 |
下面以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
JNIEnv是Native層中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浇垦。通過JavaVM的AttachCurrentThread
函數(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ù)的返回值分別為:jmethodID
和jfieldID
,分別用來代表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層MediaRecorder在JNI層的代表。緊接著找到Java層的MediaRecorder中名字為mNativeContext
和mSurface
的成員變量衩婚,并分別賦值給fields
的context
窜护,surface
,最后找到名字為postEventFromNative
的靜態(tài)方法非春,并賦值給fields
的post_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層中的成員變量和方法賦值給了jfieldID
和jmethodID
保存起來,這樣不用每次調(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)用程序的主線程中。該方法是通過JNIEnv的CallStaticVoidMethod
函數(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)用JNIEnv的DeleteLocalRef
函數(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管理骤竹。
JNIEnv的NewGlobalRef
函數(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
瓷炮。通過JNI的NewWeakGlobalRef
函數(shù)來創(chuàng)建弱全局引用葱色,調(diào)用DeleteWeakGlobalRef
函數(shù)來釋放弱全局引用,由于它可能被GC回收娘香,因此在使用之前要先判斷它是否被回收了苍狰,通過IsSameObject
函數(shù)來判斷办龄。