Google的Ndk-Sample学习笔记之一(hello-jniCallback)

前言:  近段时间因为项目的需求,需要使用JNI,所以下载了Google的Ndk-Sample学习下,准备记录 下来,留给后期自己查看

问题点一:JNI_OnLoad方法必须返回JNI的版本

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
  JNIEnv *env;
  memset(&g_ctx, 0, sizeof(g_ctx));

  g_ctx.javaVM = vm;
  if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
    return JNI_ERR;  // JNI version not supported.
  }

  jclass clz =
      (*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
  g_ctx.jniHelperClz = (*env)->NewGlobalRef(env, clz);

  jmethodID jniHelperCtor =
      (*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");
  jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);
  g_ctx.jniHelperObj = (*env)->NewGlobalRef(env, handler);
  queryRuntimeInfo(env, g_ctx.jniHelperObj);

  g_ctx.done = 0;
  g_ctx.mainActivityObj = NULL;
  return JNI_VERSION_1_6;
}

上面是hello-jniCallback项目中的原始文件hello-jnicallback.c中相关代码,为了慢慢学习知识点,我自己没有使用hello-jnicallback.c文件,而是自己创建了自己的学习文件,hello_jni.h 跟hello_jni.c.

根据原始sample的中的文件,大概代码可以简化为如下所示,返回0

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    LOGI("enter JNI_OnLoad");
    JNIEnv* env;

    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK)
    {
        LOGE("Failed to get GetEnv");
        return JNI_ERR;
    }

    LOGI("leave JNI_OnLoad");
    return 0;
}

但是运行程序之后,程序发生了错误,通过LOG信息可以发现,错误提示是错误的Jni 版本

估计是这里返回值是要返回一个JNI的对应版本,尝试了下返回JNI_VERSIOn_1_2,JNI_VERSIOn_1_4, JNI_VERSIOn_1_6都没有问题

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    LOGI("enter JNI_OnLoad");
    JNIEnv* env;

    ......

    LOGI("leave JNI_OnLoad");
    return JNI_VERSION_1_2;
}

问题点二:JNI如何调用java中的方法

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
  JNIEnv *env;
  memset(&g_ctx, 0, sizeof(g_ctx));

  g_ctx.javaVM = vm;
  if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
    return JNI_ERR;  // JNI version not supported.
  }

  jclass clz =
      (*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
  g_ctx.jniHelperClz = (*env)->NewGlobalRef(env, clz);

  jmethodID jniHelperCtor =
      (*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");
  jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);
  g_ctx.jniHelperObj = (*env)->NewGlobalRef(env, handler);
  queryRuntimeInfo(env, g_ctx.jniHelperObj);

  g_ctx.done = 0;
  g_ctx.mainActivityObj = NULL;
  return JNI_VERSION_1_6;
}

void queryRuntimeInfo(JNIEnv *env, jobject instance) {
  // Find out which OS we are running on. It does not matter for this app
  // just to demo how to call static functions.
  // Our java JniHelper class id and instance are initialized when this
  // shared lib got loaded, we just directly use them
  //    static function does not need instance, so we just need to feed
  //    class and method id to JNI
  jmethodID versionFunc = (*env)->GetStaticMethodID(
      env, g_ctx.jniHelperClz, "getBuildVersion", "()Ljava/lang/String;");
  if (!versionFunc) {
    LOGE("Failed to retrieve getBuildVersion() methodID @ line %d", __LINE__);
    return;
  }
  jstring buildVersion =
      (*env)->CallStaticObjectMethod(env, g_ctx.jniHelperClz, versionFunc);
  const char *version = (*env)->GetStringUTFChars(env, buildVersion, NULL);
  if (!version) {
    LOGE("Unable to get version string @ line %d", __LINE__);
    return;
  }
  LOGI("Android Version - %s", version);
  (*env)->ReleaseStringUTFChars(env, buildVersion, version);

  // we are called from JNI_OnLoad, so got to release LocalRef to avoid leaking
  (*env)->DeleteLocalRef(env, buildVersion);

  // Query available memory size from a non-static public function
  // we need use an instance of JniHelper class to call JNI
  jmethodID memFunc = (*env)->GetMethodID(env, g_ctx.jniHelperClz,
                                          "getRuntimeMemorySize", "()J");
  if (!memFunc) {
    LOGE("Failed to retrieve getRuntimeMemorySize() methodID @ line %d",
         __LINE__);
    return;
  }
  jlong result = (*env)->CallLongMethod(env, instance, memFunc);
  LOGI("Runtime free memory size: %" PRId64, result);
  (void)result;  // silence the compiler warning
}

 java中的JniHandler的相关方法如下

public class JniHandler {

    private static final String TAG = "hello-jniCallback";

