JNI是Java Native Interface的缩写,JNI技术实现了java与本地代码(c,c++)的相互调用。
JNI的必要性:
java运行的环境是虚拟机,虚拟机与具体平台相关,JNI层屏蔽不同平台之间的差异,使的java可以做到平台无关特性。
从运行速度与效率的角度考虑,采用JNI技术是明智的。
1.JNI动态注册
// frameworks/base/media/java/android/media/MediaScanner.java
........
public class MediaScanner
{
static {
// 加载动态库media_jni
// 实际加载时会将其拓展, Linux系统为libmedia_jni.so, Windows系统为media_jni.dll
System.loadLibrary("media_jni");
native_init(); // 调用JNI层代码
}
........
// 带native的为本地方法,具体实现在JNI层完成
private static native final void native_init();
........
}
// frameworks/base/media/jni/android_media_MediaScanner.cpp
........
static const char* const kClassMediaScanner =
"android/media/MediaScanner";
........
static void android_media_MediaScanner_native_init(JNIEnv *env) // native_init的具体实现过程
{
ALOGV("native_init");
jclass clazz = env->FindClass(kClassMediaScanner);
if (clazz == NULL) {
return;
}
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}
}
........
static JNINativeMethod gMethods[] = { // 定义JNINativeMethod类型的数组
........
{
"native_init", // java中native函数名
"()V", // 签名信息
(void *)android_media_MediaScanner_native_init // 对应的本地函数指针
},// 将所有native函数与本地函数建立对应关系
........
}
int register_android_media_MediaScanner(JNIEnv *env)
{ // 注册JNINativeMethod类型的数组,第二个参数表明Java中的那个类
return AndroidRuntime::registerNativeMethods(env,
kClassMediaScanner, gMethods, NELEM(gMethods));
}
// libnativehelper/include/nativehelper/Jni.h
........
typedef struct {
const char* name; // java中native方法名
const char* signature; // Java函数签名信息,表示参数类型与返回类型
void* fnPtr; // JNI层对应函数的函数指针
} JNINativeMethod;
........
// frameworks/base/core/jni/AndroidRuntime.cpp
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{ // 调用jniRegisterNativeMethods来完成注册
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
// libnativehelper/include/nativehelper/JNIHelp.h
........
int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int 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 ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { // 调用JNIEnv的RegisterNatives来注册
........
}
return 0;
}
........
// register_android_media_MediaScanner调用过程
// 在Java中调用System.loadLibrary加载完JNI动态库后,如果有JNI_OnLoad函数,就会调用JNI_OnLoad函数
// 如果要进行动态注册,就必须实现JNI_OnLoad函数。静态注册也可以在这个函数中做一些初始化工作
// frameworks/base/media/jni/android_media_MediaPlayer.cpp
........
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;
// 通过vm来初始化JNIEnv类型的指针env,
// vm为虚拟机对象,每个Java进程都只有一个虚拟机对象
// JNIEvn是一个与线程相关的一个结构体,每个线程也只有一个
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL);
........
// 动态注册MediaScanner的JNI函数
if (register_android_media_MediaScanner(env) < 0) {
ALOGE("ERROR: MediaScanner native registration failed\n");
goto bail;
}
........
}
........
2.JNI静态注册
1.编译MediaScanner.java生成MediaScanner.class文件如
有文件Project/src/android/media/Test.java
package android.media;
class Test{
static {
System.loadLibrary("media_jni");
native_init();
}
private static native final void native_init();
private native final void native_setup();
}
cd ../Project/src/
javac ./android/media/Test.java // 在./android/media/目录下生成Test.class文件
2.使用java的工具javah, 如
javah -classpath ./ -d ./jni android.media.Test
-classpath ./ 表示Test.class是从当前目录下的子目录中
-d ./jni 表示生成的 xxx.h文件所在的目录
android.media.Test 表示包名.类名
在../Project/src/jni/下生成android_media_Test.h文件
3.命名规则
头文件xxx.h: 包名.包名xxx.类名.java文件最终将生成 包名_包名xxx_类名.h文件
方法native_init:Java_android_media_Test_native_1init 包名前加Java_,包名中的.变为_,方法中的_变_1
4.将android_media_Test.h里面的方法实现,然后将其编译为media_jni模块导入手机
5.静态注册的弊端
每个有声明了native方法的java类所生成的class文件都要用javah生成一个头文件
javah生成的函数名特别长,不方便书写
初次调用native函数时需要根据函数名字搜索对应的JNI层函数来建立关联关系,影响运行效率
基本数据类型java->native 基本类型前加j
java boolean byte char short int long float double
native jboolean jbyte jchar jshort jint jlong jfloat jdouble
引用数据类型
java objects Class String Object[] Throwable boolean[] byte[] ...... float[] double[]
native jobject jclass jstring jobjectArray jthrowable jbooleanArray jbyteArray ...... jfloatArray jdoubleArray
4.JNIEnv操作jobject
// frameworks/base/media/jni/android_media_MediaPlayer.cpp
........
MyMediaScannerClient(JNIEnv *env, jobject client)
: ........
{
ALOGV("MyMediaScannerClient constructor");
jclass mediaScannerClientInterface = // 找到MediaScannerClient类在JNI层对应的jclass实例
env->FindClass(kClassMediaScannerClient);
if (mediaScannerClientInterface == NULL) {
ALOGE("Class %s not found", kClassMediaScannerClient);
} else {
mScanFileMethodID = env->GetMethodID(
mediaScannerClientInterface, // java类对应的JNI实例
"scanFile", // 取得scanFile方法的jMethodID, GetFieldID为获取属性jFildID
"(Ljava/lang/String;JJZZ)V");
........
}
}
........
virtual status_t scanFile(const char* path, long long lastModified,
long long fileSize, bool isDirectory, bool noMedia)
{
........
jstring pathStr; // 定义一个jstring对象,相当于java中String对象在JNI的代表
// 通过JNIEnv的NewStringUTF方法构造一个jstring对象,path为UTF-8字符串
// 也可调用NewString构造一个Unicode字符串
// 返过来,通过GetStringChars可以获取一个Unicode字符串,GetStringUTFChars可以获取一个UTF-8字符串
// 用完jstring对象后一定要释放,否则会造成内存泄露
// ReleaseStringChars函数或者ReleaseStringUTFChars函数可以释放资源
if ((pathStr = mEnv->NewStringUTF(path)) == NULL) {
// JNI中发生异常时不会直接退出,而是在函数返回后到Java层后,虚拟机才会抛出抛出异常
// ExceptionOccured函数用于判断是否发生异常
// ThrowNew函数用于向Java层抛出异常 eg: mEnv->ThrowNew(jclass, chars)
mEnv->ExceptionClear(); // 清理当前JNI层发生的异常
return NO_MEMORY;
}
// 调用JNIEnv的CallVoidMethod函数来调用java方法
// mClient表示是MediaScannerClient的jobject对象
// mScanFileMethodID表示是scanFile的jMethodID,后面的是参数
// Call<Type>Method 为调用非静态方法, CallStatic<Type>Method为调用静态方法
// Get/Set<Type>Field为获取或设置Java类的属性
// <Type>为Object,Boolean,Byte,Char,Short,Int,Long,Float,Double
// jobject由jmethodid与jfileid组成
mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
fileSize, isDirectory, noMedia);
........
}
........
5.JNI类型签名
签名字符串构造规则: (参数1类型标识参数2类型标识...参数n类型标识)返回类型标识
(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V
最右边的V表示返回类型为void
第一个参数类型标识中L表示该参数是引用类型,构造规则是:L包名; ,其中包名中的.用/替换
类型标识示意图
Z B C S I J F D Ljava/lang/String; [I [Ljava/lang/object;
boolean byte char short int long float double String int[] Object[]
例如: "(ZBCSLjava/lang/String;[Ljava/lang/object;)[I" 对应的函数为 int[] f(boolean bo, byte by, char ch, short sh, String string, Object[] objs)
Java提供了工具javap可直接生成函数或变量的签名信息,用如下
javap -classpath ./ -d ./ -s -p android.media.Test
-classpath ./ -d ./ 与上面解释相同
-s 表示输出内部数据类型的签名信息
-p 打印所有函数和成员的签名信息, 默认只打印公共的函数或变量的签名信息
6.垃圾回收
Java中创建的对象最后是由垃圾回收器来释放内存,可它对JNI会有什么影响呢
// ./Project/src/jni/android_media_Test.cpp
static jclass save_thiz = NULL;
JNIEXPORT void JNICALL Java_android_media_Test_native_1init(JNIEnv * env, jclass thiz){
........
// 保存Java层传入的jclass对象
save_thiz = thiz; // 这种赋值不会增加jclass的引用计数
........
}
JNIEXPORT void JNICALL Java_android_media_Test_native_1setup(JNIEnv *, jobject){
........
// 在这里使用save_thiz有可能出错,此时save_thiz有可能是野指针,所指的对象被Java垃圾回收机制回收
........
}
对于以上问题,JNI提供了三种类型的引用
Local Reference:
本地引用,JNI层函数返回时被垃圾回收
jobject obj = env->NewLocalRef(jobjects); // obj对象会在所在函数退出的时候被回收
env->DeleteLocalRef(obj); // 立即回收obj对象,对于不需要的对象立即回收非常必要
Global Reference:
全局引用,不主动释放即不会被垃圾回收
jobject mClient = joevn->NewGlobalRef(jobjects); // 在构造函数中创建
env->DeleteGlobalRef(mClient); // 在析构函数中回收
Weak Global Reference:
弱全局引用,在使用的过程中可能被回收,在使用前需要调用JNIEnv的IsSameObject判断是否被回收
jobject mObject = env->NewWeakGlobalRef(thiz);
env->IsSameObject(mObject,thiz);
env->DeleteWeakGlobalRef(mObject);
void test(JNIEnv * env, jstring strings){
........
for(int i=0; i<100000; i++){
jstring c = env->NewString(strings); // c将会在test方法退出时释放
........
// env->DeleteLocalRef(c); // 如果此处不调用此方法将导致内存不立即释放
}
........
}
JNI的必要性:
java运行的环境是虚拟机,虚拟机与具体平台相关,JNI层屏蔽不同平台之间的差异,使的java可以做到平台无关特性。
从运行速度与效率的角度考虑,采用JNI技术是明智的。
1.JNI动态注册
// frameworks/base/media/java/android/media/MediaScanner.java
........
public class MediaScanner
{
static {
// 加载动态库media_jni
// 实际加载时会将其拓展, Linux系统为libmedia_jni.so, Windows系统为media_jni.dll
System.loadLibrary("media_jni");
native_init(); // 调用JNI层代码
}
........
// 带native的为本地方法,具体实现在JNI层完成
private static native final void native_init();
........
}
// frameworks/base/media/jni/android_media_MediaScanner.cpp
........
static const char* const kClassMediaScanner =
"android/media/MediaScanner";
........
static void android_media_MediaScanner_native_init(JNIEnv *env) // native_init的具体实现过程
{
ALOGV("native_init");
jclass clazz = env->FindClass(kClassMediaScanner);
if (clazz == NULL) {
return;
}
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}
}
........
static JNINativeMethod gMethods[] = { // 定义JNINativeMethod类型的数组
........
{
"native_init", // java中native函数名
"()V", // 签名信息
(void *)android_media_MediaScanner_native_init // 对应的本地函数指针
},// 将所有native函数与本地函数建立对应关系
........
}
int register_android_media_MediaScanner(JNIEnv *env)
{ // 注册JNINativeMethod类型的数组,第二个参数表明Java中的那个类
return AndroidRuntime::registerNativeMethods(env,
kClassMediaScanner, gMethods, NELEM(gMethods));
}
// libnativehelper/include/nativehelper/Jni.h
........
typedef struct {
const char* name; // java中native方法名
const char* signature; // Java函数签名信息,表示参数类型与返回类型
void* fnPtr; // JNI层对应函数的函数指针
} JNINativeMethod;
........
// frameworks/base/core/jni/AndroidRuntime.cpp
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{ // 调用jniRegisterNativeMethods来完成注册
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
// libnativehelper/include/nativehelper/JNIHelp.h
........
int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int 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 ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { // 调用JNIEnv的RegisterNatives来注册
........
}
return 0;
}
........
// register_android_media_MediaScanner调用过程
// 在Java中调用System.loadLibrary加载完JNI动态库后,如果有JNI_OnLoad函数,就会调用JNI_OnLoad函数
// 如果要进行动态注册,就必须实现JNI_OnLoad函数。静态注册也可以在这个函数中做一些初始化工作
// frameworks/base/media/jni/android_media_MediaPlayer.cpp
........
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;
// 通过vm来初始化JNIEnv类型的指针env,
// vm为虚拟机对象,每个Java进程都只有一个虚拟机对象
// JNIEvn是一个与线程相关的一个结构体,每个线程也只有一个
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL);
........
// 动态注册MediaScanner的JNI函数
if (register_android_media_MediaScanner(env) < 0) {
ALOGE("ERROR: MediaScanner native registration failed\n");
goto bail;
}
........
}
........
2.JNI静态注册
1.编译MediaScanner.java生成MediaScanner.class文件如
有文件Project/src/android/media/Test.java
package android.media;
class Test{
static {
System.loadLibrary("media_jni");
native_init();
}
private static native final void native_init();
private native final void native_setup();
}
cd ../Project/src/
javac ./android/media/Test.java // 在./android/media/目录下生成Test.class文件
2.使用java的工具javah, 如
javah -classpath ./ -d ./jni android.media.Test
-classpath ./ 表示Test.class是从当前目录下的子目录中
-d ./jni 表示生成的 xxx.h文件所在的目录
android.media.Test 表示包名.类名
在../Project/src/jni/下生成android_media_Test.h文件
3.命名规则
头文件xxx.h: 包名.包名xxx.类名.java文件最终将生成 包名_包名xxx_类名.h文件
方法native_init:Java_android_media_Test_native_1init 包名前加Java_,包名中的.变为_,方法中的_变_1
4.将android_media_Test.h里面的方法实现,然后将其编译为media_jni模块导入手机
5.静态注册的弊端
每个有声明了native方法的java类所生成的class文件都要用javah生成一个头文件
javah生成的函数名特别长,不方便书写
初次调用native函数时需要根据函数名字搜索对应的JNI层函数来建立关联关系,影响运行效率
基本数据类型java->native 基本类型前加j
java boolean byte char short int long float double
native jboolean jbyte jchar jshort jint jlong jfloat jdouble
引用数据类型
java objects Class String Object[] Throwable boolean[] byte[] ...... float[] double[]
native jobject jclass jstring jobjectArray jthrowable jbooleanArray jbyteArray ...... jfloatArray jdoubleArray
4.JNIEnv操作jobject
// frameworks/base/media/jni/android_media_MediaPlayer.cpp
........
MyMediaScannerClient(JNIEnv *env, jobject client)
: ........
{
ALOGV("MyMediaScannerClient constructor");
jclass mediaScannerClientInterface = // 找到MediaScannerClient类在JNI层对应的jclass实例
env->FindClass(kClassMediaScannerClient);
if (mediaScannerClientInterface == NULL) {
ALOGE("Class %s not found", kClassMediaScannerClient);
} else {
mScanFileMethodID = env->GetMethodID(
mediaScannerClientInterface, // java类对应的JNI实例
"scanFile", // 取得scanFile方法的jMethodID, GetFieldID为获取属性jFildID
"(Ljava/lang/String;JJZZ)V");
........
}
}
........
virtual status_t scanFile(const char* path, long long lastModified,
long long fileSize, bool isDirectory, bool noMedia)
{
........
jstring pathStr; // 定义一个jstring对象,相当于java中String对象在JNI的代表
// 通过JNIEnv的NewStringUTF方法构造一个jstring对象,path为UTF-8字符串
// 也可调用NewString构造一个Unicode字符串
// 返过来,通过GetStringChars可以获取一个Unicode字符串,GetStringUTFChars可以获取一个UTF-8字符串
// 用完jstring对象后一定要释放,否则会造成内存泄露
// ReleaseStringChars函数或者ReleaseStringUTFChars函数可以释放资源
if ((pathStr = mEnv->NewStringUTF(path)) == NULL) {
// JNI中发生异常时不会直接退出,而是在函数返回后到Java层后,虚拟机才会抛出抛出异常
// ExceptionOccured函数用于判断是否发生异常
// ThrowNew函数用于向Java层抛出异常 eg: mEnv->ThrowNew(jclass, chars)
mEnv->ExceptionClear(); // 清理当前JNI层发生的异常
return NO_MEMORY;
}
// 调用JNIEnv的CallVoidMethod函数来调用java方法
// mClient表示是MediaScannerClient的jobject对象
// mScanFileMethodID表示是scanFile的jMethodID,后面的是参数
// Call<Type>Method 为调用非静态方法, CallStatic<Type>Method为调用静态方法
// Get/Set<Type>Field为获取或设置Java类的属性
// <Type>为Object,Boolean,Byte,Char,Short,Int,Long,Float,Double
// jobject由jmethodid与jfileid组成
mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
fileSize, isDirectory, noMedia);
........
}
........
5.JNI类型签名
签名字符串构造规则: (参数1类型标识参数2类型标识...参数n类型标识)返回类型标识
(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V
最右边的V表示返回类型为void
第一个参数类型标识中L表示该参数是引用类型,构造规则是:L包名; ,其中包名中的.用/替换
类型标识示意图
Z B C S I J F D Ljava/lang/String; [I [Ljava/lang/object;
boolean byte char short int long float double String int[] Object[]
例如: "(ZBCSLjava/lang/String;[Ljava/lang/object;)[I" 对应的函数为 int[] f(boolean bo, byte by, char ch, short sh, String string, Object[] objs)
Java提供了工具javap可直接生成函数或变量的签名信息,用如下
javap -classpath ./ -d ./ -s -p android.media.Test
-classpath ./ -d ./ 与上面解释相同
-s 表示输出内部数据类型的签名信息
-p 打印所有函数和成员的签名信息, 默认只打印公共的函数或变量的签名信息
6.垃圾回收
Java中创建的对象最后是由垃圾回收器来释放内存,可它对JNI会有什么影响呢
// ./Project/src/jni/android_media_Test.cpp
static jclass save_thiz = NULL;
JNIEXPORT void JNICALL Java_android_media_Test_native_1init(JNIEnv * env, jclass thiz){
........
// 保存Java层传入的jclass对象
save_thiz = thiz; // 这种赋值不会增加jclass的引用计数
........
}
JNIEXPORT void JNICALL Java_android_media_Test_native_1setup(JNIEnv *, jobject){
........
// 在这里使用save_thiz有可能出错,此时save_thiz有可能是野指针,所指的对象被Java垃圾回收机制回收
........
}
对于以上问题,JNI提供了三种类型的引用
Local Reference:
本地引用,JNI层函数返回时被垃圾回收
jobject obj = env->NewLocalRef(jobjects); // obj对象会在所在函数退出的时候被回收
env->DeleteLocalRef(obj); // 立即回收obj对象,对于不需要的对象立即回收非常必要
Global Reference:
全局引用,不主动释放即不会被垃圾回收
jobject mClient = joevn->NewGlobalRef(jobjects); // 在构造函数中创建
env->DeleteGlobalRef(mClient); // 在析构函数中回收
Weak Global Reference:
弱全局引用,在使用的过程中可能被回收,在使用前需要调用JNIEnv的IsSameObject判断是否被回收
jobject mObject = env->NewWeakGlobalRef(thiz);
env->IsSameObject(mObject,thiz);
env->DeleteWeakGlobalRef(mObject);
void test(JNIEnv * env, jstring strings){
........
for(int i=0; i<100000; i++){
jstring c = env->NewString(strings); // c将会在test方法退出时释放
........
// env->DeleteLocalRef(c); // 如果此处不调用此方法将导致内存不立即释放
}
........
}