一、概念
1.1 ndk(原生开发套件)是一套工具,能够让开发者在Android应用中使用c和c++代码。
1.2 使用场景。
★进一步提升设备性能,以降低延迟,或运行计算密集型应用,如游戏或物理模拟
★重复使用自己过其他开发者的c和c++库
**
二、编译方式
**
2.1 ndkbuild ,as2.2之后,默认Cmake为编译构建工具。ndkbuild,需要的配置文件:android.mk, application.mk ,这种编译方式较为传统,必须遵守Java-包名-类名-方法名(JniEnv *env, jobect jobj,参数……)
在gradle中配置:
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
此外,还可以android->defaultConfig 中配置加载动态库的平台
ndk{
abiFilters "armeabi-v7a"……等
}
2.2 cmake, 这种方式也是as默认的构建编译方式。相比于ndkbuild,可以不遵循复杂的命名方式,但需与Java类名保持一致。需要的配置文件: CMakeLists.txt
在gradle中配置:
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
配置加载动态库的平台:
externalNativeBuild {
cmake {
abiFilters "armeabi-v7a"
cppFlags ""
}
}
**
三、注册方式
**
3.1 静态注册
1. 原理: 根据函数名来建立 java 方法与 JNI 函数的一一对应关系。通过 JNIEXPORT 和 JNICALL 两个宏定义声明,在虚拟机加载 so 时发现上面两个宏定义的函数时就会链接到对应的 native 方法,找到对应的方法时,保存jni对应函数的指针。
2. 命名规则:Java + 包名 + 类名 + 方法名,一般使用javah 编译.class 文件生成头文件,再进行对相应的方法实现。
// Java native 方法
public native String stringFromJNI();
// JNI 对应的方法
JNIEXPORT jstring JNICALL
Java_com_afei_jnidemo_MainActivity_stringFromJNI( JNIEnv *env, jobject instance);
3.优点: 明了
4.缺点:
*方法命名过长
*必须遵循命名规则
*首次调用native 函数时需要根据函数名字搜索对应的Jni层函数来建立关联关系,这样会影响运行效率。
3.2 动态注册
1.原理: 当java层通过System.loadLibrary 加载完JNI动态库后,紧接着会查找该库中一个叫JNI_OnLoad的函数,并在JNI_OnLoad中通过 RegisterNatives 方法手动完成 native 方法和 so 中的方法的绑定, 完成动态注册。
#include <jni.h>
#include <string>
#include "log.hpp"
extern "C" {
jstring stringFromJNI(JNIEnv *env, jobject instance) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
jint add(JNIEnv *env, jclass clazz, jint a, jint b) {
return a + b;
}
jint RegisterNatives(JNIEnv *env) {
jclass clazz = env->FindClass("com/afei/jnidemo/MainActivity");
if (clazz == NULL) {
LOGE("con't find class: com/afei/jnidemo/MainActivity");
return JNI_ERR;
}
JNINativeMethod methods_MainActivity[] = {
{"stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI},
{"add", "(II)I", (void *) add}
};
// int len = sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]);
return env->RegisterNatives(clazz, methods_MainActivity,
sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));
}
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
jint result = RegisterNatives(env);
LOGD("RegisterNatives result: %d", result);
return JNI_VERSION_1_6;
}
}
2.JNINativeMethod
typedef struct {
const char* name; // native 的方法名
const char* signature; // 方法签名,例如 ()Ljava/lang/String;
void* fnPtr; // 函数指针
} JNINativeMethod;
四、数据类型转换
- 基本数据类型
基本数据类型可以直接与C/C++的相应基本数据类型映射,JNI用类型定义使得这种映射对开发人员透明
Java类型 | JNI类型 | C/C++类型 | 大小 |
---|---|---|---|
Boolean | Jblloean | unsigned char | 无符号8位 |
Byte | Jbyte | char | 有符号8位 |
Char | Jchar | unsigned short | 无符号16位 |
Short | Jshort | short | 有符号16位 |
Int | Jint | int | 有符号32位 |
Long | Jlong | long long | 有符号64位 |
Float | Jfloat | float | 32位 |
Double | Jdouble | double | 64位 |
2 引用数据类型
与基本数据类型不同,引用类型对原生方法是不透明的,它们内部的数据结构并不直接向原生代码公开
Java类型 | 原生类型 |
---|---|
java.lang.Class | jclass |
java.lang.Throwable | jthorwable |
java.lang.String | jstring |
Other objects | jobjects |
java.lang.Object[] | jobjectArray |
boolean[] | jbooleanArray |
byte[] | jbooleanArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double[] | jdoubleArray |
Other arrays | Jarray |