1. Jni层简介
1.1. Jni层的作用
Java层的函数可以调用Native层函数
Native层函数可以调用Java层函数
1.2. 实例分析
以MediaScanner
为例实例拆解JNI
层基础用法
1.2.1. 库名相关
JNI层对应的是libmedia_jni.so, media_jni对应Jni库的名字,Android
平台基本采用lib
模块名_jni
.so的名字命名。
1 2 3 4 5 6
| static { System.loadLibrary("media_jni"); native_init(); }
private static native final void native_init()
|
要使用Jni层函数,必须先加载Jni库文件,只要调用Jni函数前加载即可。其次声明该Jni层函数。
加载一般放在 static
静态块里,这样类一经创建,就加载对应的Jni库。
调用System.loadLibrary
就可以,系统会根据平台扩展成对应的库文件名。
1.2.2. Jni层文件名一般与Java层文件是对应的
1 2
| ../java/android/media/MediaSanner.java ../jni/android_media_MediaSanner.cpp
|
1.2.3. 注册Jni函数
如何能从库文件中找到对应的Jni层函数?
注册——将Java层的Native声明的函数与Jni层的实现函数关联起来
1.2.3.1. 静态方法
1 2
| MediaSanner.java-->MediaSanner .class Javah .class android_media_MediaSanner.h
|
保存Jni层函数的函数指针
,并和Java
层native
函数建立关联,再调用native
声明的函数时,直接调用相应的函数指针。
1.2.3.2. 动态注册
结构体保存关联信息
1 2 3 4 5 6 7 8
| Typedef struct {
Const char* name;
Const char* signature;
Void * fnPtr; }JniNativeMethod;
|
如MediaScanner层的Jni函数保存在gMethods
数组中,成员和Java层由native声明的函数一一对应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| static JNINativeMethod gMethods[] = { { "processDirectory", "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V", (void *)android_media_MediaScanner_processDirectory }, { "processFile", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", (void *)android_media_MediaScanner_processFile }, { "setLocale", "(Ljava/lang/String;)V", (void *)android_media_MediaScanner_setLocale }, { "extractAlbumArt", "(Ljava/io/FileDescriptor;)[B", (void *)android_media_MediaScanner_extractAlbumArt }, { "native_init", "()V", (void *)android_media_MediaScanner_native_init }, { "native_setup", "()V", (void *)android_media_MediaScanner_native_setup }, { "native_finalize", "()V", (void *)android_media_MediaScanner_native_finalize }, }
|
注册gMethods
数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| int register_android_media_MediaScanner(JNIEnv *env) { return AndroidRuntime::registerNativeMethods(env, kClassMediaScanner, gMethods, NELEM(gMethods)); } 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); ALOGV("Registering %s's %d native methods...", className, numMethods); scoped_local_ref<jclass> c(env, findClass(env, className)); if (c.get() == NULL) { char* msg; asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className); e->FatalError(msg); } if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { char* msg; asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className); e->FatalError(msg); } return 0; }
|
当Java层通过System.LoadLibrary
加载Jni库文件后,需要动态注册Jni函数,库文件中有一个JNI_OnLoad()
函数,加载时会执行该函数。如果需要动态注册,必须实现该函数。
android/media/MediaPlayer.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| jint JNI_OnLoad(JavaVM* vm, void* ) { 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_ImageReader(env) < 0) { ALOGE("ERROR: ImageReader native registration failed"); goto bail; }
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; }
if (register_android_media_MediaScanner(env) < 0) { ALOGE("ERROR: MediaScanner native registration failed\n"); goto bail; } ....... if (register_android_media_MediaHTTPConnection(env) < 0) { ALOGE("ERROR: MediaHTTPConnection native registration failed"); goto bail; }
result = JNI_VERSION_1_4;
bail: return result; }
|
1.2.3.3. 数据类型转换
Java数据分为基本数据类型和引用数据类型
1.2.3.3.1. 基本数据类型转换

注意字长
1.2.3.3.2. 引用数据类型转换

除了Java中基本数据类型的数组
、class
、String
外,其他全用jobject
表示,如Java中MediaScannerClient
类就用jobject
表示,这就牵扯到如何用jobject操作类中的成员变量和成员函数的问题。
1.2.4. JNIEnv
JNIEnv代表一个线程相关的代表Jni环境的结构体,JNIEnv
的结构如下:

JNIEnv提供了一些系统函数,通过系统函数可以:
每个Java进程只有一个JavaVM,进程中的线程共享该JavaVM
1
| jint JNI_OnLoad(JavaVM* vm, void* )
|
JavaVM和JNIEnv有如下关系:
- 调用
JavaVM
的attachCurrentThread
函数,返回JNIEnv
结构体,Native
层可通过JNIEnv
结构体回调
Java层函数。
- 后台进程退出前,调用
JavaVM
的detachCurrentThread
函数,释放对应的线程资源。
1.2.4.1. 通过JNIEnv操作jobject
即操作java 对象类的成员函数
和成员变量
jobject的**jfieldID
和jmethodID
**
取出jfieldID
和jmethodID
JNIEnv的两个系统函数:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| private native void processDirectory(String path, MediaScannerClient client); android_media_MediaScanner_processDirectory( JNIEnv *env, jobject thiz, jstring path, jobject client) { ......... MediaScanner *mp = getNativeScanner_l(env, thiz); const char *pathStr = env->GetStringUTFChars(path, NULL); MyMediaScannerClient myClient(env, client); MediaScanResult result = mp->processDirectory(pathStr, myClient); env->ReleaseStringUTFChars(path, pathStr); }
MyMediaScannerClient(JNIEnv *env, jobject client) : mEnv(env), mClient(env->NewGlobalRef(client)), mScanFileMethodID(0), mHandleStringTagMethodID(0), mSetMimeTypeMethodID(0) { jclass mediaScannerClientInterface = env->FindClass(kClassMediaScannerClient); mScanFileMethodID = env->GetMethodID( mediaScannerClientInterface, "scanFile", "(Ljava/lang/String;JJZZ)V"); mHandleStringTagMethodID = env->GetMethodID( mediaScannerClientInterface, "handleStringTag", "(Ljava/lang/String;Ljava/lang/String;)V"); ......... }
|
如上,取出MethoidId
,保存为MyMediaScannerClient
类的成员变量(提高运行效率),下一步就是如何使用这些fieldID
和methodID
。
使用jfieldID
和jmethodID
1 2 3 4 5 6 7 8 9 10 11
| virtual status_t scanFile(const char* path, long long lastModified, long long fileSize, bool isDirectory, bool noMedia) { jstring pathStr; pathStr = mEnv->NewStringUTF(path)); mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize, isDirectory, noMedia); mEnv->DeleteLocalRef(pathStr); return checkAndClearExceptionFromCallback(mEnv, "scanFile"); }
|
type对应返回值类型
调用非静态方法
1
| NativeType Call<type>Method (JNIEnv* env, jobject obj, jmethodID methodID,...);
|
调用静态方法
1
| NativeType CallStatic<type>Method(...);
|
获取jobject对象的对应的成员变量值
1
| NativeType Get<type>Field(JNIEnv* env, jobject obj, jfieldID fieldID,...);
|
设置jobject对象的对应的成员变量值
1
| NativeType Set<type>Field(JNIEnv* env, jobject obj, jfieldID fieldID,...);
|

1.2.5. jstring类型介绍
JAVA中的String也是引用类。由于使用比较频繁,Jni创建了jstring类。
1 2 3 4 5 6 7 8 9
| JNIEnv::NewString(...) JNIEnv::NewStringUTF(...)
JNIEnv::GetStringChars(...) JNIEnv::GetStringUTFChars(...);
JNIEnv::ReleaseStringChars(...) JNIEnv::ReleaseStringUTFChars(...);
|
1.2.6. Jni 类型签名
1 2 3 4 5 6
| void processFile(String path,String mimetype); ...... "processFile", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", (void *)android_media_MediaScanner_processFile ..........
|
Java支持函数重载,将参数信息和返回值信息写到签名信息里,便于查找到对应的函数.
最左侧是参数名,在括号里面,括号外面的是返回值类型。
- V代表void, 当参数类型是引用类型时,格式为 ”L包名”, String 格式为”Ljava/lang/String”;
- 基本类型名字可以直接写。
- 数组类型,后面回跟着” [”
- 引用类型最后跟 “ ;”.
Java提供javap的工具帮助生成变量或函数的签名信息。
1.2.7. 垃圾回收
Java中创建的对象由垃圾回收器释放回收内存。
1.2.7.1. Local Reference
非全局引用
Jni函数返回,对象可能被回收。
需要及时回收该类型资源,不要忘记用JNIEnv::DeleteLocalRef
函数回收该资源。
1.2.7.2. Global Reference
全局引用
不主动释放,永远不会被回收。
在类构造函数中创建和释放该类型对象(类似new
delete
的用法)
1.2.7.3. Weak Global Reference
弱全局引用
可能在运行过程中被回收
JNIEnv::isSameObject
判断是否被回收
1.2.8. Jni中的异常处理
JNI中也有异常,但是与Java、C++不同,异常不会中断函数执行,一旦出现异常,只能释放资源,return
。
关键字 |
作用 |
ExceptionOccured |
判断是否发生异常 |
ExceptionClear |
清理当前JNI层发生的异常 |
ThrowNew |
向Java层抛出异常 |