android 函数名注册,Android JNI 函数注册的两种方式(静态注册/动态注册)

JNI/NDK

在Android开发中,由于种种原因我们需要调用C/C++代码, 这个时候就要用到Android开发者都听说过的JNI(Java Native Interface)了, 在调用JNI相关方法之前, 要对java中native关键字定义的方法进行注册, 注册方式有两种: 静态注册和动态注册, 两者优缺点如下:

静态注册

优点: 理解和使用方式简单, 属于傻瓜式操作, 使用相关工具按流程操作就行, 出错率低

缺点: 当需要更改类名,包名或者方法时, 需要按照之前方法重新生成头文件, 灵活性不高

动态注册

优点: 灵活性高, 更改类名,包名或方法时, 只需对更改模块进行少量修改, 效率高

缺点: 对新手来说稍微有点难理解, 同时会由于搞错签名, 方法, 导致注册失败

静态注册

此注册方法是初学者经常用到的, 比较常见, 这里简单说下流程,

1.编写一个java类,在里面加载对应的so库并且通过native关键字定义需要调用的函数

package com.example.wenzhe.myjni;

/**

* Created by wenzhe on 16-1-27.

*/

public class JniTest {

public native int getRandomNum();

public native String getNativeString();

static {

System.loadLibrary("HelloJni");

}

}

2.在命令行下输入 javac JniTest.java 生成JniTest.class文件

然后在src目录下通过 javah com.example.wenzhe.myjni.JniTest 生成 com_example_wenzhe_myjni_JniTest.h 头文件

3.将头文件拷贝到jni目录下(eclipse在src同级目录建立文件夹,Android studio 在java同级目录建立文件夹)

4.编写C/C++源代码 并把刚拷贝的头文件包含进去 ,复制头文件中函数的定义部分,并实现其中的你想要的功能

然后编写Android.mk Application.mk(Application.mk主要用来定义适应的平台,x86 arm等)

Android.mk如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := HelloJni

LOCAL_SRC_FILES := HelloJni.cpp

include $(BUILD_SHARED_LIBRARY)

Application.mk如下:

#支持标准C++特性

APP_STL := gnustl_static

APP_CPPFLAGS := -frtti -fexceptions

#支持的CPU架构

APP_ABI := armeabi-v7a

#Android 版本

APP_PLATFORM := android-22

include $(BUILD_SHARED_LIBRARY)

其中LOCAL_MODULE定义的名字就是生成的so库名字,so库前面都会有个lib前缀,上面生产的so应该为 libHelloJni.so

5.在命令行中进入jni目录,输入ndk-build 即可生产对应so库,会自动放在libs文件夹下 至此就可以运行程序了

动态注册

动态注册基本思想是在JNI_Onload()函数中通过JNI中提供的RegisterNatives()方法来将C/C++方法和java方法对应起来(注册), 我们在调用 System.loadLibrary的时候,会在C/C++文件中回调一个名为 JNI_OnLoad ()的函数,在这个函数中一般是做一些初始化相关操作, 我们可以在这个方法里面注册函数, 注册整体流程如下:

编写Java端的相关native方法

编写C/C++代码, 实现JNI_Onload()方法

将Java 方法和 C/C++方法通过签名信息一一对应起来

通过JavaVM获取JNIEnv, JNIEnv主要用于获取Java类和调用一些JNI提供的方法

使用类名和对应起来的方法作为参数, 调用JNI提供的函数RegisterNatives()注册方法

示例代码如下:

// jni头文件

#include

#include

#include

#include

using namespace std;

//native 方法实现

jint get_random_num(){

return rand();

}

/*需要注册的函数列表,放在JNINativeMethod 类型的数组中,

以后如果需要增加函数,只需在这里添加就行了

参数:

1.java中用native关键字声明的函数名

2.签名(传进来参数类型和返回值类型的说明)

3.C/C++中对应函数的函数名(地址)

*/

static JNINativeMethod getMethods[] = {

{"getRandomNum","()I",(void*)get_random_num},

};

//此函数通过调用RegisterNatives方法来注册我们的函数

static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){

jclass clazz;

//找到声明native方法的类

clazz = env->FindClass(className);

if(clazz == NULL){

return JNI_FALSE;

}

//注册函数 参数:java类 所要注册的函数数组 注册函数的个数

if(env->RegisterNatives(clazz,getMethods,methodsNum) < 0){

return JNI_FALSE;

}

return JNI_TRUE;

}

static int registerNatives(JNIEnv* env){

//指定类的路径,通过FindClass 方法来找到对应的类

const char* className = "com/example/wenzhe/myjni/JniTest";

return registerNativeMethods(env,className,getMethods, sizeof(getMethods)/ sizeof(getMethods[0]));

}

//回调函数

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){

JNIEnv* env = NULL;

//获取JNIEnv

if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {

return -1;

}

assert(env != NULL);

//注册函数 registerNatives ->registerNativeMethods ->env->RegisterNatives

if(!registerNatives(env)){

return -1;

}

//返回jni 的版本

return JNI_VERSION_1_6;

}

上面的代码就能实现动态注册JNI了 以后要增加函数只需在java文件中声明native方法,在C/C++文件中实现,

并在getMethods数组添加一个元素并指明对应关系,通过ndk-build 生成so库就可以运行了

其中JNI版本可以在jni.h头文件中去查看支持哪些版本,一般定义在文件最后几行

JNI 签名

动态注册中 JNINativeMethod 结构体中第二个参数需注意

括号内代表传入参数的签名符号,为空可以不写,括号外代表返回参数的签名符号,为空填写 V,对应关系入下表

签名符号

C/C++

java

V

void

void

Z

jboolean

boolean

I

jint

int

J

jlong

long

D

jdouble

double

F

jfloat

float

B

jbyte

byte

C

jchar

char

S

jshort

short

[Z

jbooleanArray

boolean[]

[I

jintArray

int[]

[J

jlongArray

long[]

[D

jdoubleArray

double[]

[F

jfloatArray

float[]

[B

jbyteArray

byte[]

[C

jcharArray

char[]

[S

jshortArray

short[]

L完整包名加类名;

jobject

class

举个例子:

传入的java参数有两个 分别是 int 和 long[] 函数返回值为 String 即函数的定义为:String getString(int a ,long[] b)

签名就应该是 :"(I[J)Ljava/lang/String;"(不要漏掉英文分号)

如果有内部类 则用 $ 来分隔 如:Landroid/os/FileUtils$FileStatus;

总结

当熟悉动态注册后, 动态注册无疑是注册函数的更好方式, 唯一要注意的是注册函数时, 需要额外小心, 别把类名,函数名和签名写错了, 不然loadLibraries时会导致应用Crash, 关于JNI部分知识, 我还会写一篇关于如何高效传递数据以及JNI开发过程中的一些坑的总结.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值