前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你应该了解的JNI知识(三)——注意点

你应该了解的JNI知识(三)——注意点

作者头像
用户1108631
发布2019-08-17 12:31:24
1.2K0
发布2019-08-17 12:31:24
举报

native层打印logcat日志

native层打印logcat日志,有两种方式:

  1. 调用Java层的Log.i/v()之类的方法,可以参考你应该了解的JNI知识(二)——Java与JNI互相调用,里面介绍了如何在native层调用Java代码。
  2. 使用liblog.so进行打印,和Log.i/v()底层使用同样的原理

这里主要介绍如何使用第二种方法打印日志。

主要包含三个步骤:

  1. cmake文件中引入静态库
  2. 包含头文件
  3. 调用androidlogwrite()、androidlogprint()等方法打印日志

引入liblog.so库

系统的日志库是在liblog.so共享库中的,要使用该功能,需要在cmake中引入库。log.h的注释中有如下话:

代码语言:javascript
复制
NOTE: These functions MUST be implemented by /system/lib/liblog.so

cmake中加入如下语句:

代码语言:javascript
复制
find_library(log-lib log)  
target_link_libraries(${PROJECT_NAME} ${log-lib})

编写源代码

代码语言:javascript
复制
#include <android/log.h>
JNIEXPORT void JNICALLJava_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);
}

其中_androidlog_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类,然后保存 成静态变量,代码如下:

代码语言:javascript
复制
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类,打印日志,如下代码:

代码语言:javascript
复制
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中的部分日志如下:

代码语言:javascript
复制
2019-06-20 17:08:57.024 10341-10341/com.enniu.jnidemo D/TAG: Log not null2019-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 0x752019-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将该对象封装为全局引用。 只要作出的改变如下:

代码语言:javascript
复制
    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规范中定义了三种引用:局部引用、全局引用、弱全局引用。

  1. 局部引用:通过NewLocalRef和各种JNI接口创建。会阻止GC回收所引用的对象,不在本地函数中跨函数使用,不能跨线程使用。函数返回后局部引用所引用的对象会被JVM自动释放,或调用DeleteLocalRef释放。
  2. 全局引用:调用NewGlobalRef基于局部引用创建,会阻止GC回收所引用的对象。可以跨方法、跨线程使用。JVM不会自动释放,必须调用DeleteGlobalRef手动释放。
  3. 弱全局引用:调用NewWeakGlobalRef基于局部引用或全局引用创建,不会阻止GC回收所引用的对象,可以跨方法、跨线程使用。引用不会自动释放,在JVM认为应该回收它的时候(比如内存紧张的时候)进行内存回收而释放。或调用DeleteWeakGlobalRef手动释放。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 每天学点Android知识 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • native层打印logcat日志
    • 引入liblog.so库
      • 编写源代码
      • 混淆
      • 全局引用和局部引用
        • 举个例子
          • Java端调用
            • 原因是什么?
              • 三种引用
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档