【Android NDK 开发】JNI 动态注册 ( 动态注册流程 | JNI_OnLoad 方法 | JNINativeMethod 结构体 | GetEnv | RegisterNatives )

简介: 【Android NDK 开发】JNI 动态注册 ( 动态注册流程 | JNI_OnLoad 方法 | JNINativeMethod 结构体 | GetEnv | RegisterNatives )

文章目录

I . 动态注册流程 ( 总结 )

II . JNI_OnLoad 方法

III . 被注册的本地 C/C++ 方法参数

IV . JNINativeMethod 结构体 ( 核心重点 )

V . JavaVM 获取 JNIEnv ( GetEnv )

VI . 动态注册方法 RegisterNatives ( 核心重点 )

VII . 动态注册流程完整代码



I . 动态注册流程 ( 总结 )


动态注册流程 :



① 声明 Java 层 Native 方法 : 在 Java 类中声明 native 方法 ;


 

/**
     * 动态注册 JNI 方法 , Java 方法
     */
    public native void dynamicRegisterJavaMethod();
    public native int dynamicRegisterJavaMethod2(int i);


② 准备数据 JNINativeMethod methods[] 数组 : 其中定义了 Java 层方法与 Native 层方法的对应关系 ;


/*
    该数组中 , 每个元素都是一个 JNI 的 Native 方法
    JNINativeMethod 是结构体
        typedef struct {
            const char* name;       //Java 中定义的 Native 方法名 , 注意这是一个 C 字符串
            const char* signature;  //函数签名 , 可以使用 javap 生成
            void*       fnPtr;      //C/C++ 中的 Native 函数签名
        } JNINativeMethod;
 */
static const JNINativeMethod methods[] = {
        {"dynamicRegisterJavaMethod", "()V", (void *)dynamicRegisterCMethod},
        {"dynamicRegisterJavaMethod2", "(I)I", (void *)dynamicRegisterCMethod2}
};



③ 编写 JNI_OnLoad 方法 : 在该方法中进行 JNI 方法动态注册操作 ;


int JNI_OnLoad(JavaVM *vm , void *r){
  return JNI_VERSION_1_6;
}



④ 获取 JNIEnv 指针 : 调用 JavaVM 结构体的 GetEnv 方法 , 获取 JNIEnv 指针 ;


 

//1 . 获取 JNIEnv JNI 环境 , 需要从 JavaVM 获取
    JNIEnv *env = nullptr;
    //2 . 调用 JavaVM / _JavaVM 结构体的 jint GetEnv(void** env, jint version) 方法
    //      返回值分析 : 动态注册会返回一个结果
    //          如果 registerResult < 0 , 则动态注册失败
    //          如果 registerResult == 0 , 则动态注册失败
    int registerResult = vm->GetEnv( (void **) &env, JNI_VERSION_1_6 );



⑤ 获取 Java 类 : 调用 JNIEnv 结构体的 FindClass 方法获取 jclass 对象 ;


/*
    动态注册的 Java 类名称
        注意 : 包名类名之间使用 "/" 分割
 */
static const char* className = "kim/hsl/onload/MainActivity";
//获取要动态注册的 Java 类的 Class 对象
 jclass jclazz = env->FindClass(className);



⑥ 进行动态注册 : 调用 JNIEnv 的 RegisterNatives 方法 , 进行正式注册 ;


/*
      5 .正式注册
            注册方法解析 :
            jint RegisterNatives(
                jclass clazz,                   //要注册的 Java 类
                const JNINativeMethod* methods, //JNI注册方法数组
                jint nMethods                   //要注册的 JNI 方法个数
            )
            sizeof(methods) / sizeof(JNINativeMethod) : 计算 JNINativeMethod methods[] 数组大小
     */
    env->RegisterNatives(jclazz, methods, sizeof(methods) / sizeof(JNINativeMethod));




II . JNI_OnLoad 方法


1 . JNI_OnLoad 函数原型 : Java 类中调用 System.loadLibrary(“native-lib”) 代码时 , 调用 JNI_OnLoad 方法 ;



① jni.h 中有该函数的声明 :


/*
 * Prototypes for functions exported by loadable shared libs.  These are
 * called by JNI, not provided by JNI.
 */
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);



② 参数列表说明 :


JavaVM* vm : 表示 Java 虚拟机 ;


