你应该了解的JNI知识——注意点
Posted xingfeng_coder
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你应该了解的JNI知识——注意点相关的知识,希望对你有一定的参考价值。
native层打印logcat日志
native层打印logcat日志,有两种方式:
- 调用Java层的Log.i/v()之类的方法,可以参考你应该了解的JNI知识(二)——Java与JNI互相调用,里面介绍了如何在native层调用Java代码。
- 使用liblog.so进行打印,和Log.i/v()底层使用同样的原理
这里主要介绍如何使用第二种方法打印日志。
主要包含三个步骤:
- cmake文件中引入静态库
- 包含<android/log.h>头文件
- 调用__android_log_write()、__android_log_print()等方法打印日志
引入liblog.so库
系统的日志库是在liblog.so共享库中的,要使用该功能,需要在cmake中引入库。
log.h的注释中有如下话:
NOTE: These functions MUST be implemented by /system/lib/liblog.so
cmake中加入如下语句:
find_library(log-lib log)
target_link_libraries($PROJECT_NAME $log-lib)
编写源代码
#include <android/log.h>
JNIEXPORT void JNICALL
Java_com_enniu_jnidemo_MainActivity_log(JNIEnv *env, jobject thiz, jstring tag, jstring log)
const char *tag_chars = env->GetStringUTFChars(tag, NULL);
const char *log_chars = env->GetStringUTFChars(log, NULL);
__android_log_write(ANDROID_LOG_DEBUG, tag_chars, log_chars);
__android_log_print(ANDROID_LOG_DEBUG,tag_chars,"text from java : %s",log_chars);
env->ReleaseStringUTFChars(tag, tag_chars);
env->ReleaseStringUTFChars(log, log_chars);
其中__android_log_print()的区别在于其等同于printf。
第一个参数是优先级,和Log的等级是对应的,第二个参数是tag,第三个参数是log内容。
关于更多内容和方法可以参考log.h的注释。
混淆
做Android的同学都会遇到混淆的问题,而涉及到了JNI、NDK时更需要注意混淆的问题,这是因为不论是静态注册还是动态注册,都涉及到了包名_类名_方法名这样的关系,而这样的关系是绝对的,因此是不能进行混淆的,一旦进行了混淆,JVM就找不到对应的native方法了。
另外,由于Java代码和Native有互操作性,因此如果在native代码中操作Java代码,之前说过这种方式是类似Java的反射的,也会根据classname去找到Class类等步骤,因此如果用到了这个功能的也不能混淆对应的类和方法。
全局引用和局部引用
试想一种场景,在JNI_OnLoad中通过FindClass找到某一个类,然后用作静态变量,在以后某个场景使用该静态场景,一些是不是设想的很美好,但在JNI环境中是不行的。
举个例子
在JNI_OnLoad中找到android.util.Log类,然后保存
成静态变量,代码如下:
static jclass log_class;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *unused)
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
return JNI_FALSE;
jclass log_clazz = env->FindClass("android/util/Log");
log_class = log_clazz;
return JNI_VERSION_1_4;
Java端调用
这里定义一个native方法,去使用静态的log类,打印日志,如下代码:
if (NULL != log_class)
__android_log_write(ANDROID_LOG_DEBUG, "TAG", "Log not null");
jmethodID i_log_methodid = env->GetStaticMethodID(log_class, "i",
"(Ljava/lang/String;Ljava/lang/String;)I");
jstring tag = env->NewStringUTF("TAG");
jstring log = env->NewStringUTF("Global Ref");
env->CallStaticIntMethod(log_class, i_log_methodid, tag, log);
这里还判断了是不是为NULL,以及打印了个日志,运行看下效果,结果崩溃了。Logcat中的部分日志如下:
2019-06-20 17:08:57.024 10341-10341/com.enniu.jnidemo D/TAG: Log not null
2019-06-20 17:08:57.024 10341-10341/com.enniu.jnidemo E/zygote: JNI ERROR (app bug): accessed stale Local 0x75 (index 7 in a table of size 6)
2019-06-20 17:08:57.054 10341-10341/com.enniu.jnidemo A/zygote: java_vm_ext.cc:534] JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0x75
2019-06-20 17:08:57.054 10341-10341/com.enniu.jnidemo A/zygote: java_vm_ext.cc:534] from void com.enniu.jnidemo.MainActivity.testGlobalRef()
2019-06-20 17:08:57.054 10341-10341/com.enniu.jnidemo A/zygote: java_vm_ext.cc:534] "main" prio=5 tid=1 Runnable
可以看到错误日志主要是这个:JNI ERROR (app bug): accessed stale Local 0x75 (index 7 in a table of size 6)。
这个是因为获取不到那个对象了。
要怎么处理呢? 使用NewGlobalRef将该对象封装为全局引用。
只要作出的改变如下:
log_class= reinterpret_cast<jclass>(env->NewGlobalRef(log_clazz));//NewGlobalRef
这样log_class就成为了全局引用,就不会再崩溃了。
原因是什么?
原因见于:https://developer.android.google.cn/training/articles/perf-jni?hl=zh_cn#kotlin
三种引用
在JNI规范中定义了三种引用:局部引用、全局引用、弱全局引用。
- 局部引用:通过NewLocalRef和各种JNI接口创建。会阻止GC回收所引用的对象,不在本地函数中跨函数使用,不能跨线程使用。函数返回后局部引用所引用的对象会被JVM自动释放,或调用DeleteLocalRef释放。
- 全局引用:调用NewGlobalRef基于局部引用创建,会阻止GC回收所引用的对象。可以跨方法、跨线程使用。JVM不会自动释放,必须调用DeleteGlobalRef手动释放。
- 弱全局引用:调用NewWeakGlobalRef基于局部引用或全局引用创建,不会阻止GC回收所引用的对象,可以跨方法、跨线程使用。引用不会自动释放,在JVM认为应该回收它的时候(比如内存紧张的时候)进行内存回收而释放。或调用DeleteWeakGlobalRef手动释放。
关注我的技术公众号,不定期会有技术文章推送,不敢说优质,但至少是我自己的学习心得。微信扫一扫下方二维码即可关注:
以上是关于你应该了解的JNI知识——注意点的主要内容,如果未能解决你的问题,请参考以下文章