在上一篇文章中對JNI簡單介紹了,在這篇文章中將對JNI原理進行介紹。本篇文章將以JNI執行環境、JNI數據類型、JNI註冊方式、JNI引用、JNI變量共享以及JNI調用方式來介紹JNI原理。
一、執行環境(Runtime)
- JavaVM
jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*);
#if defined(__cplusplus)
typedef _JavaVM JavaVM; //C++的JavaVM定義
#else
typedef const struct JNIInvokeInterface* JavaVM; //C的JavaVM定義
#endif
/*
* JNI invocation interface.
*/
struct JNIInvokeInterface {
void* reserved0;//保留
void* reserved1;//保留
void* reserved2;//保留
jint (*DestroyJavaVM)(JavaVM*); // 銷燬Java虛擬機並回收資源
jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);//鏈接到當前Java線程
jint (*DetachCurrentThread)(JavaVM*); //從當前Java線程中分離
jint (*GetEnv)(JavaVM*, void**, jint);// 獲得當前線程的Java運行環境
jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*); // 將當前線程作爲守護線程
};
/*
* C++ version.
*/
struct _JavaVM {
const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
jint DestroyJavaVM()
{ return functions->DestroyJavaVM(this); }
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{ return functions->DetachCurrentThread(this); }
jint GetEnv(void** env, jint version)
{ return functions->GetEnv(this, env, version); }
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
- JNIEnv
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;//C++定義
#else
typedef const struct JNINativeInterface* JNIEnv;//C定義
#endif
/*
* Table of interface function pointers.
*/
//C JNIEnv定義
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;
jclass (*FindClass)(JNIEnv*, const char*);
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);
jfieldID (*GetStaticFieldID)(JNIEnv*, jclass, const char*,const char*);
.......
jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*, jint); //註冊本地方法
jint (*UnregisterNatives)(JNIEnv*, jclass); //反註冊本地方法
jint (*GetJavaVM)(JNIEnv*, JavaVM**); //獲取對應的JavaVM對象
......
}
/*
* C++ object wrapper.
*
* This is usually overlaid on a C struct whose first element is a
* JNINativeInterface*. We rely somewhat on compiler behavior.
*/
//C++的JNIEnv定義
struct _JNIEnv {
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
.......
}
二、JNI數據類型
2.1 JNI基本類型
Java類型
|
Native類型 |
JNI類型
|
描述
|
boolean |
unsigned char
|
jboolean
|
無符號8比特
|
byte |
signed char
|
jbyte
|
有符號8比特 |
char |
unsigned short
|
jchar
|
無符號16比特
|
short
|
short
|
jshort
|
有符號16比特
|
int
|
int
|
jint
|
有符號32比特
|
long
|
long long
|
jlong
|
有符號64比特
|
float
|
float
|
jfloat
|
32比特
|
double
|
double
|
jdouble
|
64比特
|
void |
void
|
void
|
N/A
|
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
typedef jint jsize;
需要注意的是jchar代表的是Java的char類型,對應於C/C++中的卻是unsigned short類型,因爲Java中的char類型是兩個字節,jchar相當於C/C++的寬字符。如果需要在本地方法中定義一個jchar類型的數據,規範的寫法應該是jchar = L'C'。
2.2 JNI引用類型
#ifdef __cplusplus
/*
* Reference types, in C++
*/
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};
//在C++中定義的引用類型
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;
#else /* not __cplusplus */
//在C語言中定義的JNI引用類型
/*
* Reference types, in C.
*/
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;
#endif /* not __cplusplus */
2.3 JNI類型簽名
Java類型 |
JNI類型簽名
|
boolean
|
Z
|
btye
|
B
|
char
|
C
|
short
|
S
|
int
|
I
|
long
|
J |
float
|
F
|
double
|
D
|
Class類 |
L
|
void |
V
|
數組[]
|
[ |
boolean[] | [Z |
byte[] | [B |
char[]
|
[C |
short[]
|
[S
|
int[]
|
[I |
long[] |
[J
|
float[] |
[F
|
double[] |
[D
|
基本類型
- 在方法簽名中沒有體現方法名;
- 括號內表示參數列表,參數列表緊密相挨,中間沒有逗號和空格;
- 返回值出現在括號後面;
- 如果函數沒有返回值,也要加上V類型。
typedef struct {
const char* name;//方法名字
const char* signature;//方法簽名
void* fnPtr;//本地方法名字
} JNINativeMethod;
三、JNI方法註冊方式
3.1系統啓動時註冊
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
writepid /dev/cpuset/foreground/tasks
int main(int argc, char* const argv[])
{
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));//創建AppRuntime
.........
bool zygote = false;
bool startSystemServer = false;
...........
++i;
while (i < argc) {
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
zygote = true;//啓動Zygote進程
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;//啓動SystemServer進程
}
........
}
Vector<String8> args;
if (!className.isEmpty()) {
.........
} else {
if (startSystemServer) {
args.add(String8("start-system-server"));//添加啓動SystemServer的參數
}
............
}
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);//啓動Zygote進程
}
............
}
class AppRuntime : public AndroidRuntime
{
public:
AppRuntime(char* argBlockStart, const size_t argBlockLength)
: AndroidRuntime(argBlockStart, argBlockLength)
, mClass(NULL)
{ }
}
而AndroidRuntime類定義在AndroidRuntime.cpp文件中,代碼路徑在frameworks/base/core/jni/AndroidRuntime.cpp路徑下。
AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) :
mExitWithoutCleanup(false),
mArgBlockStart(argBlockStart),
mArgBlockLength(argBlockLength)
{
SkGraphics::Init();
mOptions.setCapacity(20);
assert(gCurRuntime == NULL); // one per process
gCurRuntime = this; //將Runtime保存到一個全局變量中
}
在創建AppRuntime對象的時候,也會調用其父類AndroidRuntime的構造函數。在AndroidRuntime構造函數中會將this保存到一個全局靜態變量變量gCurRuntime中,可以通過getRuntime()函數來獲取該靜態對象。
static AndroidRuntime* gCurRuntime = NULL;
AndroidRuntime* AndroidRuntime::getRuntime()
{
return gCurRuntime;
}
/*
* Start the Android runtime. This involves starting the virtual machine
* and calling the "static void main(String[] args)" method in the class
* named by "className".
*
* Passes the main function two arguments, the class name and the specified
* options string.
*/
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
.......
static const String8 startSystemServer("start-system-server");
........
// 1.啓動虛擬機VM
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
if (startVm(&mJavaVM, &env, zygote) != 0) {
return;
}
onVmCreated(env);
//2.註冊JNI函數
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
//準備啓動Zygote進程的參數
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
stringClass = env->FindClass("java/lang/String");
assert(stringClass != NULL);
strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
assert(strArray != NULL);
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
for (size_t i = 0; i < options.size(); ++i) {
jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
assert(optionsStr != NULL);
env->SetObjectArrayElement(strArray, i + 1, optionsStr);
}
// 3.啓動Zygote進程,調用ZygoteInit.main()方法。
/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
char* slashClassName = toSlashClassName(className);
//通過JNI調用java方法,具體是通過找到ZygoteInit類,以及ZygoteInit類的靜態方法main,
//最後通過CallStaticVoidMethod調用ZygoteInit類的main方法。
// "com.android.internal.os.ZygoteInit"是通過app_main中main方法傳遞過來的參數。
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);//通過JNI調用ZygoteInit的main方法
}
}
free(slashClassName);
// 虛擬機退出了纔會執行到這裏
ALOGD("Shutting down VM\n");
if (mJavaVM->DetachCurrentThread() != JNI_OK)
ALOGW("Warning: unable to detach main thread\n");
if (mJavaVM->DestroyJavaVM() != 0)
ALOGW("Warning: VM did not shut down cleanly\n");
}
/*
* Start the Dalvik Virtual Machine.
*
* Various arguments, most determined by system properties, are passed in.
* The "mOptions" vector is updated.
*
* Returns 0 on success.
*/
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
{
JavaVMInitArgs initArgs;
//準備創建JVM的參數
.........
// 初始化VM,
/*
* Initialize the VM.
*
* The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
* If this call succeeds, the VM is ready, and we can start issuing
* JNI calls.
*/
if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
ALOGE("JNI_CreateJavaVM failed\n");
return -1;
}
return 0;
}
/*
* Register android native functions with the VM.
*/
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
ATRACE_NAME("RegisterAndroidNatives");
// 設置創建線程的方法爲javaCreateThreadEtc,該方法定義在AndroidRuntime.h中
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
......
env->PushLocalFrame(200);
//註冊gRegJNI數組中的函數
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
env->PopLocalFrame(NULL);
return 0;
}
可以看到startReg函數最後調用的是register_jni_procs函數將gRegJNI數組中的函數註冊到VM中。gRegJNI是一個包含JNI函數的數組,定義如下:/*
* Register android native functions with the VM.
*/
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
ATRACE_NAME("RegisterAndroidNatives");
// 設置創建線程的方法爲javaCreateThreadEtc,該方法定義在AndroidRuntime.h中
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
......
env->PushLocalFrame(200);
//註冊gRegJNI數組中的函數
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
env->PopLocalFrame(NULL);
return 0;
}
可以看到startReg函數最後調用的是register_jni_procs函數將gRegJNI數組中的函數註冊到VM中。gRegJNI是一個包含JNI函數的數組,定義如下:
//一個包含JNI函數的數組
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_com_android_internal_os_ZygoteInit),
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
REG_JNI(register_android_util_Log),
REG_JNI(register_android_util_MemoryIntArray),
....
}
#define REG_JNI(name) { name }
struct RegJNIRec {
int (*mProc)(JNIEnv*);
};
//循環調用gRegJNI數組中的JNI函數,每一個方法都對應於一個類的jni映射。
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
for (size_t i = 0; i < count; i++) {
if (array[i].mProc(env) < 0) {
return -1;
}
}
return 0;
}
int register_com_android_internal_os_RuntimeInit(JNIEnv* env)
{
const JNINativeMethod methods[] = {
{ "nativeFinishInit", "()V",
(void*) com_android_internal_os_RuntimeInit_nativeFinishInit },
{ "nativeSetExitWithoutCleanup", "(Z)V",
(void*) com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup },
};
return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit",
methods, NELEM(methods));
}
jniRegisterNativeMethods方法的作用是將本地方法註冊到JNI中,JNINativeMethod是定義本地方法的一個數據結構在jni.h文件中定義。
typedef struct {
const char* name;//Java方法名字
const char* signature;//方法簽名
void* fnPtr;//Java方法對應的本地函數指針
} JNINativeMethod;
至此,介紹完了JNI方法在系統啓動過程中的註冊流程。其流程如下圖所示:3.2 通過loadLibrary方法註冊
/*
* 加載libname指定的本地庫
*/
@CallerSensitive
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
在 java/lang/Runtime.java類中 /*
* 通過給定的ClassLoader搜索和加載指定的共享庫文件
*/
void loadLibrary(String libraryName, ClassLoader loader) {
//loader不爲空,進入該分支處理
if (loader != null) {
//查找庫所在路徑
String filename = loader.findLibrary(libraryName);
if (filename == null) {
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}
//加載庫文件
String error = doLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
//loader爲空,則進入該分支處理
// 返回平臺相關庫文件名字,在Android中,如果共享庫爲MyLibrary,則返回的共享庫名字爲“libMyLibrary.so”。
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
//在/system/lib/和/vendor/lib/下查找指定的filename文件
for (String directory : mLibPaths) {
String candidate = directory + filename;
candidates.add(candidate);
if (IoUtils.canOpenReadOnly(candidate)) {
// 找了對應的庫文件,加載庫文件
String error = doLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);//沒有找到滿足條件的,報錯
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
// mLibPaths是保存庫文件路徑,用來查找native庫文件
private final String[] mLibPaths = initLibPaths();
/*
*搜索JNI庫文件的路徑,通過"java.library.path"讀取出來的屬性值爲/vendor/lib:/system/lib/。
*其中/system/lib/路徑存放的是系統應用使用的so庫文件,/vendor/lib/路徑存放的是第三方應用的so庫文件。*/
private static String[] initLibPaths() {
String javaLibraryPath = System.getProperty("java.library.path");
if (javaLibraryPath == null) {
return EmptyArray.STRING;
}
String[] paths = javaLibraryPath.split(":");
// Add a '/' to the end of each directory so we don't have to do it every time.
for (int i = 0; i < paths.length; ++i) {
if (!paths[i].endsWith("/")) {
paths[i] += "/";
}
}
return paths;
}
private String doLoad(String name, ClassLoader loader) {
String ldLibraryPath = null;
if (loader != null && loader instanceof BaseDexClassLoader) {
ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
}
synchronized (this) {
//最後調用nativeLoad方法加載庫文件
return nativeLoad(name, loader, ldLibraryPath);
}
}
- 調用dlopen()函數,打開一個so文件並創建一個handle;
- 調用dlsym()函數,查看相應so文件的JNI_OnLoad()函數指針,並執行相應的函數。
總之,System.loadLibrary()的作用就是調用相應庫中的JNI_OnLoad()方法。接下來看JNI_OnLoad函數。
3.2.1JNI_OnLoad方法
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
- 通過JavaVM獲取JNIEnv,即通過getEnv函數,獲取JNIEnv,JNIEnv代表Java線程執行環境;
- 通過RegisterNative函數註冊本地方法;
- 返回JNI版本號;
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;
// 1.通過JavaVM獲取JNIEnv,指定JNI的版本爲1.4。
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL);
...
// 2.註冊JNI方法
if (register_android_media_MediaPlayer(env) < 0) {
ALOGE("ERROR: MediaPlayer native registration failed\n");
goto bail;
}
...
/* success -- return valid version number */
// 3.返回JNI版本號
result = JNI_VERSION_1_4;
bail:
return result;
}
// 調用registerNativeMethods方法註冊本地方法
static int register_android_media_MediaPlayer(JNIEnv *env)
{
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
static const JNINativeMethod gMethods[] = {
...
{"_prepare", "()V", (void *)android_media_MediaPlayer_prepare},
{"_start", "()V", (void *)android_media_MediaPlayer_start},
{"_stop", "()V", (void *)android_media_MediaPlayer_stop},
{"seekTo", "(I)V", (void *)android_media_MediaPlayer_seekTo},
{"_pause", "()V", (void *)android_media_MediaPlayer_pause},
{"isPlaying", "()Z", (void *)android_media_MediaPlayer_isPlaying},
{"getCurrentPosition", "()I", (void *)android_media_MediaPlayer_getCurrentPosition},
{"getDuration", "()I", (void *)android_media_MediaPlayer_getDuration},
{"_release", "()V", (void *)android_media_MediaPlayer_release},
{"_reset", "()V", (void *)android_media_MediaPlayer_reset},
{"setParameter", "(ILandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_setParameter},
{"native_setup", "(Ljava/lang/Object;)V", (void *)android_media_MediaPlayer_native_setup},
....
};
在framework/core/jni/AndroidRuntime.cpp文件中實現了registerNativeMethods方法,並最終調用jniRegisterNativeMethods方法。/*
* Register native methods using JNI.
*/
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
scoped_local_ref<jclass> c(env, findClass(env, className));
...
// 調用RegisterNatives()方法完成註冊
if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
...
}
return 0;
}
RegisterNatives方法在jni.h文件中定義。struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
...
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods){
return functions->RegisterNatives(this, clazz, methods, nMethods);
}
....
}
functions是一個JNINativeInterface的指針,也將調用RegisterNatives()方法,再往下面就是虛擬機的內部實現了,在此不再詳述。struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;
....
jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,jint);
....
}
當不需要這些映射關係時,或者需要更新映射關係時,則需要調用UnregisterNatives函數,來刪除這些映射關係。jint (*UnregisterNatives)(JNIEnv*, jclass);
至此,介紹了通過System.loadLibrary()方法註冊JNI函數的過程。整體的流程如下圖所示:3.3如何查找native方法
四、JNI引用
|
局部引用
|
全局引用
|
作用範圍 |
本地方法內
|
全局範圍 |
回收時機
|
本地方法返回時,自動回收
|
顯式通知GC回收
|
適用範圍
|
只在當前線程中有效 |
可以跨越多個線程
|
創建引用函數
|
jobject (*NewLocalRef)(JNIEnv*, jobject)
|
jobject (*NewGlobalRef)(JNIEnv*, jobject)
|
銷燬引用函數 |
void (*DeleteLocalRef)(JNIEnv*, jobject) |
void (*DeleteGlobalRef)(JNIEnv*, jobject)
|
jobject (*NewLocalRef)(JNIEnv*, jobject);
void (*DeleteLocalRef)(JNIEnv*, jobject);
jobject (*NewGlobalRef)(JNIEnv*, jobject);
void (*DeleteGlobalRef)(JNIEnv*, jobject);
jweak (*NewWeakGlobalRef)(JNIEnv*, jobject);
void (*DeleteWeakGlobalRef)(JNIEnv*, jweak);
4.1局部引用
jobject (*NewLocalRef)(JNIEnv*, jobject);
void (*DeleteGlobalRef)(JNIEnv*, jobject);
jstring Jstring2CStr(JNIEnv* env, jstring jstr)
{
....
jclass clsstring = (*env)->FindClass(env,"java/lang/String");//局部變量
jstring native_desc = env->NewStringUTF(" I am Native");//局部變量
...
}
- 在本地方法中引用了一個很大的Java對象,在使用完該Java對象後,還需要繼續執行一些耗時操作,如果不主動釋放該局部引用對象的話,則需要等到本地方法執行完成才能釋放該局部引用對象。對於內存比較緊張的情況,可能會由於局部引用對象沒有及時回收而導致內存不足的問題。
- 在本地方法中創建了大量的局部對象,可能會導致JNI局部引用表溢出,此時需要手動釋放這些不再使用的局部引用對象。例如,在本地代碼中創建一個很大的對象數組。
- 不返回的本地函數。例如:在一個本地函數中循環處理消息,如果不釋放循環中使用的局部引用,則會無限地累積,進而導致內存泄漏。此時在循環體中需要手動釋放局部引用。
4.2全局引用
當一個本地方法需要被多個線程調用,並且希望本地方法中的引用在多個線程間可以共享使用時,那麼可以使用全局引用。全局引用可以跨越多個線程,全局引用需要手動釋放,顯式通知垃圾回收器GC回收它,否則會一直存在。JNIMediaPlayerListener::JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz)
{ ....
jclass clazz = env->GetObjectClass(thiz);//局部引用
....
mClass = (jclass)env->NewGlobalRef(clazz);//創建全局引用並指向局部引用
...
mObject = env->NewGlobalRef(weak_thiz);//創建全局引用
}
JNIMediaPlayerListener::~JNIMediaPlayerListener()
{
// remove global references
JNIEnv *env = AndroidRuntime::getJNIEnv();
env->DeleteGlobalRef(mObject);//移除全局引用
env->DeleteGlobalRef(mClass);//移除全局引用
}
當本地方法不再需要使用全局引用時,應該通過DeleteGlobalRef方法來釋放全局引用。否則的話,JVM不會回收被全局引用的對象。4.3 JNI變量共享
static void android_media_MediaPlayer_native_init(JNIEnv *env,jobject obj)
{ ....
jclass clazz;
clazz = env->FindClass("android/media/MediaPlayer");//獲取MediaPlayer類
jfieldID name = env->GetFieldID(clazz,"name","Ljava/lang/String;");//獲取MediaPlayer類的域name的ID
jstring newName = env->NewStringUTF("media player");//創建一個新的字符串
env->setObjectField(obj,name,newName);//將MediaPlayer類中的name屬性更新
....
}
static void android_media_MediaPlayer_start(JNIEnv *env, jobject obj)
{
....
jclass clazz;
clazz = env->FindClass("android/media/MediaPlayer");//獲取MediaPlayer類
jfieldID name = env->GetFieldID(clazz,"name","Ljava/lang/String;");//獲取MediaPlayer類的域name的ID
jstring mediaName = (jstring)env->getObjectField(obj,name);//讀取在android_media_MediaPlayer_native_init方法中更新的name對象值
...
}
五、調用方式
5.1Java訪問本地方法
- 在java代碼中,聲明native方法;
- 編譯生成本地代碼庫.so文件;
- 在java代碼中,加載本地代碼庫.so文件;
- 在java代碼中,調用本地native方法;
public class JavaToNative{
static{
System.loadLibrary(“java_to_native”); // 通過System.loadLibrary()來加載本地代碼庫
}
private static native String hello(); // 聲明native方法
public static void main(String args[]){
System.out.println(JavaToNative.hello()); // 調用native方法
}
}
5.2本地方法訪問Java成員
- 獲取Java運行環境中的類對象class;
- 獲取Java類或Java對象的屬性ID和方法ID;
- 通過屬性ID和方法ID訪問Java類或Java對象的屬性和方法;
jclass FindClass(const char* name);
name爲類的全名,包含了類的完整路徑,即包名+類名,以"/"代替"."分割符。例如:jclass newStr = env->FindClass("java/lang/String");
還有一種方法是通過Object對象來獲取其對應的類class對象,所調用的方法爲 GetObjectClass,定義如下: jclass GetObjectClass(jobject obj);
obj代表Java傳遞過來的對象,通過該方法可以獲取該對象的類類型,其功能如同Object.getClass()方法。struct _jfieldID; /* opaque structure */
typedef struct _jfieldID* jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID* jmethodID; /* method IDs */
在jni.h文件中,定義了獲取屬性ID和方法ID的方法://獲取Java對象的屬性ID
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig);
//獲取Java類的靜態屬性ID
jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
//獲取Java對象的方法ID
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
//獲取Java類的靜態方法ID
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig);
jclass clazz = env->FindClass("android/media/MediaPlayer");//獲取MediaPlayer類class
jfieldID name = env->GetFieldID(clazz,"name","Ljava/lang/String;");//獲取MediaPlayer對象的屬性name的ID
jmethodID getHost = env->GetMethodID(clazz, "getHost", "(V)Ljava/lang/String;");//獲取MediaPlayer對象方法getHost()的ID
jfieldID port = env->GetStaticFieldID(clazz,"port","I");//獲取MediaPlayer類的靜態屬性port的ID
jmethodID getPort = env->GetStaticMethodID(clazz,"getPort","(V)I");//獲取MediaPlayer類的靜態方法getPort()的ID
- 操作Java屬性
jobject GetObjectField(jobject obj, jfieldID fieldID);
jboolean GetBooleanField(jobject obj, jfieldID fieldID);
jint GetIntField(jobject obj, jfieldID fieldID);
...
void SetObjectField(jobject obj, jfieldID fieldID, jobject value);
void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value);
void SetIntField(jobject obj, jfieldID fieldID, jint value);
...
jobject GetStaticObjectField(jclass clazz, jfieldID fieldID);
jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID);
jint GetStaticIntField(jclass clazz, jfieldID fieldID);
...
void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value);
void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value);
void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value);
....
static void android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
...
jclass clazz = env->GetObjectClass(thiz);//獲取MediaPlayer類class
//獲取和設置Java對象的屬性
jfieldID name = env->GetFieldID(clazz,"name","Ljava/lang/String;");//獲取MediaPlayer對象的屬性name的ID
jstring newStr = env->NewStringUTF("local media player");
env->SetObjectField(thiz,name,newStr);//設置MediaPlayer對象的屬性name的值
//獲取和設置類的靜態屬性
jfieldID port = env->GetStaticFieldID(clazz,"port","I");//獲取MediaPlayer類的靜態屬性port的ID
jint portNum = env->GetStaticIntField(claszz,port);//獲取MediaPlayer類靜態屬性port的值
env->SetStaticIntField(clazz,port,portNum + 100);//設置MediaPlayer類靜態屬性port的值
...
}
- 操作Java方法
void CallVoidMethod(jobject obj, jmethodID methodID, ...);
void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args);
void CallVoidMethodA(jobject obj, jmethodID methodID, jvalue* args);
void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...);
void CallStaticVoidMethodV(jclass clazz, jmethodID methodID, va_list args);
void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, jvalue* args);
static void android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
...
jclass clazz = env->FindClass("android/media/MediaPlayer");//獲取MediaPlayer類class
jmethodID getHost = env->GetMethodID(clazz, "getHost", "(V)Ljava/lang/String;");//獲取MediaPlayer對象方法getHost()的ID
jmethodID getPort = env->GetStaticMethodID(clazz,"getPort","(V)I");//獲取MediaPlayer類的靜態方法getPort()的ID
env->CallObjectMethod(this,getHost);//調用MediaPlayer的成員方法getHost()
env->CallStaticIntMethod(clazz,getPort);//調用MediaPlayer類的靜態方法getPort()
...
}
- 在本地代碼中創建Java對象
jobject NewObject(jclass clazz, jmethodID methodID, ...);
jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args);
jobject NewObjectA(jclass clazz, jmethodID methodID, jvalue* args);
jclass clazz = env->FindClass("android/media/MediaPlayer");//獲取MediaPlayer類class
jmethodID ctor = env->GetMethodID(clazz,"<init>","(V)V");//獲取默認構造方法ID
jobject mediaPlayer = env->NewObject(clazz,ctor);//調用構造函數創建MediaPlayer對象
- 在本地代碼中創建String對象
jstring NewString(const jchar* unicodeChars, jsize len);
jstring NewStringUTF(const char* bytes);
const char* GetStringUTFChars(jstring string, jboolean* isCopy);
const jchar* GetStringChars(jstring string, jboolean* isCopy);
typedef unsigned short jchar; /* unsigned 16 bits */
void ReleaseStringChars(jstring string, const jchar* chars);
void ReleaseStringUTFChars(jstring string, const char* utf);
const char *tmp = env->GetStringUTFChars(path, NULL);
env->ReleaseStringUTFChars(path, tmp);
- 在本地代碼中處理Java數組
- 通過GetFieldID方法獲取Java數組變量的ID;
- 通過GetObjectField方法獲取Java數組變量的值,保存到jobject中。
- 強制將jobject對象轉換爲j<類型>Array類型;
- 將j<類型>Array類型轉換爲C/C++中的數組。
jsize GetArrayLength(jarray array);//獲取數組的長度
jobjectArray NewObjectArray(jsize length, jclass elementClass,jobject initialElement);//創建Object對象數組
jobject GetObjectArrayElement(jobjectArray array, jsize index);//相當於array[index]
void SetObjectArrayElement(jobjectArray array, jsize index, jobject value);//相當於arry[index] = value
jcharArray NewCharArray(jsize length);
jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy);
void ReleaseCharArrayElements(jcharArray array, jchar* elems,jint mode);
static void android_media_MediaPlayer_release(JNIEnv *env, jobject thiz)
{
jclass clazz = env->FindClass("android/media/MediaPlayer");//獲取MediaPlayer類class
jfieldID arrayID = env->GetFieldID(clazz,"arrays", "[I");
jintArray array = (jintArray)(env->GetObjectField(thiz, arrayID));
jint* int_array = env->GetIntArrayElements(arr, NULL);
jsize len = env->GetArrayLength(array);
env->ReleaseIntArrayElements(array, int_array, JNI_ABORT);
}