NDK与JNI基础-JIN的引用、缓存策略、异常


先上一张图,同时此文章需要 NDK与JNI基础-AndriodStudio混合编译使用记录 作为铺垫

 

JNI引用

引用解决什么问题?解决JVM什么时候来回收JNI这个对象,

Activity中添加:

public native void getLocalReference();

CPP中去实现一个模拟:

extern "C"
JNIEXPORT void JNICALL
Java_com_lckiss_ndkgradle_MainActivity_getLocalReference(JNIEnv *env, jobject instance) {

    //模拟一个循环 100对象
    for (int i = 0; i < 100; ++i) {
        jclass _jclass = env->FindClass("java/util/Date");
        //env->GetMethodID(_jclass,"<init>","()V") 构造函数的调用方法
        jobject _jobject = env->NewObject(_jclass, env->GetMethodID(_jclass, "<init>", "()V"));
        //-----对象操作
        //释放对象 需要手动释放
        env->DeleteLocalRef(_jobject);
        //全局变量释放
//        env->DeleteGlobalRef(_jobject);
        //弱全局引用释放
//        env->DeleteWeakGlobalRef(_jobject);
    }
}

怎么模拟呢?试着不移除,看看内存情况呗。100不够 你就10000个,看看你的内存会不会有问题

JNI异常处理

我们尝试去构造一个异常,并在java中去捕获:

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }
    //为后面铺垫
    public String name="Test ";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //异常处理部分 看能否捕获JNI中的异常
        try{
            exception();
        }
        catch (Exception e){
            Log.d("Exception", "onCreate: "+e.getMessage());
        }
        //看出了异常后是否会继续
        Log.d("Exception", "onCreate: ------------");
    }
    public native void exception();
}

CPP中对应代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_lckiss_ndkgradle_MainActivity_exception(JNIEnv *env, jobject instance) {
    //构造一个异常
    int i=0;
    int j=100;
    int c=j/i;
}

运行后你会发现,异常发生了但是并未捕获(打印了 onCreat------),那么应该怎么做呢?

接着改下CPP

extern "C"
JNIEXPORT void JNICALL
Java_com_lckiss_ndkgradle_MainActivity_exception(JNIEnv *env, jobject instance) {
    //重新构建一个异常 去获取一个不存在的属性 假定为notCreat,其为String类型
    jclass _jclass= env->GetObjectClass(instance);
   jfieldID _jfieldID=env->GetFieldID(_jclass,"notCreat","Ljava/lang/String");
    //没有这个变量 所以会有异常
}

然后你会发现APP直接挂掉,log日志里显示了异常原因,但是JAVA里还是不能捕获,而且后面的代码也不会执行,也就是一个log都不会打印

解决办法:加上头文件以及内容

#include <sstream>

extern "C"
JNIEXPORT void JNICALL
Java_com_lckiss_ndkgradle_MainActivity_exception(JNIEnv *env, jobject instance) {
    //重新构建一个异常 去获取一个不存在的属性 假定为notCreat,其为String类型
    jclass _jclass= env->GetObjectClass(instance);
    jfieldID _jfieldID=env->GetFieldID(_jclass,"notCreat","Ljava/lang/String;");
    //--没有这个变量 所以会有异常

    //检测异常
    jthrowable _jthrowable=  env->ExceptionOccurred();
    if(_jthrowable!=NULL){
        //为了保证Java能继续运行,需要清除异常
        env->ExceptionClear();
        //补救措施 需要在Act中去创建一个name的String属性 public String name="Test ";
        _jfieldID=env->GetFieldID(_jclass,"name","Ljava/lang/String;");
    }
    jstring _jstring= (jstring) env->GetObjectField(instance, _jfieldID);
    char* str= (char *) env->GetStringUTFChars(_jstring, NULL);

    //以上 就可以正常运行 如果需要java层面需要捕获怎么做?
    if(strcmp(str,"www")!=0){
        //抛出异常 一定要是java的异常
       jclass newException= env->FindClass("java/lang/IllegalArgumentException");
        env->ThrowNew(newException,"非法异常");
    }
}

然后运行就会发现打印了那句话,

结论:需要抛出异常时,自行定义异常并抛出

JNI缓存策略

实际上是说的 对象的生命周期的问题 上例子

Activity中:

public class MainActivity extends AppCompatActivity {
 // Used to load the 'native-lib' library on application startup.
 static {
 System.loadLibrary("native-lib");
 }

 public String name="Test ";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 //测试缓存机制
 for (int i = 0; i <10 ; i++) {
 cached();
 }
 }
 public native void cached();
}

Cpp中:

#include <android/log.h>
#define TAG "jni" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型

extern "C"
JNIEXPORT void JNICALL
Java_com_lckiss_ndkgradle_MainActivity_cached(JNIEnv *env, jobject instance) {

 jclass _jclass = env->GetObjectClass(instance);

 jfieldID _jfieldID = NULL;
 if (_jfieldID == NULL) {
 _jfieldID = env->GetFieldID(_jclass, "name", "Ljava/lang/String;");
 LOGI("-------------GetFieldID------------");
 }
}

然后结果出来后,大吃一惊,按Java的写法,应该是只会打印一次,但是打印了2次,说明了啥?

说明这是一个问题,要解决。。。。解决如下

第一种 使用静态局部变量

extern "C"
JNIEXPORT void JNICALL
Java_com_lckiss_ndkgradle_MainActivity_cached(JNIEnv *env, jobject instance) {
    jclass _jclass = env->GetObjectClass(instance);
//加上static局部关键字就好了。缓存策略还有一种,就是动态加载的时候初始化全局变量
    static jfieldID _jfieldID = NULL;
    if (_jfieldID == NULL) {
        _jfieldID = env->GetFieldID(_jclass, "name", "Ljava/lang/String;");
        LOGI("-------------GetFieldID------------");
    }
}

LOG打印可以看这篇:http://blog.csdn.net/yf210yf/article/details/9305623

第二种 动态加载的时候做成员变量的初始化

Act中:

public static native void initIds();

static {
    System.loadLibrary("native-lib");
    initIds();
}

Cpp中(加入后,将上面的static关键字和去掉后,继续循环cached,PS:这里有一些修改):

jfieldID _jfieldID = NULL;
extern "C"
JNIEXPORT void JNICALL
Java_com_lckiss_ndkgradle_MainActivity_cached(JNIEnv *env, jobject instance) {

    jclass _jclass = env->GetObjectClass(instance);

    if (_jfieldID == NULL) {
        _jfieldID = env->GetFieldID(_jclass, "name", "Ljava/lang/String;");
        LOGI("-------------GetFieldID------------");
    }
}

extern "C"
JNIEXPORT void JNICALL
Java_com_lckiss_ndkgradle_MainActivity_initIds(JNIEnv *env, jclass type) {
//    static jfieldID _jfieldID = NULL;
    if (_jfieldID == NULL) {
        _jfieldID = env->GetFieldID(type, "name", "Ljava/lang/String;");
        LOGI("-------------GetFieldID------------");
    }
}

最后也只会打印一次Log,这种方式在真实的企业开发中用于做成员变量的初始化,细心的同学会发现,这里是jclass而不是jobject,原因是这里是static的native方法。

这部分也可以看这篇文章:https://www.jianshu.com/p/a8e68d4da473

关于NDK与JNI,基础部分就这么多了,产生这些东西的最主要原因还是因为Java的执行效率问题,有时间将最复杂的东西放在底层用C/C++去执行会快很多。

关于AndroidStudio的一些工具以及配置文件

以前没有用Gradle的时候,用的是Eclipse,而Eclipse用的是.mk的文件 .mk文件的构建工具在NDK中是有的,就是NDK目录中的那个ndk-build,现在使用AS就不再需要了,因为被externalNativeBuild替代了。

另外就是一个LLDB的工具

作用呢就是用来调试debug之类的,需要在SDK中下载这个工具包

教程和指令在LLDB官网可以查到。在AS中调试时,与普通Java调试无异,但是需要配合命令进行,比如查看数据值的po命令,po +(变量名)、bt命令查看线程详细信息等...

日常发生崩溃时第三方SO库怎么抓日志

因为第三方SO库是没有源码的,所以不能在AS中直接debug,也就没有控制台,怎么做呢?

首先使用adb进行日志抓取:adb logcat >error.log 拿到路径

打开终端cd到SDK的ndk目录下用

ndk-stack -sym 你的SO库路径 -dump 你的Log日志路径

这样就可以拿到很详细的日志信息,就可以定位到崩溃位置

所有的源码:源码下载 (有多的C币的希望贡献两个,谢谢了,没有的上面的代码基本上都贴完了,也无大碍)

声明:TIL|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA[ZH]协议进行授权

转载:转载请注明原文链接 - NDK与JNI基础-JIN的引用、缓存策略、异常


Life is very interesting. In the end, some of your greatest pains become your greatest strengths.