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

native层打印logcat日志

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

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

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

主要包含三个步骤:

  1. cmake文件中引入静态库
  2. 包含<android/log.h>头文件
  3. 调用__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规范中定义了三种引用:局部引用、全局引用、弱全局引用。

  1. 局部引用:通过NewLocalRef和各种JNI接口创建。会阻止GC回收所引用的对象,不在本地函数中跨函数使用,不能跨线程使用。函数返回后局部引用所引用的对象会被JVM自动释放,或调用DeleteLocalRef释放。
  2. 全局引用:调用NewGlobalRef基于局部引用创建,会阻止GC回收所引用的对象。可以跨方法、跨线程使用。JVM不会自动释放,必须调用DeleteGlobalRef手动释放。
  3. 弱全局引用:调用NewWeakGlobalRef基于局部引用或全局引用创建,不会阻止GC回收所引用的对象,可以跨方法、跨线程使用。引用不会自动释放,在JVM认为应该回收它的时候(比如内存紧张的时候)进行内存回收而释放。或调用DeleteWeakGlobalRef手动释放。

关注我的技术公众号,不定期会有技术文章推送,不敢说优质,但至少是我自己的学习心得。微信扫一扫下方二维码即可关注:
二维码

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页