Android native方法的动态注册
Android native方法的动态注册
目录
- Android native方法的动态注册
- 目录
- Android JNI简介
- Android JNI的一般注册方法
- Android JNI的动态注册
- JNINativeMethod结构体
- Java类映射的方法签名
- 基本类型签名
- 数组的签名
- Java类的签名
- Java内部类的签名
- 注册
- 两种注册方式的优劣
- 静态注册的优劣
- 优势
- 劣势
- 动态注册的优劣
- 优势
- 劣势
- 静态注册的优劣
- 示例源码
Android JNI简介
java的JNI(java native interface)是用于java调用底层C/C++代码的,在Android中,同样也有JNI的调用方法
在以前,使用的是Ndk来编译
而现在Android的标准开发工具由eclipse转向Android Studio后,Android Studio不仅支持NDK,而且增加了Cmake编译的支持
至于Ndk和Cmake,在此篇中不多作介绍
Android JNI的一般注册方法
以Android Studio自带模板为例子
新建项目时勾选include C++ support生成一个Empty Activity
在Android Studio会在MainActivityJava中的一个类里声明一个native方法
public native String stringFromJNI();
这声明很像接口或者说是抽象类的方式.
在app下的build.gradle也会生成Cmake的配置
android {compileSdkVersion 27defaultConfig {//........................externalNativeBuild {cmake {cppFlags ""}}}//............................externalNativeBuild {cmake {path "CMakeLists.txt"}}
}
在app目录下也会生成一个CmakeLists.txt的文件,这个文件中定义了源文件路径,依赖的库,生成库的名称等信息.
默认的CmakeLists.txt指定了app/src/main/cpp/native-lib.cpp作为源代码文件
打开这个文件如下
#include
#include extern "C" JNIEXPORT jstringJNICALL
Java_com_yxf_dynamicnative_MainActivity_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}
Java_com_yxf_dynamicnative_MainActivity_stringFromJNI()这个方法便是在MainActivity中native方法stringFromJNI()方法的实现
这个方法很长,因为java层调用就是依据这个这个函数名称来寻找到这个native方法的,方法的结构是这样的Java_包名_类名_方法名,Java固定,包名的”.”,用下划线代替.
Android JNI的动态注册
说完了一般的注册方法,接下来介绍此篇的重点–JNI的动态的注册
为了方便对比,依然使用上面自动生成的代码
在MainActivity中再添加一个新的native方法
public native String getRepeatString(String string,int repeatCount);
然后在native-lib.cpp中添加如下几个方法
static void logD(const char *str) {__android_log_write(ANDROID_LOG_DEBUG, str, TAG);
}static void logE(const char *str) {__android_log_write(ANDROID_LOG_ERROR, str, TAG);
}static bool registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods) {jclass clazz;clazz = env->FindClass(className);if (clazz == NULL) {logE("the class we found is null , class name : ");logE(className);return false;}if (methods == NULL) {logE("the methods is null");return false;}int methodCount = sizeof(*methods) / sizeof(methods[0]);int result = env->RegisterNatives(clazz, methods, methodCount);char message[1024];sprintf(message, "result : %d", result);logD(message);if (result == JNI_OK) {return true;} else {return false;}
}
前两个方法只是为了打Log而已不多做介绍
Jni的动态注册关键便是env->RegisterNatives,在此对这个方法做了一层装封,装封成registerNativeMethods
env->RegisterNatives方法有3个参数,分别为含有此jni方法的jclass的引用,方法结构体数组,方法数.
JNINativeMethod结构体
在此重点说下其中的方法结构体JNINativeMethod
此结构体定义如下
typedef struct {const char* name;const char* signature;void* fnPtr;
} JNINativeMethod;
定义如下
| 参数 | 含义 |
|---|---|
| name | Java类中的方法名称 |
| signature | Java native 方法的方法签名 |
| fnPtr | 和Java native方法对接的C/C++方法的指针 |
先创建getRepeatString的实现方法
static jstring getRepeatString(JNIEnv *env, jobject obj, jstring string_, jint repeatCount) {using namespace std;const char *str = env->GetStringUTFChars(string_, 0);string cStr = string(str);string result;for (int i = 0; i < repeatCount; ++i) {result = result + cStr;}env->ReleaseStringUTFChars(string_, str);return env->NewStringUTF(result.c_str());
}
然后生成这个方法结构体数组
static JNINativeMethod methods[] = {//Java Invoke C.{"getRepeatString", "(Ljava/lang/String;I)Ljava/lang/String;", (void *) getRepeatString},
};
Java类映射的方法签名
在此再简介下Java类映射的方法签名(方法描述符)
方法签名由参数和返回值的参数类型签名组成,其中小括号中的即为方法的参数签名组合,括号右边的为返回值类型签名.
基本类型签名
基本类型签名映射如下
| Java 类型 | 签名 |
|---|---|
| boolean | Z |
| byte | B |
| char | C |
| short | S |
| int | I |
| long | J |
| float | F |
| double | D |
| type[] | [type |
数组的签名
对最后一个type[] 做下说明
数组类型的签名是签名映射后前面加一个[符号
举例
int[] –> [I
double[] –>[D
Java类的签名
对于非基本类型的Java类,其签名是前面加一个L,中间跟包名路径,不过包名不能以.作为分隔,而需要用/分隔,然后最后加;
以String举例
String 类全名是java.lang.String映射成Jni的签名便是Ljava/lang/String;
如果是String数组则是[Ljava/lang/String;
Java内部类的签名
内部类比较特殊,它的签名则是
L + 外部类包名路径 + 外部类名称 + $ + 内部类名称 + ;
注册
实现方法,方法结构体,注册方法都有了,接下来就是注册了
Jni方法的注册一般是放在JNI_OnLoad方法中的,一般JNI_OnLoad方法会在
System.loadLibrary("native-lib");
方法调用时被调用
在native-lib.cpp方法中添加classPath常量
static const char *classPath = "com/yxf/dynamicnative/MainActivity";
在native-lib.cpp方法中添加JNI_OnLoad方法如下
jint JNI_OnLoad(JavaVM *vm, void *reserved) {//获得JNIEnv对象void *env = NULL;if (vm->GetEnv(&env, JNI_VERSION_1_6) != JNI_OK) {logE("get JNIEnv failed ,register methods failed");return -1;}//注册if (registerNativeMethods((JNIEnv *)env,classPath,methods) != JNI_TRUE) {logE("register methods failed");} else {logD("register methods successfully");}return JNI_VERSION_1_6;
}
好了,动态注册的方法就算结束了
运行调用方法会发现成功了,在此不再贴图,若想查看效果可自行下载源码运行.
两种注册方式的优劣
静态注册的优劣
优势
- 如果使用Cmake + Android Studio 来开发,现在静态注册已经可以使用Alt + Enter自动生成方法名了,可以说已经很方便了.
- 书写没有动态注册麻烦
劣势
- 如果使用NDK或者Eclipse老的工具,书写还是比较麻烦,而且容易写错,传统的方式喜欢加一个头文件,书写头文件也是件很麻烦的事情,当然写头文件也算是一种良好的习惯.
- 初次调用native函数时要根据函数名字搜索对应用JNI层函数来建立关联关系,这样会影响运行效率(摘自《深入理解Android(卷1)》)
动态注册的优劣
优势
- 运行效率优于静态注册
- 可实现取消注册和根据不同情况注册不同的方法,更加灵活
- 方法不带包名,类名路径等信息方便迁移移植
劣势
- 签名等书写麻烦而且易错
- 无法像静态注册一样直接自动生成方法
实际使用哪种注册方式应当综合实际情况考虑
如果大型项目建议还是使用动态注册,扩展性,灵活性,执行效率都高一些
示例源码
DynamicNative
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
