0%

Jni 基础学习文档

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();
}
// Native函数声明,加native关键字, 实际功能由Jni层完成
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层函数的函数指针,并和Javanative函数建立关联,再调用native声明的函数时,直接调用相应的函数指针。

1.2.3.2. 动态注册

结构体保存关联信息

1
2
3
4
5
6
7
8
Typedef struct {
// java层函数名字,如”native_init”,不用携带包的名称
Const char* name;
// Java层函数的签名信息,是代表参数类型和返回值的字符串
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);
}

// /libnativehelper/JNIHelp.cpp
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);
}
//调用JniEnv的RegisterNatives函数,注册关联起来。
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
// 注册所有Media相关类的Jni函数
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;
// 第一个参数为JavaVM,虚拟机在Jni层的代表,每个JAVA进程只有一个这样的//JavaVM。
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;
}

/* success -- return valid version number */
result = JNI_VERSION_1_4;

bail:
return result;
}

1.2.3.3. 数据类型转换

Java数据分为基本数据类型和引用数据类型

1.2.3.3.1. 基本数据类型转换

Picture2

注意字长

1.2.3.3.2. 引用数据类型转换

Picture3

除了Java中基本数据类型的数组classString外,其他全用jobject表示,如Java中MediaScannerClient 类就用jobject表示,这就牵扯到如何用jobject操作类中的成员变量和成员函数的问题。

1.2.4. JNIEnv

JNIEnv代表一个线程相关的代表Jni环境的结构体,JNIEnv的结构如下:

Picture4

JNIEnv提供了一些系统函数,通过系统函数可以:

  • 调用Java函数
  • 操作jobject对象

每个Java进程只有一个JavaVM,进程中的线程共享该JavaVM

1
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)

JavaVM和JNIEnv有如下关系:

  • 调用JavaVMattachCurrentThread函数,返回JNIEnv结构体,Native层可通过JNIEnv结构体回调Java层函数。
  • 后台进程退出前,调用JavaVMdetachCurrentThread函数,释放对应的线程资源。

1.2.4.1. 通过JNIEnv操作jobject

即操作java 对象类的成员函数成员变量

jobject的**jfieldIDjmethodID**

取出jfieldIDjmethodID

JNIEnv的两个系统函数:

Picture5

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 构造函数
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类的成员变量(提高运行效率),下一步就是如何使用这些fieldIDmethodID

使用jfieldIDjmethodID

1
2
3
4
5
6
7
8
9
10
11
//通过JNIEnv,Native层回调Java层函数    
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,...);

Picture6

1.2.5. jstring类型介绍

JAVA中的String也是引用类。由于使用比较频繁,Jni创建了jstring类。

1
2
3
4
5
6
7
8
9
// 由Native本地字符串(char*)转换成jstring,得到Java 的String对象。
JNIEnv::NewString(...) //unicode
JNIEnv::NewStringUTF(...) //utf-8
// Java.String (jstring)转换为Native. String(const char*);
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
Javap -s -p xxx.class

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层抛出异常