jni基础

2023-11-18

JNI相关
静态注册
// java代码需要和C++代码相符通讯就需要通过JNI来进行注册
//java: public native String stringFromJNI(); //代表该函数的实现在so
//so: Java_com_first_firstndkdemo_MainActivity_stringFromJNI(...)
//静态注册时,so中的方法名编写规则:Java_+java中该函数所在的 包名_+类名_+方法名_
JNI_onLoad
//1.so中各种函数的执行时机:
//	init、init_array、JNI_OnLoad
	
//2.JNI_Onload的定义:
JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *reserved){  //JavaVM是在jni.h中定义好的一个结构体
    JNIEnv *env = nullptr;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK){  //GetEnv:获取jni env(得到jni的环境)
        LOGD("GetEnv failed");
        return -1;
    }
    return JNI_VERSION_1_6;
}

//3.注意事项
	//一个so中可以不定义JNI_OnLoad
	//一旦定义了JNI_OnLoad,在so首次被加载的时候就会被执行
	//必须返回JNI版本 JNI_VERSION_1_6

Java VM
//在jni.h中用C++定义的一个结构体,里面常用的几个方法,GetEnv(在主线程中获取jni的环境等)。。。。

//JavaVM的获取方式:
	//JNI_OnLoad的第一个参数
	//JNI_OnUnLoad的第一个参数
	//env->GetJavaVM
	//对比各种方式获取的JavaVM指针是否一致
