android jni动态注册实例,android jni动态注册

题外话

转眼间2019年也已经接近尾声了,回顾这一年也发生了很多事,换工作、搬家、赶项目、学习新技术等等很多事,忙碌的一年,博客也被放下了,真的是越来越懒了,每次都有一万个不写博客的理由,之前每年至少还要更新几篇文章,然而今年一篇文章也没写,趁着这个周末没事情,抓住2019年的小尾巴,把自己一直想写的文章写了,于是就有了今天这篇文章。

jni简介

JNI是Java Native Interface的缩写,它提供了若干的接口实现了Java和其他语言的通信(主要是c、c++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。jni是Android中java和c++之间连接的桥梁,jni是jvm提供的一种与native方法对接的方式。

jni的注册方式分为静态注册和动态注册两种,之前有一篇文章写过jni相关的知识,那篇文章介绍的关于jni的知识就是静态注册的方式,今天主要写的是jni的动态注册。静态注册和动态注册, 两者优缺点如下:

静态注册

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

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

动态注册

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

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

静态注册

开始jni动态注册之前,可以先来回顾一下静态注册的流程:

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

在对应路径命令行下输入 javac xxx.java 生成xxx.class文件,然后在src目录下通过 javah xxx.class 生成 com_xxx_xxx.h 头文件

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

编写C/C++源代码 并把刚拷贝的头文件包含进去 ,复制头文件中函数的定义部分,并实现其中的你想要的功能,然后编写Android.mk Application.mk(ndk-build编译方式,cmake方式可以不写)

ndk-build编译方式在命令行中进入jni目录,输入ndk-build 即可生产对应so库,cmake方式build一下项目便会生成对应的so库,库文件会自动放在libs文件夹下 至此就可以运行程序了

动态注册

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

编写Java端的相关native方法

编写C/C++代码, 根据Java文件路径和方法签名确定JNINativeMethod,实现JNIEXPORT int JNICALL JNI_OnLoad(JavaVM *vm,void *reserved)方法

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

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

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

相关实例代码如下:

定义jni工具类,里面定义native方法public class JniUtil {

// Used to load the 'native-lib' library on application startup.

static {

System.loadLibrary("native-lib");

}

/**

* A native method that is implemented by the 'native-lib' native library,

* which is packaged with this application.

*/

public native String stringFromJNI();

public native String dynamicMethod();

}

在对应的native-lib中实现定义的本地方法#include

#include

#define JNI_JAVA_CLASS "com/example/dynamicjnidemo/JniUtil"

//静态注册对应本地方法

extern "C"

JNIEXPORT jstring JNICALL

Java_com_example_dynamicjnidemo_JniUtil_stringFromJNI(JNIEnv *env, jobject thiz) {

std::string hello = "jniUtil,Hello from C++";

return env->NewStringUTF(hello.c_str());

}

//动态注册对应本地方法

extern "C"

JNIEXPORT jstring JNICALL

defineDynamicMethod(JNIEnv *env, jobject thiz) {

return env->NewStringUTF("dynamic register jni method from jni");

}

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

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

参数:

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

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

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

*/

static JNINativeMethod all_methods[] = {

// 多个方法依次都好分割写在这里

{"dynamicMethod", "()Ljava/lang/String;", (void *) defineDynamicMethod},

};

//必须实现的回调函数

JNIEXPORT int JNICALL JNI_OnLoad(JavaVM *vm, void *unused) {

JNIEnv *env;

// 获取JNIEnv

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

return JNI_ERR;

}

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

jclass java_class = env->FindClass(JNI_JAVA_CLASS);

if (java_class == NULL) {

return JNI_ERR;

}

int method_count = sizeof(all_methods) / sizeof(all_methods[0]);

// 注册对应的本地方法 参数:java类 所要注册的函数数组 注册函数的个数

// registerNatives ->registerNativeMethods ->env->RegisterNatives

if (env->RegisterNatives(java_class, all_methods, method_count) < 0) {

return JNI_ERR;

}

// 返回jni 的版本

return JNI_VERSION_1_6;

}

在需要地方调用package com.example.dynamicjnidemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private JniUtil jniUtil = new JniUtil();

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

// Example of a call to a native method

TextView tv = findViewById(R.id.sample_text);

tv.setText(jniUtil.stringFromJNI());

TextView tvDynamic = findViewById(R.id.textViewDynamic);

tvDynamic.setText(jniUtil.dynamicMethod());

}

}

最终效果如下图:

6140ac9056b9767218c23c6735a907a0.png

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

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

Java方法签名

关于查询Java方法签名,可以使用javap -s命令查询对应的class文件(先编译出对应的class文件,再查询),如果方法签名错了,编译能通过,但运行时会报NoSuchMethod异常。动态注册中 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

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

总结:

一般来说,静态注册书写简单,但是由于方法名的对应关系,所以耦合性强,如果包名发生变化,那么需要大量修改,而且初次运行的效率也不如动态注册,所以动态注册无疑是注册函数的更好方式, 唯一要注意的是注册函数时, 需要额外小心, 别把类名,函数名和签名写错了, 不然loadLibraries时会导致应用Crash,能力有限就先写这么多,后面再补充。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值