NDK开发提升性能
NDK开发的优点
- 运行效率高:Android开发的原生代码不需要在Dalvik/ART虚拟机下运行
- 代码安全性高:Java层代码很容易被反编译,而C/C++库反编译难度较大;同时,相对于针对Java代码的混淆保护来说,可以运用当前一切C/C++代码保护技术,如控制流混淆、加壳等
- 易于移植、可复用海量代码库:当使用第三方C/C++开源库时便于移植,可以充分复用C/C++海量代码库
- 等其他优点
NDK开发的缺点
- 开发效率低:和Android的Java世界(Dalvik/ART)交互繁琐,无法直接使用Android系统框架提供的丰富的API
- 开发难度大:对于当前市面的APP开发人员来说熟练掌握JNI开发存在一定困难
- 稳定性难以保证:对开发人员要求高,如存在内存泄露出现频率高、修正bug困难等问题
- 等其他缺点
java函数运行模式
- 纯解释模式下执行
- JIT模式
- 经过dex2oat编译后在quick模式下运行
注意:Android7.0开始结合使用AOT、即时(JIT)编译和配置文件引导型编译。因此一个java函数可能运行在解释模式、JIT或者quick模式。
啥意思:在7.0之前,apk在安装时,会将dex进行优化,将dex中的smali指令转换为原生汇编指令,然后将dex和生成的原生汇编指令存储到oat文件中,具体细节可以使用oatdump命令进行查看odex文件。
ART的运作模式 https://source.android.com/devices/tech/dalvik/configure#how_art_works
分别比较相同代码逻辑的java函数和JNI函数的时间代价
java函数运行模式:
- 在4.4以前的dalvik下运行或者在ART下利用hook禁用掉dex2oat过程强制让其运行在解释模式下
- 使用Android6.0测试java函数在quick模式(运行dex2oat编译以后的汇编代码)
JNI函数实现:
- 和java函数相同的逻辑,纯C/C++实现
- 和java函数相同的逻辑,经过JNI提供的接口频繁调用java函数
public int java_add(int num){
int result = 0;
for(int i=0;i<=num;i++){
result += i;
}
return result;
}
extern "C" JNIEXPORT jint JNICALL
Java_com_showme_myapplication_MainActivity_StringFromJNI2(
JNIEnv* env,
jobject /* this */,jint num) {
int result = 0;
for(int i=0;i<=num;i++){
result = +=i;
}
return result;
}
在纯解释模式下进行测试:在android4.4之前、在android7.0之后第一次运行。JNI是java效率的5倍
在quick模式下进行测试:使用android6进行测试。JNI是java效率的1.5倍左右
java引用数据类型(只有4种)
JNI数据类型 | java数据类型 |
---|---|
jobject | java.lang.Object |
jclass | java.lang.Class |
jstring | java.lang.String |
jthrowable | java.lang.Throwable |
jni引用数据的继承关系
- jobject
- jclass
- jstring
- jarray
- jobjectArray
- jbooleanArray
- jbyteArray
- jcharArray
- jshortArray
- jintArry
- jlongArray
- jfloatArray
- jdoubleArray
- jthrowable
JNIEnv
JNIEnv:指Java Native Interface Environment,是一个JNI接口指针,指向了本地方法的一个函数表,该函数表中的每一个成员指向了一个JNI函数
JNIEnv与JavaVM
JNIEnv 概念 : 是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ;
JNIEnv 与 JavaVM : 注意区分这两个概念;
– JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有一个;
– JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv;
JNIEnv 作用 :
– 调用 Java 函数 : JNIEnv 代表 Java 运行环境, 可以使用 JNIEnv 调用 Java 中的代码;
– 操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用 JNIEnv 来操作这个 Java 对象;
JNIEnv 体系结构
线程相关 : JNIEnv 是线程相关的, 即 在 每个线程中 都有一个 JNIEnv 指针, 每个JNIEnv 都是线程专有的, 其它线程不能使用本线程中的 JNIEnv, 线程 A 不能调用 线程 B 的 JNIEnv;
JNIEnv 不能跨线程 :
– 当前线程有效 : JNIEnv 只在当前线程有效, JNIEnv 不能在 线程之间进行传递, 在同一个线程中, 多次调用 JNI层方法, 传入的 JNIEnv 是相同的;
– 本地方法匹配多JNIEnv : 在 Java 层定义的本地方法, 可以在不同的线程调用, 因此 可以接受不同的 JNIEnv;
JNIEnv 结构 : 由上面的代码可以得出, JNIEnv 是一个指针, 指向一个线程相关的结构, 线程相关结构指向 JNI 函数指针 数组, 这个数组中存放了大量的 JNI 函数指针, 这些指针指向了具体的 JNI 函数;
注意:JNIEnv只在当前线程中有效。本地方法不能将JNIEnv从一个线程传递到另一个线程中。相同的 Java 线程中对本地方法多次调用时,传递给该本地方法的JNIEnv是相同的。但是,一个本地方法可被不同的 Java 线程所调用,因此可以接受不同的 JNIEnv。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vJHnNtTT-1614360457624)(pic/002_001.png)]
jni中的字符串操作
- jstring NewStringUTF(const char* bytes):函数使用给定的C字符串创建一个新的JNI字符串(jstring),不能为构造的java.lang.String分配足够的内存,NewStringUTF会抛出一个OutOfMemoryError异常,并返回一个NULL
- const char* GetStringUTFChars(jstring string , jboolean* isCopy):函数可用于从给定的Java的jstring创建新的C字符串(char*)。如果无法分配内存,在该函数返回NULL。在内存不够时会抛出一个OutOfMemoryError异常,因此每次调用该函数时,都需要对返回值进行检查。在不使用分配的字符串时,需要调用ReleaseStringUTFChars()函数进行内存回收
- jsize GetStringUTFLength(jstring string):用于获取jstring的长度
基本上字符串函数分为两类:char(8字节),jchar(16字节)
* jstring (*NewString)(JNIENv * ,const jchar* jsize); // 创建一个unicode编码字符串,传递的jchar是java中的char类型,占两个字节
* jsize (*GetStringLength)(JNIENv *, jstring); // 返回unicode编码的字符串的长度
* const jchar* (*GetStringChars)(JNIEN *,jstring ,jboolean *);//返回一个unicode编码的jstring的jchar指针
* void (*ReleaseStringChars)(JNIEnv *, jstring, const jchar *);// 释放使用NewString时分配的内存
* jstring (*NewStringUTF)(JNIENv *,const char *);//char为1个字节,utf8编码字符串
* jsize (*GetStringUTFLength)(JNIENv *, jstring);// 返回utf8编码的字符串的字节长度,字符个数
* const char* (*GetStringUTFChars)(JNIENv *,jstring, jboolean *);
* void (*ReleaseStringUTFChars)(JNIENv *,jstring,const char * ); // 释放使用NewStringUTFChars时分配的内存
extern "C" JNIEXPORT jstring JNICALL
Java_com_showme_performancetest01_MainActivity_testjstringapis(
JNIEnv *env,
jobject /* this */,jstring content) {
const char* a=env->GetStringUTFChars(content,nullptr);
int jstring_size=env->GetStringUTFLength(content);
if(a!= nullptr){
__android_log_print(ANDROID_LOG_INFO,"showme", "char content:%s,size:%d", a,jstring_size);
}
env->ReleaseStringUTFChars(content,a);
jstring result=env->NewStringUTF("Hello from jni");
return result;
}