NDK是让开发者能在Android应用中使用C/C++代码的一套开发工具。
-
运行效率高
将C/C++源代码直接编译成机器码 -
代码安全性好
APK的Java层代码很容易被反编译,而C/C++库 反编译难度大 -
跨平台
用C/C++写得库可以方便在其他的嵌入式平台上再次使用
核心概念
- 本地共享库:以扩展名.so构建的库,在运行时是共享和动态链接的
- 本地静态库:以扩展名.a构建的库,这类库实际上是在编译时静态链接的
- JNI:将Java中对native方法的调用引导到C/C++编写的本地库中
- ABI:定义二进制接口交互规则,允许编译的机器码运行在不同的CPU架构上
- Android.mk:告诉ndk-build脚本模块及名称的定义,以及需要编译的源文件还有需要链接的库
- Application.mk:可选文件。用于列出应用程序所需的模块。包含abi,工具链和标准库等信息
JNI
连接Java世界和C/C++世界的桥梁
注册
将Java层的native函数与JNI层对应的实现函数关联起来,这样在调用java层的native函数时,就能顺利转到JNI层对应的函数执行。
- 静态注册
根据函数名来找对应的JNI函数,需要javah参与,必须遵循方法命名规则
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_jni_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
- 动态注册
使用数据结构JNINativeMethod来记录Java native函数和JNI函数的对应关系。必须实现JNI_OnLoad方法,并在里面完成注册
#include <jni.h>
#include <string>
jstring stringFromJNI(JNIEnv *env, jobject) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
static const JNINativeMethod gMethods[] = {
{"stringFromJNI", "()Ljava/lang/String;", (jstring *) stringFromJNI}
};
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
jclass clazz = env->FindClass("com/demo/jni/MainActivity");
if (!clazz) {
return -1;
}
if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) {
return -1;
}
return JNI_VERSION_1_6;
}
JNIEnv
JNIEnv是线程相关的结构体,该结构体代表了Java在本线程的执行环境。JNIEnv可以用来调用Java中的函数以及操作Java中的变量/对象,但是只能在本线程中生效,不能传递到另一个线程。那么,如果在其他线程中也需要使用呢?
JavaVM
JavaVM是虚拟机在JNI层的代理,全进程唯一。JNI_OnLoad方法传入,可以通过调用其方法AttachCurrentThread关联线程获取JNIEnv对象。可以在线程的任何地方通过方法GetEnv获取JNIEnv,前提是先调用AttachCurrentThread进行关联。最后可以调用DetachCurrentThread解除关联,释放资源。
特性应用
参数
可以传入任何类型的参数到native方法中
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_jni_MainActivity_concat(JNIEnv *env, jobject thiz, jstring words) {
char *buf = NULL;
jclass strclass = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("GB2312");
jmethodID mid = env->GetMethodID(strclass, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barray = (jbyteArray) env->CallObjectMethod(words, mid, strencode);
jsize len = env->GetArrayLength(barray);
jbyte *bp = env->GetByteArrayElements(barray, JNI_FALSE);
if (len > 0) {
buf = (char *) malloc(len + 1); //"\0"
memcpy(buf, bp, len);
buf[len] = 0;
}
char *hello = "Hello";
strcat(hello, buf);
env->ReleaseByteArrayElements(barray, bp, 0);
return env->NewStringUTF(hello);
}
方法
JNIEnv提供了很多方法调用
extern "C" JNIEXPORT jobject JNICALL
Java_com_demo_jni_MainActivity_createUser(JNIEnv *env, jobject thiz, jstring name, jint age) {
jclass userclass = env->FindClass("com/demo/jni/User");
jobject newUser = env->AllocObject(userclass);
jfieldID nameField = env->GetFieldID(userclass, "name", "Ljava/lang/String;");
jfieldID ageField = env->GetFieldID(userclass, "age", "I");
env->SetObjectField(newUser, nameField, name);
env->SetIntField(newUser, ageField, age);
return newUser;
}
基本类型映射
Java Type | Native Type | Description | Type Signature |
---|---|---|---|
boolean | jboolean | unsigned 8 bits | Z |
byte | jbyte | signed 8 bits | B |
char | jchar | unsigned 16 bits | C |
short | jshort | signed 16 bits | S |
int | jint | signed 32 bits | I |
long | jlong | signed 64 bits | J |
float | jfloat | 32 bits | F |
double | jdouble | 64 bits | D |
void | void | N/A | V |
引用类型映射
Java Type | Native Type | Type Signature |
---|---|---|
java.lang.Object | jobject | Lfully-qualified-class ; |
java.lang.Class | jclass | Ljava/lang/Class; |
java.lang.String | jstring | Ljava/lang/String; |
java.lang.Object[] | jobjectArray | [Lfully-qualified-class ; |
boolean[] | jbooleanArray | [Z |
byte[] | jbyteArray | [B |
char[] | jcharArray | [C |
short[] | jshortArray | [S |
int[] | jintArray | [I |
long[] | jlongArray | [J |
float[] | jfloatArray | [F |
double[] | jdoubleArray | [D |
java.lang.Throwable | jthrowable | Ljava/lang/Throwable; |
引用
局部引用
- 通过NewLocalRef和各种JNI方法创建(如FindClass、NewObject等)
- 阻止GC回收
- 本地方法返回会自动回收所引用的对象或者通过手动调用DeleteLocalRef回收
- 线程相关,只能在创建它的线程里使用,通过全局变量缓存并使用在其他线程是不合法的
- 避免使用静态变量缓存局部引用
全局引用
- 通过NewGlobalRef创建
- 阻止GC回收
- 必须手动调用DeleteGlobalRef释放,否则一直有效
- 全局引用可以跨方法、跨线程使用
- 可以使用静态变量来缓存
弱全局引用
- 通过NewWeakGlobalRef创建
- 使用DeleteGlobalWeakRef来释放
- 失效之前可以跨方法多次使用,也可以在多线程中使用
- 无法保证所引用的对象不被GC回收
- 使用IsSameObject检查
- 可缓存
图解原理
感谢大家的支持,如有错误请指正,如需转载请标明原文出处!