void* reserved : 一般是 NULL ;



③ 返回值说明 : 返回当前 NDK 使用的 JNI 版本 ;


JNI 版本 中可选的有四个值 , 但是只能选择返回后三个 JNI_VERSION_1_2 , JNI_VERSION_1_4 , JNI_VERSION_1_6 , 返回上述三个值任意一个没有区别 ;


返回 JNI_VERSION_1_1 会报错 ;


#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#define JNI_VERSION_1_6 0x00010006


2 . JNI_OnLoad 方法常用操作 :



① 获取 JavaVM 对象 ;


② 动态注册 JNI 方法 ;




III . 被注册的本地 C/C++ 方法参数


1 . 动态注册对应的 C/C++ 本地方法 参数情况 :


① 传递参数 : 如果动态注册的方法需要传递参数 , 需要加上 前面的 JNIEnv *env, jobject obj 两个参数


jint dynamicRegisterCMethod2(JNIEnv *env, jobject obj, jint i){
    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "dynamicRegisterCMethod2 : %d", i);
    return i + 1;
}



② 不传递参数 : 如果不传递参数 , 就可以不添加任何参数 , 参数可以空着 ;


void dynamicRegisterCMethod(){
    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "dynamicRegisterCMethod");
}




IV . JNINativeMethod 结构体 ( 核心重点 )


1 . 结构体定义 : 该结构体定义了 C/C++ 方法 与 Java 方法的映射 ;



① const char* name : Java 中定义的 Native 方法名 , 注意这是一个 C 字符串


② const char* signature : 函数签名 , 可以使用 javap 生成


③ void* fnPtr : C/C++ 中的 Native 函数指针


typedef struct {
    const char* name;  // Java 中定义的 Native 方法名 , 注意这是一个 C 字符串
    const char* signature;  // 函数签名 , 可以使用 javap 生成
    void*       fnPtr;  // C/C++ 中的 Native 函数签名
} JNINativeMethod;


2 . JNINativeMethod methods[] 数组 :



该数组是 JNI 方法动态注册的参数 , 每个结构体表示了本地方法 与 Java 层方法的映射 ;


数组中有几个元素 , 那么就映射了几个方法 ;




V . JavaVM 获取 JNIEnv ( GetEnv )


函数原型 : 从 Java 虚拟机 ( JavaVM ) 中获取 JNI 运行环境 ( JNIEnv ) ;



① 参数说明 :


void** env : JNIEnv 的二级指针 ;


jint version : JNI 的版本 , 一般传入 JNI_VERSION_1_6 ;



② 返回值说明 : 返回动态注册结果 ;


动态注册成功 : 返回 JNI_OK , 即 0 ;


动态注册失败 : 返回一个小于 0 的值 ;


struct _JavaVM {
  //封装了 JNIInvokeInterface 结构体 , C 语言环境中调用该结构体中的方法
    const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
    ...
    //C++ 中封装了 JNIInvokeInterface 的 GetEnv 方法
    jint GetEnv(void** env, jint version)
    { return functions->GetEnv(this, env, version); }
  ...
#endif /*__cplusplus*/
};




VI . 动态注册方法 RegisterNatives ( 核心重点 )


1 . 函数原型 : 通过该 RegisterNatives 方法注册 JNI 方法 ;



参数 :


jclass clazz : 要注册方法所在的 Java 类
const JNINativeMethod* methods : Java 方法 与 本地方法的映射关系数组 ;
jint nMethods : 映射的方法个数 , 一般是 methods 数组元素个数 ;
struct _JNIEnv {
    /* _JNIEnv  结构体中封装了 JNINativeInterface 结构体指针 */
    const struct JNINativeInterface* functions;
    ...
    // 最终 调用的 还是 JNINativeInterface 结构体中封装的 RegisterNatives 方法
    jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
        jint nMethods)
    { return functions->RegisterNatives(this, clazz, methods, nMethods); }
    ...
}



2 . 代码示例 :


//1 . Java 与 本地 方法映射数组
JNINativeMethod methods[] = {
        {"dynamicRegisterJavaMethod", "()V", (void *)dynamicRegisterCMethod},
        {"dynamicRegisterJavaMethod2", "(I)I", (void *)dynamicRegisterCMethod2}
};
...
//2 . Java 类名 , 注意 : 包名类名之间使用 "/" 分割
char* className = "kim/hsl/onload/MainActivity";
...
//3 . 获取要动态注册的 Java 类的 Class 对象
jclass jclazz = env->FindClass(className);
...
//4 . 正式注册
env->RegisterNatives(jclazz, methods, sizeof(methods) / sizeof(JNINativeMethod));



