IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 边实验边分析-NDK篇-JNI使用RegisterNatives进行显式方法注册 -> 正文阅读

[移动开发]边实验边分析-NDK篇-JNI使用RegisterNatives进行显式方法注册

什么是JNI

JNI指的是Java Native Interface,即JAVA原生接口,它的作用是定义了JAVA代码与C/C++的代码进行互动的方式,从而使得Java能够调用c/c++代码,也可以使得c/c++能够调用java的方法(回调),方便原生开发,严格来说,其实是使用 Java 或 Kotlin 编程语言编写的字节码与原生代码的互动,所以Kotlin也是完全支持的

JNI在Android中的使用方式

配置c/c++编译环境

由于本文并不是主要说明如何配置Native的开发环境,且官网说明也比较详细,故配置过程可以参考官网的说明进行https://developer.android.google.cn/studio/projects/add-native-code,这边不做另外说明

注意,本文会采用CMake的编译方式进行接下来的说明

加载

JAVA层需要通过System.loadLibrary方法来加载so库,通过在静态初始化方法中调用
JAVA调用方式

JAVA

public class MainNativeInterface {
    static {
        System.loadLibrary("native-lib");
    }
}

Kotlin

class MainNativeInterfaceKotlin {

    companion object {
        init {
            System.loadLibrary("native-lib")
        }
    }
}

注意这边的Kotlin写法上,我们使用了companion object里面来执行init,这样最终翻译出来的Java代码会是static静态代码块,如果init放在外层,则Java代码会变成在该类的构造方法中调用。官方推荐做法也是在静态初始化方法中调用,这样可以提前加载

注册Native方法

一般的说法上,我们会把Native方法区分成为静态的注册方式和动态的注册方式2种,但是无论是静态的注册还是动态的注册方式,Native的方法都是在运行时进行注册绑定的,静态注册的方式JNI层会在方法调用的时候去查找符合相应规则的方法名进行绑定,而动态注册则会在JNI进行加载动态库的时候就完成所有方法的绑定,所以其实可以理解为静态注册是隐式的被动注册方式,而动态注册则是显示的主动注册方式,下面我们来看一下这两种方式的具体做法,就能发现其显示、隐式,主动、被动的区别

静态注册-隐式被动

该方式通过JNI替我们完成注册的过程,我们只需要按一定的方法名称规则配置好我们的方法名即可,先来看一下Java层的写法

JAVA

public class MainNativeInterface {
    static {
        System.loadLibrary("native-lib");
    }

    public native static String stringFromJNI();
}

Kotlin

class MainNativeInterface {

    companion object {
        init {
            System.loadLibrary("native-lib")
        }

        @JvmStatic
        external fun stringFromJNI(): String
    }
}

我们定义了一个stringFromJNI方法,该方法为native的方法,并且这边是一个static的方法

注:Kotlin版本为了对应Java的版本,我们使用了JvmStatic注解来表明其是一个static的方法,并且其是直接属于MainNativeInterface这个类的,这边可能会有同学疑惑,为什么companion
object不行,大家可以尝试去除它,然后看其翻译的Java版本的代码就一目了然了(Tools->Kotlin->showKotlinBytecode,
Decompile),原来Kotlin里面的companion
object会创建一个静态的Companion类,然后将该方法作为该静态类的内部方法,这样做无疑对于JNI在进行方法查询的绑定的时候会产生影响,我们在写JNI层方法名称的时候,只会写包名+类名+方法名,而类名我们只会写MainNativeInterface,所以会导致绑定失败
java.lang.NoSuchMethodError: no static or non-static method

然后我们在cpp目录下有个native-lib.cpp文件,在里面我们定义对应的native层的方法

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_nativeevaldemo_MainNativeInterface_stringFromJNI(JNIEnv *env, jclass thiz) {
    return env->NewStringUTF("native auto search method bind");
}

这样,我们就完成了方法的隐式被动注册,使用起来也十分方便,因为我们声明了static的方法,所以直接调用该静态native方法即可,当然你也可以使用非静态的方法

JAVA

public class MainNativeInterface {
    static {
        System.loadLibrary("native-lib");
    }

    public native String stringFromJNI();
}

Kotlin

class MainNativeInterface {

    companion object {
        init {
            System.loadLibrary("native-lib")
        }
    }
    
	external fun stringFromJNI(): String
}

JNI层需要修改该方法传递进来的最后一个参数为jobject

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_nativeevaldemo_MainNativeInterface_stringFromJNI(JNIEnv *env, jobject thiz) {
    return env->NewStringUTF("native auto search method bind");
}

在使用上,非静态方法就会有点费劲了,需要先new出来这个class再进行使用咯

显式主动注册