//作用:获取JNI_Env,然后实现so与Java进行通信
JNIEnv
//1.它本身是一个在jni.h中定义的一个很大的结构体
//2.通过它里面的函数让C++代码与Java代码进行交互
so函数注册
'''
一般不是静态注册的函数不会出现在导出表里面
1.jin函数的静态注册
	必须遵循一定的命名规则,一般是Java_包名_类名_方法名
	系统会通过dlopen加载对应的so,通过dlsym来获取指定的函数地址,然后调用静态注册jni函数,必然在导出表里
	
2.jni函数的动态注册
	通过env->RegisterNatives注册函数,通常在JNI_OnLoad中注册
	JNINativeMethod
	函数签名
	可以给同一个Java函数注册多个native函数,以最后一次为准
	
	
	//翻译java层的代码,将这个函数作为注册的第三个函数
jstring encodeFromC(JNIEnv* env,jobject obj,jint a,jbyteArray b,jstring c){
    return env->NewStringUTF("encodeFromC:Hello from C++");
    //NewStringUTF:将C语言的char*转为jstring
}
	
	//注册操作是在 JNI_OnLoad中操作的,所以在加载so的时候就会进行注册操作
JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *reserved){
    JNIEnv *env = nullptr;
    if(vm->GetEnv((void **) &env,JNI_VERSION_1_6)!=JNI_OK){
        LOGD("GetEnv failed");
        return -1;
    }


    //=======================动态注册=============================
    // 动态注册是在加载so的时候就注册的,静态注册是在调用他的时候在注册的
    //首先去寻找类,因为是根据类来注册的(就是需要注册的函数所在的类)
    jclass MainActivityClass = env->FindClass("com/first/firstndk/MainActivity");
    //因为通常是注册多个函数,所以这个地方定义成一个数组
    //JNINativeMethod的原型就是一个结构体
//    typedef struct {
//        const char* name;
//        const char* signature;
//        void*       fnPtr;
//    } JNINativeMethod;
    JNINativeMethod methods[] = {
            //public native String encode(int i,String str,byte[] byt);
            //{"需要注册的函数名字","函数的签名(传入的参数和返回的类型)",(void *)encodeFromC}
            {"stringFromJNI2", "(I[BLjava/lang/String;)Ljava/lang/String;",(void *)encodeFromC}
            //(I[B)Ljava/lang/String;): I:int [B:字节数组,L;:对象,Ljava/lang/String;:代表java/lang/String对象
            //后面的哪个 Ljava/lang/String; 代表返回值
            //(void *)encodeFromC:就是将java成的那个函数翻译为C代码
    };
    //jint RegisterNatives(jclass clazz(类), const JNINativeMethod* methods(地址),jint nMethods(注册数量))
    env->RegisterNatives(MainActivityClass,methods,sizeof(methods)/sizeof(JNINativeMethod));


    JavaVM *vm2;
    env->GetJavaVM(&vm2);
    //因为在同一个线程中,所以这两个env打印出来的值肯定是一样的
    LOGD("JNI_OnLoad JavaVM1: %p",vm);
    LOGD("JNI_OnLoad JavaVM2: %p",vm2);
    LOGD("JNI_OnLoad GetEnv: %p",env);
    return JNI_VERSION_1_6;
}

//当函数为动态注册时,这是不会出现在导出表里面,因为我们在动态注册的时候是在JNI_OnLoad中注册的,这时候就在导出表中直接搜jni_onload,然后进入

//arm中:DCQ表示8个字节,就是当传入的参数是指针的时候用这个模式(字母d切换模式)
//可以给同一个java函数注册多个native函数,以最后一次为准
so中常见的Log输出
//原生
#include <android/log.h> //安卓中的log包
    //这里的数字3 是代编选择什么样的形式输出(info,debugger,error.....),在源码里面这个第一个参数就是一个枚举类型,
    //枚举中第一个参数是0,依次往后推,故这里的3就是选的第四个参数
    //参数:sheation是标签名
    //参数:"cstr: %s",hello.c_str()是要输出的内容
__android_log_print(3,"sheation","cstr: %s",hello.c_str());

//封装后
#define TAG "sheation"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__);
LOGD("cstr: %s",hello.c_str());
多个cpp编译成一个so
//1.步骤
//	编写多个cpp文件
	//当创建一个cpp文件的时候,这时候想要在其他文件是使用他的话,需要在当前cpp文件中加上一个 extern 函数名();
//	修改CMakeLists.txt 文件(需要该两个)
		add_library( # Sets the name of the library.
             native-lib  # 这个名字就是编译后的so文件的名字

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             file_1.cpp file_2.cpp ...)
            
        target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
    //如果要编译成多个so,那就把这个在复制一个 改一下so的名字 和下面的需要编译的cpp文件的名字
      add_library( # Sets the name of the library.
             native-lib_2  # 这个名字就是编译后的so文件的名字

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             file_3.cpp file_4.cpp ...)
            
       target_link_libraries( # Specifies the target library.
                       native-lib_2

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
//	Java静态代码块加载多个so
    //如果需要使用该so就必须在java层加载该函数
     static {
        System.loadLibrary("native-lib");
        System.loadLibrary("native-lib1");
    }
so路径的动态获取
//由于32位和64位存放的路径是不一样的,为了更加通用,可以用代码动态获取so的路径
//当手机安装好app后,存放路径在data/app目录下的
public class Utils {
    public String getPath(Context cxt){
        // 通过上下文context获取包管理器
        PackageManager pm = cxt.getPackageManager();
        //通过包管理器得到已经安装的所有app
        List<PackageInfo> pkgList = pm.getInstalledPackages(0);
        
        if(pkgList == null || pkgList.size() == 0) return null;
        for (PackageInfo pi : pkgList){
            if (pi.applicationInfo.nativeLibraryDir.startsWith("/data/app")
            && pi.packageName.startsWith("com.first.firstndk")){
                return pi.applicationInfo.nativeLibraryDir;
            }
        }
        return null;
    }
}
so之间的相互调用
//1.使用dlopen,dlsym,dlclose获取函数地址,然后调用,需要导入dlfcn.h(安卓7.0,7.1用不了 dlopen)
jstring encodeFromC(JNIEnv* env,jobject obj,jint a,jbyteArray b,jstring c){
    //将jstring转换为C中的char*
    const char *soPath = env->GetStringUTFChars(c, nullptr);
    //获取函数地址
    void *soinfo = dlopen(soPath,RTLD_NOW);  //RTLD_NOW:加载完后立马初始化
    //定义一个函数指针
    void (*def)() = nullptr;
    //dlsym(soinfo,"_Z7fromSoBPc"):soinfo:so文件的指针,"_Z7fromSoBPc"对应的cpp函数名(这里的函数名是符号修饰过后的名字,也即是该函数在汇编里面的样子)
    //返回该函数的地址,这里需要将其强转成函数指针后才能用
    //函数指针定义:void (*test)();  void (*)() 代表函数指针的类型,test代表函数指针的变量的名字
    def = reinterpret_cast<void (*)()>(dlsym(soinfo,"_Z4testv"));
    //通过函数指针调用函数
    def();
    return env->NewStringUTF("encodeFromC:Hello from C++");
    //NewStringUTF:将C语言的char*转为jstring
}

通过jni在so中创建Java对象
// 1.通过NewObject创建对象
	jclass clazz = env->FindClass("com/sheation/ndk/NDKDemo"); //寻找类
	jmethodID methodID = env->GetMethodID(clazz "<init>","()V"); //寻找方法
	jobject ReflecDemoObj = env->NewObject(clazz,methodID);//实例化对象
	LOGD("ReflectDemoObj %p",ReflectDemoObj);

//2.通过ALLocObject创建对象
	jclass clazz = env-FindClass("com/sheation/ndk/NDKDemo");  //寻找类
	jmethodID methodID2 = env-?GetMethodID(clazz,"<init>","(Ljava/lang/String;I)V");//寻找方法
	jobject ReflectDemoObj2 = env->AllocObject(clazz); //分配内存
	jstring jstr = env->NewStringUTF("from jni str");
	env->CallNovirtualVoidMethod(ReflectDemoObj2,clazz,methodID2,jstr,100);//初始化对象
通过jni访问Java属性
//1.获取静态字段
	//GetStaticFieldID这个方法是根据查找方法的类型而定的
	jfieldID privateStaticStringFieldID = env->GetStaticFieldID(NDKDemoClazz,"privateStaticStringField","Ljava/lang/String;");
    //获取属性结果,需要转换一下,因为GetStaticObjectField返回的jobject,而我们需要的是jstring
    jstring jstr = static_cast<jstring>(env->GetStaticObjectField(NDKDemoClazz,
                                                                  privateStaticStringFieldID));
    //如果需要在当前文件中打印的话还需要将jstring转为cstring
    const char* cstr = env->GetStringUTFChars(jstr, nullptr);
    LOGD("cstr %s",cstr);

//获取对象字段
	jfieldID privateStringFieldID = env->GetFieldID(NDKDemoClazz,"privateStringField","Ljava/lang/String;");
    //这里传的是对象的名字,不是类
    jstring jstr1 = static_cast<jstring>(env->GetObjectField(ndkobj,privateStringFieldID));
    const char* cstr1 = env->GetStringUTFChars(jstr1, nullptr);
    LOGD("cstr1 %s",cstr1);
env->ReleaseStringChars(jstr1, reinterpret_cast<const jchar *>(cstr1));//操作完释放一下

//3。设置值
//先获取fieldID
    jfieldID privateStringFieldID1 = env->GetFieldID(NDKDemoClazz,"privateStringField","Ljava/lang/String;");
    //然后设置值
    env->SetObjectField(ndkobj,privateStringFieldID1,env->NewStringUTF("sheation"));
    //接着获取值
    jstring setJstr = static_cast<jstring>(env->GetObjectField(ndkobj, privateStringFieldID1));
    //转换类型
    const char* setCstr = env->GetStringUTFChars(setJstr, nullptr);
    LOGD("setCstr %s",setCstr);
通过jni访问java数组
//访问数组
    //获取数组字段ID
    jfieldID byteArrayID = env->GetFieldID(NDKDemoClazz,"byteArray","[B");
    //获取数组
    jbyteArray byteArray = static_cast<jbyteArray>(env->GetObjectField(ndkobj, byteArrayID));
    //获取数组长度
    jsize _byteArrayLength = env->GetArrayLength(byteArray);
    LOGD("byteArrayLength",_byteArrayLength);
	

	//修改数组字段
    jbyte newArray[_byteArrayLength];
    for (int i = 0; i < _byteArrayLength; ++i) {
        newArray[i] = i*100;
    }
    env->SetByteArrayRegion(byteArray, 0, _byteArrayLength,reinterpret_cast<const jbyte *>(&newArray));


    //获取数组元素
    char* cbyteArray = reinterpret_cast<char *>(env->GetByteArrayElements(byteArray, nullptr));
    //打印出数组元素
    for (int i = 0; i < _byteArrayLength; ++i) {
        LOGD("byteArray[%d]=%d",i,cbyteArray[i]);
    }
    //释放数组 
    env->ReleaseByteArrayElements(byteArray, reinterpret_cast<jbyte *>(cbyteArray), 0);
通过jni访问java方法
//调用静态函数
    //获取函数ID
    jmethodID publicStaticFuncID = env->GetStaticMethodID(NDKDemoClazz,"publicStaticFunc","()V");
    //调用函数
    env->CallStaticVoidMethod(NDKDemoClazz,publicStaticFuncID);

//调用对象函数
    jmethodID publicFuncID = env->GetMethodID(NDKDemoClazz,"publicFunc","()V");
    env->CallVoidMethod(ndkobj,publicFuncID);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

jni基础 的相关文章

  • 井字游戏代码有助于改进

    这是我必须检查玩家在井字棋游戏中获胜的代码 这是一个很长的 if 语句 可以改进 该板由 9 个图片框组成 我是一名 C 初学者 pBox Image Player players Player playerTurn getImage ch
  • fork 和 exec 之间的区别

    两者有什么区别fork and exec 指某东西的用途fork and exec它体现了 UNIX 的精神 它提供了一种非常简单的方法来启动新进程 The fork调用基本上复制了当前进程 在almost任何方式 并非所有内容都会被复制
  • 从数组中输入多个数字,每个数字检查是否为整数

    每个人 我希望有人能帮我弄清楚C语言的一些东西 这是我第一次认真地做IT方面的作业 我没有经验 而且我正在电子学习中学习 所以老师的帮助不是很好 我需要用C语言开发控制台应用程序 用户需要输入10个整数 如果插入的数字不是整数 需要输出错误
  • 如何在 C# 中启动文件

    编辑 我觉得自己像个白痴 我有一种感觉 像下面的答案会起作用 但没有看到任何与下面的答案类似的谷歌结果 所以当我看到这段复杂的代码时 我想它一定是这样的 我搜索并找到了这个Windows 列出并启动与扩展关联的应用程序 https stac
  • 找不到 HttpContextBase 命名空间

    public string GetCartId HttpContextBase context if context Session CartSessionKey null if string IsNullOrWhiteSpace cont
  • 用C#发送USSD?

    我想编写一个在 Windows Mobile 6 上运行的简单 C 应用程序 它可以发送 USSD 消息 有没有任何图书馆可以帮助我做到这一点 或者是否有任何示例解释如何使用线路发送USSD http msdn microsoft com
  • File.ReadAllLines 或流读取器

    我们可以使用以下方式读取文件StreamReader http msdn microsoft com en us library vstudio system io streamreader或通过使用File ReadAllLines ht
  • 析构函数、dispose 和 Finalize 方法之间的区别

    我正在研究垃圾收集器在 C 中的工作原理 我对使用感到困惑Destructor Dispose and Finalize方法 根据我的研究和理解 在我的类中拥有析构函数方法将告诉垃圾收集器以析构函数方法中提到的方式执行垃圾收集 该方法不能在
  • 泛型类上的 DebuggerDisplay

    我在应用时遇到问题DebuggerDisplay泛型类的属性 DebuggerDisplay foo class Foo DebuggerDisplay Bar t class Bar
  • 模板“内联”函数的静态局部变量[重复]

    这个问题在这里已经有答案了 static的局部变量inline如果我的理解是正确的 C 中的函数保证像单个全局变量一样存在 如果inline函数是一个模板 编译器可以在哪里生成该函数的多个版本 下面这篇文章应该很好地回答你的问题 http
  • 将数字 n 拆分为 k 个不同数字的总和

    我有一个数字 n 我必须将它分成 k 个数字 使得所有 k 个数字都是不同的 k 个数字的总和等于 n 并且 k 最大 例如 如果 n 为 9 则答案应为 1 2 6 如果 n 为 15 则答案应为 1 2 3 4 5 这就是我尝试过的 v
  • 访问结构向量

    我有一个结构 struct OutputStore int myINT string mySTRING 如果我创建一个 OutputStore 类型的数组 如下所示 OutputStore OutputFileData new Output
  • C# 的 xml 序列化中是否有一个属性可以跳过空数组?

    C 的 xml 序列化中是否有一个属性可以跳过空数组 这将提高 xml 输出的可读性 好吧 你也许可以添加一个ShouldSerializeFoo method using System using System ComponentMode
  • 使用可变参数模板函数计算多个值的平均值

    我正在尝试编写一个函数来确定任意数量参数的平均值 所有参数都具有相同的类型 出于学习目的 我尝试使用可变参数模板函数来做到这一点 这是我到目前为止所拥有的 template
  • 如何将此 Boost ASIO 示例应用到我的应用程序中

    我已经阅读了很多 ASIO 示例 但我仍然对如何在我的应用程序中使用它们感到困惑 基本上 我的服务器端需要接受超过100个连接 客户端 这部分是通过使用线程池 通常每个CPU核心2 4个线程 来完成的 为简单起见 我们假设只有一个连接 为了
  • 如何将这个基于代码的 WPF 工具提示转换为 Silverlight?

    以下工具提示代码适用于WPF 我正在努力让它发挥作用银光 但它给了我这些errors TextBlock does not contain a definition for ToolTip Cursors does not contain
  • Lambda 按值捕获和“mutable”关键字

    关键词的必要性mutable在 lambda 中 是造成极大混乱的根源 考虑代码 int x 10 function
  • 在 Visual Studio C++ 资源编辑器中导入 png 文件

    我希望能够在 Visual Studio 资源编辑器中导入 png 文件 以便能够在不同的其他项目中使用嵌入的资源 有解决办法吗 我知道它适用于位图 但我对 png 感兴趣 因为即使在较低格式 16x16 或 32x32 上也可以使用 透明
  • 致命:所有操作都需要OperationId。请为路径的“获取”操作添加它

    我正在使用 AutoRest 从 swagger json 生成 api 的客户端 输出是 AutoRest code generation utility cli version 3 0 6187 node v10 16 3 max me
  • 使用 Powershell 或 C# 获取 Azure“文件和文件夹”作业状态

    我一直在尝试找到一种方法来获取在 AzureRM 中运行的几个客户上运行的 文件和文件夹 备份作业的状态 可以在 AzureRm 门户中手动找到状态 恢复服务保管库 gt 作业 gt 备份作业 使用powershell不显示任何作业信息 G

随机推荐

  • Jfugue编程概要

    转自 http www sudu cn info html edu java 20060912 304274 html JFugue是个用于音乐作曲的Java API 和其他的音乐API不同 他能够让你用数据字符串来指定音符 乐器 和弦 及
  • java sql 查询中的转义序列不对_在 JDBC 中使用 SQL 转义序列 - SQL Server

    使用 SQL 转义序列Using SQL escape sequences 08 12 2019 本文内容 按照 JDBC API 的定义 Microsoft JDBC Driver for SQL ServerMicrosoft JDBC
  • 20天零基础自学Python

    大家好 我是宁一 Python 数字数据类型是用来存储数值的 是我们从小学就开始接触的老朋友了 也是python中最基础的数据类型 1 Number 数据类型 python3的 Number 数据类型包括 int 整数 float 浮点数
  • B站化播放量为播放时长,是谁的狂欢?

    6月26日晚 B站举办了14周年庆典晚会 在晚会上 除了周深 美依礼芽同框献唱受到关注 B站董事长兼CEO陈睿的演讲内容同样值得深思 一来 陈睿提到 要将目前B站视频前台显示的播放量数据从次数改为分钟数 计划未来几周 将完成产品更新 二来
  • 堆栈内存和闭包

    思维导图 堆栈内存小科普 1 js中的内存分为 堆内存和栈内存 堆内存 只要用来存储引用数据类型的值 对象存的是键值对 函数存的是字符串 栈内存 供js运行的环境 函数执行 存基本数据类型的值 堆栈内存的释放问题 我们每次给变量存值或者执行
  • 程序员秋招最全Java面试题及答案整理(2023最新版)

    前言 大家好 最近一个月 花了不少时间 给大家整理了一套 2023 的技术面试资料 包括各大厂最新面试题以及面经解析涉及JVM Mysql 并发 Spring Mybatis Redis RocketMQ Kafka Zookeeper N
  • 【C刷题】day1

    一 选择题 1 正确的输出结果是 int x 5 y 7 void swap int z z x x y y z int main int x 3 y 8 swap printf d d n x y return 0 答案 3 8 解析 考
  • 怎样将excel文件导入mysql中

    1 整理好excel表中的字段 2 在Navicat中创建表 如果导入的是一个追加的表 则无需创建新表 CREATE TABLE orderinfo orderid VARCHAR 10 NULL 订单 id 主键 userid INT 1
  • 华为OD机试2023年最新题库(JAVA、Python、C++)

    我是一名软件开发培训机构老师 我的学生已经有上百人通过了华为OD机试 学生们每次考完试 会把题目拿出来一起交流分享 重要 5 11月份考的都是OD统一考试 B卷 2023年5月份题库已经更新为OD统一考试 B卷 题库由三部分组成 1 202
  • 【H5】 svg内text、image、path标签的使用

    H5 svg内text image path标签的使用 text标签 div style width 500px height 500px border 2px solid pink margin 50px auto 0 div
  • XML中约束文档的引用和书写

    在XML中定义了一套规则 来对文档内容进行约束 这叫做XML约束 常用的俩种约束语言 DTD约束 Schema约束 XML文档中可以引入多个约束文档 为了防止出现不同含义的同名名称冲突 所以 所以可以XML提供了名称空间 1 DTD语法 D
  • 【HTML】列表标签、表格标签、块级标签、表单标签

    文章目录 一 列表标签 1 无序列表 2 无序列表 3 定义列表 项目列表 二 表格标签 1 表格整体架构 2 表格的标签介绍 3 table标签的属性 4 tr标签的属性 5 th td标签的属性 6 跨行 跨列的表格 三 块级标签 1
  • vue猜数字游戏

    div p msg p div
  • node.js JSON对象和string的相互转化

    JSON stringify obj 将JSON转为字符串 var json aa sdddssd bb 892394829342394792399 23894723984729374932874 cc 11111111111111 gt
  • html5期末知识点归纳总结,web期末考试知识点

    题型及知识点 一 知识点 上课内容全覆盖 除补充的html5和css3的内容 常用的html标记及属性 弄清楚哪些是块元素 哪些是行内元素 特殊字符标记 p40 Css属性 i 字体 font size font family font w
  • 蓝桥杯JAVA B组 2020(1)第五题 排序

    一 题目描述 小蓝最近学习了一些排序算法 其中冒泡排序让他印象深刻 在冒泡排序中 每次只能交换相邻的两个元素 小蓝发现 如果对一个字符串中的字符排序 只允许交换相邻的两个字符 则在所有可能的排序方案中 冒泡排序的总交换次数是最少的 例如 对
  • SQLCipher核心思想

    加密原理 page data iv hmac iv是一段随机数 可以保证每一页的iv值都不一样 和page data一起作用 用于生成hmac值 sizeof page data p
  • kubeasz 二进制安装k8s高可用集群

    一 kubeasz介绍 项目致力于提供快速部署高可用k8s集群的工具 同时也努力成为k8s实践 使用的参考书 基于二进制方式部署和利用ansible playbook实现自动化 既提供一键安装脚本 也可以根据安装指南分步执行安装各个组件 二
  • Maven2部署构件到Nexus时出现的Failed to transfer file错误

    原文出处 http www javatang com archives 2010 01 23 4518375 html 作者 Jet Mah from Java堂 声明 可以非商业性任意转载 转载时请务必以超链接形式标明文章原始出处 作者信
  • jni基础

    JNI相关 静态注册 java代码需要和C 代码相符通讯就需要通过JNI来进行注册 java public native String stringFromJNI 代表该函数的实现在so so Java com first firstndk