VII . 动态注册流程完整代码


1 . Java 层代码 :


package kim.hsl.onload;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dynamicRegisterJavaMethod();
        dynamicRegisterJavaMethod2(250);
    }
    /**
     * 动态注册 JNI 方法 , Java 方法
     */
    public native void dynamicRegisterJavaMethod();
    public native int dynamicRegisterJavaMethod2(int i);
}



2 . 本地代码 ( C++ ) :


#include <jni.h>
#include <string>
#include <android/log.h>
/*
    I . JNI_Onload 方法
    JNI_Onload 方法在 Java 层执行 System.loadLibrary("native-lib") 代码时调用的方法
        主要是执行一些 JNI 初始化操作
    JNI_Onload 常见操作 :
        ① 保存 JavaVM 对象 , 使用全局变量记录该 Java 虚拟机对象
        ② 动态注册 : 动态注册是该方法中最常见的操作
    JNI_Onload 参数说明 :
        JavaVM *vm : 代表了 Java 虚拟机
        void *r : 一般是 NULL
    JNI_Onload 返回值说明 :
        int 类型返回值代表了当前 NDK 使用的 JNI 版本
        JNI 版本 中可选的有四个值 , 但是只能选择返回后三个 JNI_VERSION_1_2 , JNI_VERSION_1_4 , JNI_VERSION_1_6
            返回上述三个值任意一个没有区别
            返回 JNI_VERSION_1_1 会报错
            #define JNI_VERSION_1_1 0x00010001
            #define JNI_VERSION_1_2 0x00010002
            #define JNI_VERSION_1_4 0x00010004
            #define JNI_VERSION_1_6 0x00010006
            这四个值分别代表了 JDK 1.1 , 1.2 , 1.4 , 1.6 对应的 JNI 版本
    II . 动态注册
    动态注册 :
        动态注册与静态注册 :
            静态注册 : 使用 Java_包名_类名_方法名(JNIEnv* env, jobject obj, ...) 方式进行注册是静态注册
            动态注册 : 将 C/C++ 中的本地方法 与 Java 中的方法对应起来 , 就需要使用动态注册
        动态注册 与 静态注册 : 没有太大区别 , 都可以将 C/C++ 本地方法 与 Java 方法对应起来
    动态注册流程 :
        ① 声明 Java 层 Native 方法
        ② 准备数据 JNINativeMethod methods[] 数组
        ③ 编写 JNI_OnLoad 方法
        ④ 获取 JNIEnv 指针
        ⑤ 获取 Java 类
        ⑥ 进行动态注册
 */
//使用 全局变量 记录 Java 虚拟机对象
JavaVM *_vm;
/*
    动态注册对应的 C/C++ 本地方法
    如果动态注册的方法需要传递参数 , 需要加上 前面的 JNIEnv *env, jobject obj 两个参数
    如果不传递参数 , 就可以不添加任何参数
    不传递参数 , 参数可以空着
 */
void dynamicRegisterCMethod(){
    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "dynamicRegisterCMethod");
}
/*
    动态注册对应的 C/C++ 本地方法
    如果动态注册的方法需要传递参数 , 需要加上 前面的 JNIEnv *env, jobject obj 两个参数
    如果不传递参数 , 就可以不添加任何参数
    传递参数 , 那么需要写上 JNI 调用的完整参数
 */
jint dynamicRegisterCMethod2(JNIEnv *env, jobject obj, jint i){
    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "dynamicRegisterCMethod2 : %d", i);
    return i + 1;
}
/*
    该数组中 , 每个元素都是一个 JNI 的 Native 方法
    JNINativeMethod 是结构体
        typedef struct {
            const char* name;       //Java 中定义的 Native 方法名 , 注意这是一个 C 字符串
            const char* signature;  //函数签名 , 可以使用 javap 生成
            void*       fnPtr;      //C/C++ 中的 Native 函数指针
        } JNINativeMethod;
 */