该方式需要我们自己去主动向JVM注册绑定Native方法
Java层我们不需要变,我们来看一下JNI层的写法,在native-lib.cpp下创建一个JNI_OnLoad方法

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }

    // Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
    jclass c = env->FindClass("com/example/nativeevaldemo/MainNativeInterface");
    if (c == nullptr) return JNI_ERR;

    // Register your class' native methods.
    static const JNINativeMethod methods[] = {
            {"stringFromJNI", "()Ljava/lang/String;", (void*) stringFromJNI}
    };
    int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod));
    if (rc != JNI_OK) return rc;

    return JNI_VERSION_1_6;
}

static jstring JNICALL
stringFromJNI(JNIEnv *jenv, jclass ths){
    return jenv->NewStringUTF("native hand method bind");
}

这边需要注意的是

  • FindClass里面,我们需要使用"/"来代替“.”,来指定我们的class完整的包路径
  • 绑定的方法一定都要在你的Class里面找得到,不然会报错
  • methods数组中的方法签名需要注意要对应Java层的方法,具体规则可以查看https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html
  • 最后返回想使用的JNI的版本

这样我们就完成了方法的绑定了

这两种方式推荐使用动态绑定的方式,因为这种方式更加灵活,而且效率更高,唯一的缺点就是需要写的代码比静态的方式要多点吧

Callback的注册方式

接下来我们来示例一种callback的方式

在Java层的MainNativeInterface类增加一个方法,并增加callback的interface


public interface JNICallback {
        void result(String message);
    }

public native void callAsyncMethod(JNICallback callback);

JNI层onLoad方法中动态绑定方法

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    javaVM = vm;
    // Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
    jclass c = env->FindClass("com/example/nativeevaldemo/MainNativeInterface");
    if (c == nullptr) return JNI_ERR;

    // Register your class' native methods.
    static const JNINativeMethod methods[] = {
            {"stringFromJNI", "()Ljava/lang/String;", (void*) stringFromJNI},
            //新增该方法签名,注意这边的参数为我们定义的interface
            {"callAsyncMethod", "(Lcom/example/nativeevaldemo/MainNativeInterface$JNICallback;)V", (void*) callAsyncMethod}
    };
    int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod));
    if (rc != JNI_OK) return rc;

    return JNI_VERSION_1_6;
}

//增加真正绑定的c++方法
static void
callAsyncMethod(JNIEnv *env, jclass thiz, jobject callback) {
    jclass clazz = env->GetObjectClass(callback);
    jmethodID jmid = env->GetMethodID(clazz, "result", "(Ljava/lang/String;)V");

    env->CallVoidMethod(callback, jmid, env->NewStringUTF("this is callback result message"));
}

callAsyncMethod方法中,可以通过传入的callback的object取得class类(即传入进来的接口的实现类),然后找到class类里面的result方法的Id,然后通过callxxxMethod方法调用

使用上

//使用lambda会很简单
MainNativeInterface.callAsyncMethod {
	Log.d("NativeTestActivity", "result message == $it")
}

调用callAsyncMethod 方法输出
在这里插入图片描述
这边的callback只是一个简单的回调方法的说明,真正使用中也许需要增加线程来做到异步的处理或者是在其他的.c或.cpp文件中进行回调的处理,这个时候就需要将此callback维护起来

扩展

有没有好奇的小伙伴们注意到,如果我们即写了显示主动注册又写了隐式被动注册会如何?
这边大家不妨也可以试试,其实是会使用主动注册方式绑定的,因为我们知道隐式被动注册会在调用方法的时候才去查找绑定native方法,而且,只要绑定过了一次后,就不会再次绑定,而显示主动注册方式的onLoad会先执行,先为native方法绑定

RegisterNatives方法也可以不在onLoad方法中调用,我们可以这样做来实验一下,我们先去除onLoad的注册方法

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

用隐式被动注册来注册一个方法,并且在其内部使用RegisterNatives方法注册绑定另一个我们显示的native方法

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_nativeevaldemo_MainNativeInterface_stringFromJNI(JNIEnv *env, jclass thiz) {
    // Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
    jclass c = env->FindClass("com/example/nativeevaldemo/MainNativeInterface");

    // Register your class' native methods.
    static const JNINativeMethod methodss[] = {
            {"stringFromJNI", "()Ljava/lang/String;", (void*) stringFromJNI}
    };
    int rc = env->RegisterNatives(c, methodss, sizeof(methodss)/sizeof(JNINativeMethod));
    return env->NewStringUTF("native auto search method bind");
}
static jstring
stringFromJNI(JNIEnv *jenv, jclass ths){
    return jenv->NewStringUTF("native hand method bind");
}

我们调用该方法2次,会发现该方法的绑定会被切换
在这里插入图片描述
是不是很有趣呢

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-03-03 16:26:11  更:2022-03-03 16:30:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 19:06:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码