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;

定义如下

参数含义
nameJava类中的方法名称
signatureJava 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 类型签名
booleanZ
byteB
charC
shortS
intI
longJ
floatF
doubleD
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


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部