• Android中Java调用C++的两种方法—静态注册和动态注册


    1.静态注册

    通过将 Java 方法和对应的 C++ 函数的名称严格按照 JNI 的命名规则进行匹配来实现的。这种方法比较简单直观,但函数名称必须按照特定的规则命名。

    • 在Activity中声明JNI函数接口
    • 在cpp 文件中生成对应的c++的函数实现

    示例:

    在MainActivity中声明 public native String stringFromJNI();

    在native-lib.cpp中声明具体的实现,名字有严格的要求,格式为Java+包名+函数名

    实现如下:

    extern "C" JNIEXPORT jstring JNICALL
    Java_com_marxist_firstjni_MainActivity_stringFromJNI(
            JNIEnv* env,
            jobject /* this */) {
        std::string hello = "Java 调用 C++ 静态注册方法";
        return env->NewStringUTF(hello.c_str());
    }
    

    2.动态注册

    动态注册方法允许在运行时注册 C++ 函数到 Java 方法。这种方法灵活性更高,不需要严格遵循命名规则。

    主要步骤:

    • 在Activity中声明JNI函数接口
    • 在cpp 文件中生成对应的c++的函数实现(名字随意)
    • 生成对应的动态注册表
    • 提供运行时注册函数

    示例如下:

    声明接口

    在MainActivity中声明 getStringFromNative

    在native-lib.cpp中声明具体的实现,函数名字不做具体的严格要求

    jstring getStringFromNative(JNIEnv* env, jobject /* this */) {
        std::string hello = "Java 调用 C++ 动态注册方法";
        return env->NewStringUTF(hello.c_str());
    }
    
    生成对应动态的注册表
    static JNINativeMethod methods[] = {
            {"getStringFromNative", "()Ljava/lang/String;", (void *)getStringFromNative}
    };
    

    JNINativeMethod 是一个结构体,用于表示 Java 本地方法和其对应的 C/C++ 实现。它的定义如下:

    typedef struct {
        const char* name;     //Java 方法的名称。
        const char* signature; //Java 方法的签名,用于描述方法的参数和返回类型。
        void* fnPtr; //指向实现该方法的本地函数的指针。
    } JNINativeMethod;
    

    methods[] 为数组,这个数组用于动态注册 JNI 方法。对应的元素为

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

    这个元素具体表示一个 Java 方法与其对应的本地实现的关系:

    • name"getStringFromNative"
      这是 Java 方法的名称,必须与 Java 类中声明的本地方法名称一致。
    • signature"()Ljava/lang/String;"
      这是方法的签名,描述了方法的参数和返回类型:
      • () 表示这个方法没有参数。
      • Ljava/lang/String; 表示这个方法返回一个 java.lang.String 类型的对象。
    • fnPtr(void *)getStringFromNative
      这是一个指向本地函数的指针,即 C/C++ 实现的方法 getStringFromNative 的地址。
      • (void *) 是将函数指针转换为 void* 类型,符合 JNINativeMethod 结构体的定义。
    运行时注册函数
    // 注册本地方法
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
        JNIEnv* env;
        // 寻找JEnv
        if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
            return JNI_ERR;
        }
        //寻找 加载native的类对象 , 从包名开始 用/分割,Android Studio有动态提示
        jclass clazz = env->FindClass("com/marxist/firstjni/MainActivity");
        if (clazz == nullptr) {
            return JNI_ERR;
        }
        //根据动态注册表的参数进行动态注册
        if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
            return JNI_ERR;
        }
        return JNI_VERSION_1_6;
    }
    

    当JVM运行的时候,将会注册 C++ 函数到 Java 方法,然后被调用。

    3.完整示例代码

    MainActivity:

    package com.marxist.firstjni;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Bundle;
    import android.widget.TextView;
    
    import com.marxist.firstjni.databinding.ActivityMainBinding;
    
    public class MainActivity extends AppCompatActivity {
    
        // 加载C++库
        static {
            System.loadLibrary("firstjni");
        }
    
        private ActivityMainBinding binding;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            binding = ActivityMainBinding.inflate(getLayoutInflater());
            setContentView(binding.getRoot());
            TextView tv = binding.sampleText;
            tv.setText(stringFromJNI()+"\n"+getStringFromNative());
        }
        //声明一个JNI函数 接口
        public native String stringFromJNI();
        public native String getStringFromNative();
    }
    

    native-lib.cpp:

    #include 
    #include 
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_marxist_firstjni_MainActivity_stringFromJNI(
            JNIEnv* env,
            jobject /* this */) {
        std::string hello = "Java 调用 C++ 静态注册方法";
        return env->NewStringUTF(hello.c_str());
    }
    jstring getStringFromNative(JNIEnv* env, jobject /* this */) {
        std::string hello = "Java 调用 C++ 动态注册方法";
        return env->NewStringUTF(hello.c_str());
    }
    // 动态注册表
    static JNINativeMethod methods[] = {
            {"getStringFromNative", "()Ljava/lang/String;", (void *)getStringFromNative}
    };
    //JVM启动的时候调用的函数
    jint JNI_OnLoad(JavaVM *vm,void* reserved){
        JNIEnv *env =  NULL;
        if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
            return JNI_ERR;
        }
        jclass clazz = env->FindClass("com/marxist/firstjni/MainActivity"); //调用native 方法的Java 类
        if (clazz == nullptr) {
            return JNI_ERR;
        }
        if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
            return JNI_ERR;
        }
        return JNI_VERSION_1_6;
    }
    

    静态注册:简单、直接,通过严格的命名规则将 Java 方法与 C++ 函数对应起来。

    动态注册灵活性更高,允许在运行时注册方法,不受命名规则限制。

    效果展示:

    在这里插入图片描述

    4.附录:JNI中的签名(signature)

    JNI(Java Native Interface)的签名(signature)用于描述 Java 方法的参数和返回类型。签名字符串是方法签名的编码表示,用于在本地代码和 Java 虚拟机之间传递方法的类型信息。以下是 JNI 签名的详细解释。

    基本类型签名

    每种 Java 基本类型都有一个对应的签名字符:

    • Z - boolean
    • B - byte
    • C - char
    • S - short
    • I - int
    • J - long
    • F - float
    • D - double
    • V - void

    对象类型签名

    对象类型的签名由 L 开头,后跟类的全限定名,并以分号 ; 结尾。例如:

    • Ljava/lang/String; 表示 java.lang.String 类。
    • Ljava/util/List; 表示 java.util.List 类。

    数组类型签名

    数组类型的签名由一个或多个左方括号 [ 开头,后跟元素类型的签名。例如:

    • [I 表示 int[] 数组。
    • [Ljava/lang/String; 表示 java.lang.String[] 数组。
    • [[D 表示 double[][] 数组。

    方法签名

    方法签名由参数类型签名列表和返回类型签名组成,参数类型签名列表用圆括号 () 括起来,返回类型签名放在括号之后。例如:

    • ()V 表示一个没有参数且没有返回值的方法。
    • (I)V 表示一个接受一个 int 参数且没有返回值的方法。
    • (Ljava/lang/String;)I 表示一个接受一个 java.lang.String 参数并返回一个 int 的方法。
    • ([I)Ljava/lang/String; 表示一个接受 int[] 参数并返回 java.lang.String 的方法。

    示例解析

    以下是一些示例签名的解析:

    示例 1

    Java 方法:

    public void myMethod();
    

    JNI 签名:

    ()V
    

    解释:

    • () 表示方法没有参数。
    • V 表示方法返回 void 类型。
    示例 2

    Java 方法:

    public int add(int a, int b);
    

    JNI 签名:

    (II)I
    

    解释:

    • (II) 表示方法有两个 int 参数。
    • I 表示方法返回 int 类型。
    示例 3

    Java 方法:

    public String getMessage(String prefix);
    

    JNI 签名:

    (Ljava/lang/String;)Ljava/lang/String;
    

    解释:

    • (Ljava/lang/String;) 表示方法有一个 java.lang.String 参数。
    • Ljava/lang/String; 表示方法返回一个 java.lang.String 类型。
    示例 4

    Java 方法:

    public int[] processArray(int[] input);
    

    JNI 签名:

    ([I)[I
    

    解释:

    • ([I) 表示方法有一个 int[] 参数。
    • [I 表示方法返回一个 int[] 类型。
  • 相关阅读:
    C++多线程编程(3):接收线程处理函数的返回值
    遨博机械臂URDF功能包ROS仿真
    Doris 平滑缩容,Be 节点卡住不动
    深度学习时代的视频理解综述
    基于 Hexo 从零开始搭建个人博客(五)
    算法刷题-哈希表
    C/C++选择题好题分享
    计时器Timing Wheel 时间轮算法
    前缀,中缀,后缀表达式规则 [数据结构][Java]
    Kubernetes部署单元-Pod
  • 原文地址:https://blog.csdn.net/weixin_46999174/article/details/140433854