Android native方法的动态注册

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)》)

动态注册的优劣

优势

  • 运行效率优于静态注册
  • 可实现取消注册和根据不同情况注册不同的方法,更加灵活
  • 方法不带包名,类名路径等信息方便迁移移植

劣势

  • 签名等书写麻烦而且易错
  • 无法像静态注册一样直接自动生成方法

实际使用哪种注册方式应当综合实际情况考虑

如果大型项目建议还是使用动态注册,扩展性,灵活性,执行效率都高一些

示例源码

DynamicNative

猜你喜欢

转载自blog.csdn.net/dqh147258/article/details/80794202