static const JNINativeMethod methods[] = {
        {"dynamicRegisterJavaMethod", "()V", (void *)dynamicRegisterCMethod},
        {"dynamicRegisterJavaMethod2", "(I)I", (void *)dynamicRegisterCMethod2}
};
/*
    动态注册的 Java 类名称
        注意 : 包名类名之间使用 "/" 分割
 */
static const char* className = "kim/hsl/onload/MainActivity";
int JNI_OnLoad(JavaVM *vm , void* reserved){
    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "JNI_Onload");
    //I . 存储 Java 虚拟机对象
    //将 Java 虚拟机对象记录到全局变量中
    _vm = vm;
    //II . 动态注册
    //1 . 获取 JNIEnv JNI 环境 , 需要从 JavaVM 获取
    JNIEnv *env = nullptr;
    //2 . 调用 JavaVM / _JavaVM 结构体的 jint GetEnv(void** env, jint version) 方法
    //      返回值分析 : 动态注册会返回一个结果
    //          如果 registerResult < 0 , 则动态注册失败
    //          如果 registerResult == 0 , 则动态注册失败
    int registerResult = vm->GetEnv( (void **) &env, JNI_VERSION_1_6 );
    //3 . 判断结果 : 如果动态注册 Native 方法失败 , 直接退出
    if(registerResult != JNI_OK){
        return -1;
    }
    //4 . 获取要动态注册的 Java 类的 Class 对象
    jclass jclazz = env->FindClass(className);
    /*
      5 .正式注册
            注册方法解析 :
            jint RegisterNatives(
                jclass clazz,                   //要注册的 Java 类
                const JNINativeMethod* methods, //JNI注册方法数组
                jint nMethods                   //要注册的 JNI 方法个数
            )
            sizeof(methods) / sizeof(JNINativeMethod) : 计算 JNINativeMethod methods[] 数组大小
     */
    env->RegisterNatives(jclazz, methods, sizeof(methods) / sizeof(JNINativeMethod));
    return JNI_VERSION_1_6;
}


3 . 执行结果 :


2020-02-08 14:01:51.142 13375-13375/? I/JNI_TAG: JNI_Onload
2020-02-08 14:01:51.299 13375-13375/? I/JNI_TAG: dynamicRegisterCMethod
2020-02-08 14:01:51.300 13375-13375/? I/JNI_TAG: dynamicRegisterCMethod2 : 250



目录
相关文章
|
4天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
3天前
|
Android开发
Android JNI 报错(signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr )
Android JNI 报错(signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr )
20 1
|
1天前
|
数据库 Android开发 开发者
安卓应用开发:构建高效用户界面的策略
【4月更文挑战第24天】 在竞争激烈的移动应用市场中,一个流畅且响应迅速的用户界面(UI)是吸引和保留用户的关键。针对安卓平台,开发者面临着多样化的设备和系统版本,这增加了构建高效UI的复杂性。本文将深入分析安卓平台上构建高效用户界面的最佳实践,包括布局优化、资源管理和绘制性能的考量,旨在为开发者提供实用的技术指南,帮助他们创建更流畅的用户体验。
|
2天前
|
Android开发
Android JNI与CAN通信遇到的问题总结
Android JNI与CAN通信遇到的问题总结
22 1
|
18天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
20天前
|
监控 算法 Android开发
安卓应用开发:打造高效启动流程
【4月更文挑战第5天】 在移动应用的世界中,用户的第一印象至关重要。特别是对于安卓应用而言,启动时间是用户体验的关键指标之一。本文将深入探讨如何优化安卓应用的启动流程,从而减少启动时间,提升用户满意度。我们将从分析应用启动流程的各个阶段入手,提出一系列实用的技术策略,包括代码层面的优化、资源加载的管理以及异步初始化等,帮助开发者构建快速响应的安卓应用。
|
Java Android开发 C++
【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )(一)
【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )(一)
268 0
【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )(一)
|
Java Android开发 C++
【Android NDK 开发】JNI 方法解析 ( C/C++ 设置 Java 对象字段 | 查找字段 | 设置字段 )
【Android NDK 开发】JNI 方法解析 ( C/C++ 设置 Java 对象字段 | 查找字段 | 设置字段 )
172 0
|
Java Android开发 C++
【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )(三)
【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )(三)
302 0
|
Java Android开发 C++
【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )(二)
【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )(二)
362 0