    /*
     * Print out status to logcat
     */
    @Keep
    private void updateStatus(String msg) {
        if (msg.toLowerCase().contains("error")) {
            Log.e("JniHandler", "Native Err: " + msg);
        } else {
            Log.i("JniHandler", "Native Msg: " + msg);
        }
    }

    /*
     * Return OS build version: a static function
     */
    @Keep
    static public String getBuildVersion() {
        return Build.VERSION.RELEASE;
    }

    /*
     * Return Java memory info
     */
    @Keep
    public long getRuntimeMemorySize() {
        return Runtime.getRuntime().freeMemory();
    }
}

Log信息如下:

通过上面的代码分析:这里有两种方法的调用,一种是CallStaicObjectMethod方法,另外一种是CalllongMethod方法,以为我之前接触过一点JNI的知识,了解JNI跟java的交互分为对象的调用跟类的调用

1.JNI如何调用java静态方法

经过代码分析,大概的调用流程如下所示

  1. findClass
  2. NewGlobalRef
  3. GetMethodId
  4. CallMeehod
jclass clz =
      (*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
  g_ctx.jniHelperClz = (*env)->NewGlobalRef(env, clz);

  jmethodID jniHelperCtor =
      (*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");
  jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);
  g_ctx.jniHelperObj = (*env)->NewGlobalRef(env, handler);


jstring buildVersion =
      (*env)->CallStaticObjectMethod(env, g_ctx.jniHelperClz, versionFunc);
  const char *version = (*env)->GetStringUTFChars(env, buildVersion, NULL);
  if (!version) {
    LOGE("Unable to get version string @ line %d", __LINE__);
    return;
  }
  LOGI("Android Version - %s", version);
  (*env)->ReleaseStringUTFChars(env, buildVersion, version);

  // we are called from JNI_OnLoad, so got to release LocalRef to avoid leaking
  (*env)->DeleteLocalRef(env, buildVersion);

代码修改为如下

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    LOGI("enter JNI_OnLoad");
    JNIEnv* env;

    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK)
    {
        LOGE("Failed to get GetEnv");
        return JNI_ERR;
    }

    LOGI("leave JNI_OnLoad");

    jclass clz = (*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
    jobject objRef = (*env)->NewGlobalRef(env, clz);

    jmethodID versionFun = (*env)->GetStaticMethodID(env, objRef, "getBuildVersion", "()Ljava/lang/String;");
    jstring buildVersion = (*env)->CallStaticObjectMethod(env, objRef, versionFun);
    const char* version = (*env)->GetStringUTFChars(env, buildVersion, NULL);
    if (version == NULL)
    {
        LOGE("Failed to get version");
        return JNI_ERR;
    }

    LOGI("java build version - %s", version);

    return JNI_VERSION_1_6;
}

经过测试,这样是没有问题的,

但是上面有一个NewGlobalRef的调用通过jni.h的查询发现,jclass跟jobject其实是同一个类型void*

准备测试下,如果不调用这个这个NewGlobalRef能不能正常运行,通过这个方法描述,这个好像只是一个全局的引用,修改为如下所示:

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    LOGI("enter JNI_OnLoad");
    JNIEnv* env;

    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK)
    {
        LOGE("Failed to get GetEnv");
        return JNI_ERR;
    }

    LOGI("leave JNI_OnLoad");

    jclass clz = (*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
    jmethodID versionFun = (*env)->GetStaticMethodID(env, clz, "getBuildVersion", "()Ljava/lang/String;");
    jstring buildVersion = (*env)->CallStaticObjectMethod(env, clz, versionFun);
    const char* version = (*env)->GetStringUTFChars(env, buildVersion, NULL);
    if (version == NULL)
    {
        LOGE("Failed to get version");
        return JNI_ERR;
    }

    LOGI("java build version - %s", version);

    return JNI_VERSION_1_6;
}

经过测试发现,没有任何问题

总结:JNI调用java中的静态方法的流程是

  1. FindClass
  2. GetStaticMethodID
  3. CallStaticobjectMethod

2.JNI如何调用java的对象方法

通过跟上面调用JNI调用静态方法的对比,猜测调用对象方法,应该是一样的

在 java中的JniHandler中添加一个新方法,如下所示

@Keep
    public String getBuildVersion2() {
        return "BuildVersion2";
    }

新的代码如下所示

但是发现报错了,错误提示信息如下

JVM object referenced by 'clz' is of type 'Class<JniHandler>' and it does not have access to method 'getBuildVersion2()' declared in class 'JniHandler'. 

猜测是类型不匹配,这里应该是一个jobject类型,而不是jclass类型 ,在上面的步骤中,不是可以通过调用NewGlobalR来获取jobject类型吗,修改如下,

信心满满,这下应该没有问题吧,现实是残酷的,啪啪打脸.c代码发生crash了

 再对比原始的方法,发现有一段这样的代码调用

jmethodID jniHelperCtor =
      (*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");
  jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);

应该需要通过先调用java的非静态方法,然后再根据生成的jmethodID去调用NewObject.开始编写代码.大概是如下所示

在编写过程中, 在传入参数,versionFun的时候, Android Studio报错了,提示

Not a constructor.  

不是一个构造期,难道需要跟java反射一样吗.需要搞一个构造器,再次去看了下原来的代码,发现有如下几行代码

jmethodID jniHelperCtor =
    (*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");
jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);

根据名字猜测JnihelperCtor,这个难道是需要调用构造器吗,立马行动起来,修改如下

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    LOGI("enter JNI_OnLoad");
    JNIEnv* env;

    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK)
    {
        LOGE("Failed to get GetEnv");
        return JNI_ERR;
    }

    LOGI("leave JNI_OnLoad");

    jclass clz = (*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
    jmethodID ctor = (*env)->GetMethodID(env, clz,  "<init>", "()V");
    jobject obj = (*env)->NewObject(env, clz, ctor);

    jmethodID versionFun = (*env)->GetMethodID(env, clz, "getBuildVersion2", "()Ljava/lang/String;");
    jstring buildVersion = (*env)->CallObjectMethod(env, obj, versionFun);
    const char* version = (*env)->GetStringUTFChars(env, buildVersion, NULL);
    if (version == NULL)
    {
        LOGE("Failed to get version");
        return JNI_ERR;
    }

    LOGI("java build version - %s", version);

    return JNI_VERSION_1_6;
}

 运行测试,发现可以了

考虑了下,调用流程应该跟java的反射一样,需要调用构造器函数,调用流程如下

  1. FindClass
  2. GeMethodID(必须是构造器方法)
  3. NewObject
  4. GetMethodID(你想调用的java非静态方法) 
  5. CallObjectMethod

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/313722.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

竞赛保研 基于深度学的图像修复 图像补全

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学的图像修复 图像补全 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng-se…

C#编程-实现多线程

实现多线程 多线程帮助同时执行各种操作。这为用户节省时间。多线程程序包括一个主线程和其他用户定义的线程以同时执行多个任务。 微处理器为执行的进程分配内存。每个进程占有内存中它们自己的地址空间。但是,所有在进程中的线程占有相同的地址空间。多线程允许在一个程序…

美团2024届秋招笔试第一场编程真题(js版本)

1.小美的外卖订单 简单的加法逻辑&#xff0c;需要注意的是各个数据的边界问题 折扣价不能超过原价减的价格不能超过满的价格满减优惠仅限原价购入 const rl require("readline").createInterface({ input: process.stdin }); void (async function () {let count…

JavaScript保留字和预定义的全局变量及函数汇总

保留字也称关键字&#xff0c;每种语言中都有该语言本身规定的一些关键字&#xff0c;这些关键字都是该语言的语法实现基础&#xff0c;JavaScript中规定了一些标识符作为现行版本的关键字或者将来版本中可能会用到的关键字&#xff0c;所以当我们定义标识符时就不能使用这些关…

典型场景解析|PolarDB分布式版如何支撑SaaS多租户?

SaaS多租户背景 很多平台类应用或系统&#xff08;如电商CRM平台、仓库订单平台等等&#xff09;&#xff0c;它们的服务模型是围绕用户维度&#xff08;这里的用户维度可以是一个卖家或品牌&#xff0c;可以是一个仓库等&#xff09;展开的。因此&#xff0c;这类型的平台业务…

文件上传漏洞(全网最详细)

目录 前言 文件上传漏洞介绍 文件上传漏洞危害 文件上传漏洞满足条件 文件检测流程 CTFSHOW 151关-170关 151关&#xff1a;前端验证绕过 152关&#xff1a;后端校验 Content-Type 校验文件格式 153关&#xff1a;filename字段文件后缀校验 154关&#xff1a;关键字过…

18_类加载

文章目录 类加载器类加载时机Java代码的3个阶段 反射关于Class配置文件(.properties)Properties类通过反射获取构造方法(Constructor)通过反射获取成员变量(Field)通过反射获取成员方法(Method) 其他API自定义类加载器反射的应用 类加载器 分类&#xff1a; Bootstrap ClassLo…

Visual Studio中项目添加链接文件

这个需求在VS里面使用还真不多见&#xff0c;只是最近在做项目的版本编号的时候遇到一个头大的问题&#xff0c;我一个解决方案下面有几十个类库&#xff0c;再发布的时候这几十个类库的版本号必须要统一&#xff0c;之前我们都是在单个的AssemblyInfo.cs里面去改相关的信息&am…

轻量级图床Imagewheel本地部署并结合内网穿透实现远程访问

