JNI:Java本地调用 ,是Java Native Interface的缩写。JNI是一种技术,可以做到以下两点:
Java语言是平台无关,但是承载Java世界的虚拟机是用Native语言写的,而虚拟机又运行在具体平台上,所以虚拟机本身无法做到平台无关,JNI技术可以针对Java层屏蔽不同操作系统之间的差异,这样就能够实现平台无关特性。
public native String stringFromJNI();
static {
System.loadLibrary("JniDemo");
}
JniDemo是JNI库的名字。实际加载动态库的时候会拓展成libJniDemo.so,在Windows平台上将拓展为JniDemo.dll。
public class MainActivity extends AppCompatActivity {
// Used to load the 'JniDemo' library on application startup.
static {
System.loadLibrary("JniDemo");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Example of a call to a native method
TextView tv = binding.sampleText;
tv.setText(stringFromJNI()); // java代码调用native函数
}
/**
* A native method that is implemented by the 'JniDemo' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_JniDemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
通过原生Android代码分析流程
java: MediaCrypto.java
jni:android_media_MediaCrypto.cpp
MediaCrypto.java 部分代码
99 private static native final void native_init(); //声明一个native函数。native为Java的关键字,表示它将由JNI层完成。
100
101
102
104 static {
105 System.loadLibrary("media_jni"); //加载对应的JNI库,media_jni是JNI库的名字
106 native_init(); //调用 jni native_init函数
107 }
108
加载JNI库
Java要调用Native函数,就必须通过一个位于JNI层的动态库才能做到。加载动态库的时机原则上是在调用native函数前,任何时候、任何地方加载都可以。通行的做法是,在类的static语句中加载,通过调用System.loadLibrary方法就可以了。
Java的native函数
从上面代码中可以发现,native_init函数前有Java的关键字native,它表示这两个函数将由JNI层来实现。、
只要完成下面两项工作就可以使用JNI了:
· 1. 加载对应的JNI库。
· 2. 声明由关键字native修饰的函数。
android_media_MediaCrypto.cpp 部分代码
static void android_media_MediaCrypto_native_init(JNIEnv *env) { //这个函数是native_init的JNI层实现。
160 jclass clazz = env->FindClass("android/media/MediaCrypto");
161 CHECK(clazz != NULL);
162
163 gFields.context = env->GetFieldID(clazz, "mNativeContext", "J");
164 CHECK(gFields.context != NULL);
165 }
android_media_MediaCrypto_native_init是native_init的jni 层实现, java层和native层需要将这2个函数绑定形成关联关系,系统才能找到它们,也就是接下来要说的注册.
在JNI技术中,用JNINativeMethod的结构来记录对应关系,其定义如下
typedef struct {
const char* name; //Java中native函数的名字,不用携带包的路径。例如“native_init“。
const char* signature;//Java函数的签名信息,用字符串表示,是参数类型和返回值类型的组合。
void* fnPtr; //JNI层对应函数的函数指针,注意它是void*类型。
} JNINativeMethod;
如何使用这个数据结构呢,看下对应 android_media_MediaCrypto.cpp 文件代码
6 static const JNINativeMethod gMethods[] = {
307 { "release", "()V", (void *)android_media_MediaCrypto_release },
308 { "native_init", Java中native函数的函数名。
"()V", native_init签名信息,后面再做介绍
(void *)android_media_MediaCrypto_native_init //JNI层对应函数指针。
},
310
312 ...
324 };
325
326 int register_android_media_Crypto(JNIEnv *env) { //注册JNINativeMethod数组
327 return AndroidRuntime::registerNativeMethods(env,
328 "android/media/MediaCrypto", gMethods, NELEM(gMethods));
329 }
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
320 const JNINativeMethod* methods, int numMethods)
321 {
322 ALOGV("Registering %s's %d native methods...", className, numMethods);
323 jclass clazz = (*env)->FindClass(env, className);
324 ALOG_ALWAYS_FATAL_IF(clazz == NULL,
325 "Native registration unable to find class '%s'; aborting...",
326 className);
327 int result = (*env)->RegisterNatives(env, clazz, methods, numMethods);
328 (*env)->DeleteLocalRef(env, clazz);
329 ...
347 }
注册函数register_android_media_Crypto调用时机
1463 jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
1464 {
1465 JNIEnv* env = NULL;
1466 jint result = -1;
1467
1468 if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
1469 ALOGE("ERROR: GetEnv failed\n");
1470 goto bail;
1471 }
1472 assert(env != NULL);
1473
1474 if (register_android_media_ImageWriter(env) != JNI_OK) {
1475 ALOGE("ERROR: ImageWriter native registration failed");
1476 goto bail;
1477 }
...
1554 if (register_android_media_Crypto(env) < 0) {
1555 ALOGE("ERROR: MediaCodec native registration failed");
1556 goto bail;
1557 }
}
当Java层通过System.loadLibrary加载完JNI动态库后,紧接着会查找该库中一个叫JNI_OnLoad的函数,如果有,就调用它,而动态注册的工作就是在这里完成的。所以,如果想使用动态注册方法,就必须要实现JNI_OnLoad函数,只有在这个函数中,才有机会完成动态注册的工作
静态注册是根据函数名来找对应的JNI函数,它要求JNI层函数的名字必须遵循特定的格式, 函数名规则如下
Java_ + JNI方法所在的完整的类名,把类名里面的”.”替换成”_” + 真实的JNI方法名,这个方法名要和Java代码里面声明的JNI方法名一样+ JNI函数必须的默认参数(JNIEnv* env, jobjectthiz)
//native_init对应的JNI函数
//Java层函数名中如果有一个”_”的话,转换成JNI后就变成了”_l”。
JNIEXPORT void JNICALL Java_android_media_MediaCrypto_native_1init(JNIEnv* env, jclass thiz);
当Java层调用native_init函数时,它会从对应的JNI库查找Java_android_media_MediaCrypto_native_linit,如果没有,就会报错。如果找到,则会为这个native_init和Java_android_media_MediaCrypto_native_linit建立一个关联关系,其实就是保存JNI层函数的函数指针。以后再调用native_init函数时,直接使用这个函数指针就可以了,当然这项工作是由虚拟机完成的。
对应的jni头文件可以手写,不过比较麻烦, 可以使用 javah工具自动生成
弊端:
jdk10已经移除javah工具,相应的功能已经集成到javac中,你可以试试javac -h替代javah。
在Java中调用native函数传递的参数是Java数据类型,这些参数类型到了JNI层会变成JNI对应的数据类型
Java数据类型分为基本数据类型和引用数据类型两种,JNI层也是区别对待这二者的。
除了Java中基本数据类型的数组、Class、String和Throwable外,其余所有Java对象的数据类型在JNI中都用jobject表示。
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_JniDemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_JniDemo_MainActivity_stringFromJNI2(JNIEnv *env, jclass clazz) {
// TODO: implement stringFromJNI2()
}
普通的JNI方法对应的JNI函数的第二个参数是jobject类型,而静态的JNI方法对应的JNI函数的第二个参数是jclass类型
在JNI世界里离不开JNIEnv,JNIEnv是一个和线程相关的,代表JNI环境的结构体
JNIEnv提供了一些JNI系统函数,通过这些函数可以
JNIEnv,是一个和线程有关的变量。线程A有一个JNIEnv,线程B有一个JNIEnv。由于线程相关,所以不能在线程B中使用线程A的JNIEnv结构体。当后台线程收到一个网络消息,需要由Native层函数主动回调Java层函数时,JNIEnv是从何而来呢?根据前面的介绍可知,我们不能保存另外一个线程的JNIEnv结构体,然后把它放到后台线程中来用。
前面提到过JNI_OnLoad函数,第一个参数是JavaVM,它是虚拟机在JNI层的代表。不论检查中多少个线程,JavaVM独此一份,在任意地方都可以使用它。
如果我们需要在其他线程访问JVM,那么必须先调用AttachCurrentThread将当前线程与JVM进行关联,然后才能获得JNIEnv对象。
JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL};
JavaVM* vm = AndroidRuntime::getJavaVM();
int result = vm->AttachCurrentThread(&env, (void*) &args);
当然,我们在必要时需要调用DetachCurrentThread来解除链接。
JavaVM* vm = AndroidRuntime::getJavaVM();
int result = vm->DetachCurrentThread();
Java的引用类型除了少数几个外,最终在JNI层都用jobject来表示对象的数据类型,操作jobject的本质是操作这些对象的成员变量和成员函数
JNI规则中,用jfieldID 和jmethodID 来表示Java类的成员变量和成员函数
通过JNIEnv的下面两个函数可以得到:
jfieldID GetFieldID(jclass clazz,const char*name, const char *sig);
jmethodID GetMethodID(jclass clazz, const char*name,const char *sig);
static void android_media_MediaCrypto_native_init(JNIEnv *env) {
160 jclass clazz = env->FindClass("android/media/MediaCrypto");
161 CHECK(clazz != NULL);
162
163 gFields.context = env->GetFieldID(clazz, "mNativeContext", "J");
164 CHECK(gFields.context != NULL);
165 }
接下来就是使用
static sp<JCrypto> setCrypto(
142 JNIEnv *env, jobject thiz, const sp<JCrypto> &crypto) {
143 sp<JCrypto> old = (JCrypto *)env->GetLongField(thiz, gFields.context);
144 if (crypto != NULL) {
145 crypto->incStrong(thiz);
146 }
147 if (old != NULL) {
148 old->decStrong(thiz);
149 }
150 env->SetLongField(thiz, gFields.context, (jlong)crypto.get());
151
152 return old;
153 }
实际上JNIEnv输出了一系列类似GetLongField的函数,形式如下:
//获得fieldID后,可调用Get<type>Field系列函数获取jobject对应成员变量的值。
NativeType Get<type>Field(JNIEnv *env,jobject obj,jfieldID fieldID)
//或者调用Set<type>Field系列函数来设置jobject对应成员变量的值。
void Set<type>Field(JNIEnv *env,jobject obj,jfieldID fieldID,NativeType value)
//下面我们列出一些参加的Get/Set函数。
GetObjectField() SetObjectField()
GetBooleanField() SetBooleanField()
如果想调用Java中的static 字段,则用JNIEnv输出的GetStatic<Type>Field系列函数。
同理 通过JNIEnv操作jobject的成员函数和字段类似
形式如下:
NativeType Call<type>Method(JNIEnv *env,jobject obj,jmethodID methodID, ...)
调用Java中的static函数
JNIEnv输出的CallStatic<Type>Method系列函数
Java中的String也是引用类型,不过由于它的使用非常频繁,所以在JNI规范中单独创建了一个jstring类型来表示Java中的String类型。Java中的String包含很多成员函数,但是jstring是一种独立的数据类型,并没有提供成员函数供操作。
操作jstring得依靠JNIEnv提供的帮助。
可以把一个jstring对象看成是Java中String对象在JNI层的代表,也就是说,jstring就是一个Java String
动态注册中的数组信息
static const JNINativeMethod gMethods[] = {
307 { "release", "()V", (void *)android_media_MediaCrypto_release },
308 { "native_init", "()V", (void *)android_media_MediaCrypto_native_init },
309
310 { "native_setup", "([B[B)V",
311 (void *)android_media_MediaCrypto_native_setup },
312
313 { "native_finalize", "()V",
314 (void *)android_media_MediaCrypto_native_finalize },
315
316 { "isCryptoSchemeSupportedNative", "([B)Z",
317 (void *)android_media_MediaCrypto_isCryptoSchemeSupportedNative },
318
319 { "requiresSecureDecoderComponent", "(Ljava/lang/String;)Z",
320 (void *)android_media_MediaCrypto_requiresSecureDecoderComponent },
321
322 { "setMediaDrmSession", "([B)V",
323 (void *)android_media_MediaCrypto_setMediaDrmSession },
324 };
(void *)android_media_MediaCrypto_native_init 为函数native_init的签名信息,由参数类型和返回值类型共同组成
格式为:
(参数1类型标示参数2类型标示...参数n类型标示)返回值类型标示
因为Java支持函数重载,也就是说,可以定义同名但不同参数的函数。但仅仅根据函数名,是没法找到具体函数的。为了解决这个问题,JNI技术中就使用了参数类型和返回值类型的组合,作为一个函数的签名信息,有了签名信息和函数名,就能很顺利地找到Java中的函数了。
签名信息可以通过以下几种方式获取:
从上面2条结论可得知,当jni层通过 赋值 “=” 保存Java层传入的jobject对象,在某个对象调用时,java层可能已经释放对象
但是JNI规范已很好地解决了这一问题,JNI技术一共提供了三种类型的引用,它们分别是:
Local Reference:本地引用。在JNI层函数中使用的非全局引用对象都是Local Reference。它包括函数调用时传入的jobject、在JNI层函数中创建的jobject。LocalReference最大的特点就是,一旦JNI层函数返回,这些jobject就可能被垃圾回收。
如果不调用DeleteLocalRef,pathStr将在函数返回后被回收;如果调用DeleteLocalRef的话,pathStr会立即被回收。
Global Reference:(env->NewGlobalRef(client)) )全局引用,这种对象如不主动释放,就永远不会被垃圾回收,调用DeleteGlobalRef释放这个全局引用。
Weak Global Reference:弱全局引用,一种特殊的GlobalReference,在运行过程中可能会被垃圾回收。所以在程序中使用它之前,需要调用JNIEnv的IsSameObject判断它是不是被回收了。
参考 https://blog.csdn.net/qinjuning/article/details/7595104
当JNI函数调用的Java方法出现异常的时候,并不会影响JNI方法的执行,但是我们并不推荐JNI函数忽略Java方法出现的异常继续执行,这样可能会带来更多的问题。我们推荐的方法是,当JNI函数调用的Java方法出现异常的时候,JNI函数应该合理的停止执行代码。
ExceptionClear函数用来清除JNI函数调用的Java方法出现的异常
Java代码:
JNI代码: