先上一张图,同时此文章需要 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币的希望贡献两个,谢谢了,没有的上面的代码基本上都贴完了,也无大碍)
本站由以下主机服务商提供服务支持:
0条评论