文章目录 1.前言2. Imagewheel网站搭建2.1. Imagewheel下载和安装2.2. Imagewheel网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar临时数据隧道3.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测…

Echarts的常用API,以及常用的写法

ECharts是一款基于JavaScript的开源可视化库&#xff0c;用于构建交互式的图表和可视化数据。它提供了丰富的API用于定制图表和处理数据。下面是一些常用的ECharts API和写法的简介&#xff1a; 初始化图表容器&#xff1a; var myChart echarts.init(document.getElementBy…

win11更改桌面默认存储路径

打开文件资源管理器 右击桌面点击属性 在属性中找到位置选项卡&#xff0c;在里面有一个移动&#xff0c;点击它选择你想要的位置 选好位置后点击应用&#xff0c;随后会出现一个进度条&#xff0c;跑完后点击确认 到这里就完成了桌面默认位置的转移

在windows11系统上利用docker搭建linux记录

我的windows11系统上&#xff0c;之前已经安装好了window版本的docker&#xff0c;没有安装的小伙伴需要去安装一下。 下面直接记录安装linux的步骤&#xff1a; 一、创建linux容器 1、拉取镜像 docker pull ubuntu 2、查看镜像 docker images 3、创建容器 docker run --…

2024 年 1 月安全更新修补了 58 个漏洞(Android )

谷歌发布了针对 Android 平台 58 个漏洞的补丁&#xff0c;并修复了 Pixel 设备中的 3 个安全漏洞&#xff0c;拉开了 2024 年的序幕。 Android 2024 年 1 月更新的第一部分以 2024 年 1 月 1 日安全补丁级别发布在设备上&#xff0c;解决了框架和系统组件中的 10 个安全漏洞&…

高级分布式系统-第6讲 分布式系统的容错性--故障/错误/失效/异常

分布式系统容错性的概念 分布式系统的容错性&#xff1a; 当发生故障时&#xff0c; 分布式系统应当在进行恢复的同时继续以可接受的方式进行操作&#xff0c; 并且可以从部分失效中自动恢复&#xff0c; 且不会严重影响整体性能。 具体包括以下4个方面的内容&#xff1a; 可…

redis — redis cluster集群模式下如何实现批量可重入锁?

一、redis cluster 集群版 在Redis 3.0版本以后,Redis发布了Redis Cluster。该集群主要支持搞并发和海量数据处理等优势,当 Redis 在集群模式下运行时,它处理数据存储的方式与作为单个实例运行时不同。这是因为它应该准备好跨多个节点分发数据,从而实现水平可扩展性。具体能…

【科研技巧】如何判断某个期刊是什么类别及影响因子?是否是顶会?如何期刊内检索?AI写综述?AI做PPT?

相关链接 查找和免费下载文献的方式汇总国内外各大期刊关系、如何查看期刊等级以及查看某篇论文属于哪个期刊登录和访问EI(Engineering Village)数据库查找文献 1 如何判断某个期刊是什么类别及影响因子 https://sci.justscience.cn/ IFold是影响因子 期刊类别为SCIE、查看…

在线ai扩图是什么?有什么工具?分享3个好用的工具。

在线ai扩图是什么&#xff1f;有什么工具&#xff1f;分享3个好用的工具。 在当今数字化的时代&#xff0c;图像处理成为了我们日常生活和工作中不可或缺的一部分。有时候&#xff0c;我们需要将图像放大以获取更多的细节&#xff0c;但传统的方法往往会导致图像质量的损失。幸…

Nginx服务配置文件

在Nginx服务器的主配置文件/usr/local/nginx/conf/nginx.conf 中&#xff0c;包括全局配置、I/O事件配置 和HTTP配置这三大块内容&#xff0c;配置语句的格式为“关键字 值&#xff1a;”&#xff08;末尾以分号表示结束&#xff09;&#xff0c;以“#” 开始的部分表示注释。 …

小手也能用的高性能鼠标,自定义空间还挺高,雷柏VT9Pro mini上手

今年搭载PAW3395传感器的电竞鼠标很受欢迎&#xff0c;雷柏就出了不少型号&#xff0c;满足各种喜好的玩家选择&#xff0c;像是近期新出的搭载3395高定版的VT9Pro和VT9Pro mini&#xff0c;就在轻量化的基础上&#xff0c;满足了各种手型的玩家的使用需要&#xff0c;而且价格…

2024年美妆品牌如何突破营销困境,强势突围?

随着人们消费观念的升级&#xff0c;美妆护肤几乎成为人们的日常标配&#xff0c;不仅仅女性还有男性也开始注重管理&#xff0c;美妆产品的目标消费群体在不断扩大&#xff0c;对产品的要求也逐渐多元化&#xff0c;在这一趋势下&#xff0c;2024年美妆品牌怎么做才能突破营销…