Android native方法的动态注册
目录
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 27
defaultConfig {
//........................
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
//............................
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
在app
目录下也会生成一个CmakeLists.txt
的文件,这个文件中定义了源文件路径,依赖的库,生成库的名称等信息.
默认的CmakeLists.txt
指定了app/src/main/cpp/native-lib.cpp作为源代码文件
打开这个文件如下
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring
JNICALL
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)》)
动态注册的优劣
优势
- 运行效率优于静态注册
- 可实现取消注册和根据不同情况注册不同的方法,更加灵活
- 方法不带包名,类名路径等信息方便迁移移植
劣势
- 签名等书写麻烦而且易错
- 无法像静态注册一样直接自动生成方法
实际使用哪种注册方式应当综合实际情况考虑
如果大型项目建议还是使用动态注册,扩展性,灵活性,执行效率都高一些