Android JNI复杂用法,回调,C++中调用Java方法
Android JNI复杂用法,回调,C++中调用Java方法
一、前言
Android JNI的 普通用法估计很多人都会,但是C++中调用Java方法很多人不熟悉,并且网上很多介绍都是片段的。
虽然C/C++调用Java不常用,但是掌握多一点还是有好处的。
Android JNI的基础知识介绍,之前已经有介绍,不熟悉的可以先看看:
Android Jni的介绍和简单Demo实现:
https://blog.csdn.net/wenzhi20102321/article/details/136291126
本文主要介绍JNI C++调用Java代码实现和相关知识,有兴趣的可以看看。
二、C++调用Java方法实现代码
1、上层代码 MainAcitvity.java
package com.demo.jnicallback;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;public class MainActivity extends AppCompatActivity {String TAG = "MainActivity.java";static {System.loadLibrary("native-lib");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Log.i(TAG, "conCreate");TextView tv = findViewById(R.id.sample_text);String jniString = stringFromJNI();Log.i(TAG, "conCreate cppCallBackMethod jniString = " + jniString);tv.setText("" + jniString);}//C++调用Java 的方法,定义成private方法,cpp也是可以调用到的,因为是通过反射过来的public void cppCallBackMethod(String name, int age) {Log.i(TAG, "cppCallBackMethod name = " + name + ",age = " + age);}//Java 调用到 cpp 的native方法public native String stringFromJNI();}
布局上未做修改,运行后的默认字符串"Hello from C++"。
Java代码这里加了一个给C++调用过来的方法,具体实现效果可以看是日志。
2、cpp代码 native-lib.cpp 代码:
#include <jni.h>
#include <string>#include <android/log.h> //添加头文件
#define LOG_TAG "native-lib.cpp" //定义TAG
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)#include <iostream>
#include <chrono>
#include <thread>extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_jnicallback_MainActivity_stringFromJNI(JNIEnv* env,jobject thiz /* this */) {std::string hello = "Hello from C++";LOGI("stringFromJNI hello = %s", hello.c_str());//c++调用Java方法:public void cppCallBackMethod(String name, int age)jobject m_object = env->NewGlobalRef(thiz);//创建对象的本地变量jclass mainActivityCls=env->FindClass("com/demo/jnicallback/MainActivity");//获取类对象jmethodID cppCallBackMethod = env->GetMethodID(mainActivityCls, "cppCallBackMethod", "(Ljava/lang/String;I)V");const char *message = "cppA";int age = 10;env->CallVoidMethod(m_object, cppCallBackMethod, env->NewStringUTF(message), age);while (age < 50) {//睡眠1秒std::chrono::seconds duration(1); // 休眠一秒钟std::this_thread::sleep_for(duration);age = age + 10;env->CallVoidMethod(m_object, cppCallBackMethod, env->NewStringUTF(message), age);}return env->NewStringUTF(hello.c_str());
}
上面代码可以看到获取类对象,是为了获取方法id;获取对象的本地变量是为了调用方法。
网上有些示例可能写法不一样,熟悉c++代码的应该知道,"env->“的写法和”(*env)."是一个意思。
3、效果日志:
//Java打印最开始日志
2024-03-01 16:27:50.401 I/MainActivity.java: conCreate
//cpp文件打印,开始的日志
2024-03-01 16:27:50.402 I/native-lib.cpp: stringFromJNI hello = Hello from C++
//cpp调用Java部分日志,在Java代码每隔一秒的打印
2024-03-01 16:27:50.402 I/MainActivity.java: cppCallBackMethod name = cppA,age = 10
2024-03-01 16:27:51.403 I/MainActivity.java: cppCallBackMethod name = cppA,age = 20
2024-03-01 16:27:52.403 I/MainActivity.java: cppCallBackMethod name = cppA,age = 30
2024-03-01 16:27:53.403 I/MainActivity.java: cppCallBackMethod name = cppA,age = 40
2024-03-01 16:27:54.404 I/MainActivity.java: cppCallBackMethod name = cppA,age = 50
//Java onCreate最后的日志,打印C++返回的字符串
2024-03-01 16:27:54.404 I/MainActivity.java: conCreate cppCallBackMethod jniString = Hello from C++
上面的代码就有Java --> C++和C++ --> Java的代码流程。
注意,这里的示例代码添加了睡眠代码,如果在主线程长时间执行任务是有可能导致ANR的。
4、cpp代码 native-lib.cpp 代码另一种写法
下面这种写法不用NewGlobalRef创建对象的本地变量。
中间的区别就是这里函数的调用没有使用"->“,使用的”(*env)."
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_jnicallback_MainActivity_stringFromJNI(JNIEnv* env,jobject thiz /* this */) {std::string hello = "Hello from C++";LOGI("stringFromJNI hello = %s", hello.c_str());//c++调用Java方法:public void cppCallBackMethod(String name, int age)jclass mainActivityCls=(*env).FindClass("com/demo/jnicallback/MainActivity");//获取类对象jmethodID cppCallBackMethod = (*env).GetMethodID(mainActivityCls, "cppCallBackMethod", "(Ljava/lang/String;I)V");const char *message = "cppA";int age = 10;(*env).CallVoidMethod(thiz, cppCallBackMethod, env->NewStringUTF(message), age);while (age < 50) {//睡眠1秒std::chrono::seconds duration(1); // 休眠一秒钟std::this_thread::sleep_for(duration);age = age + 10;(*env).CallVoidMethod(thiz, cppCallBackMethod, env->NewStringUTF(message), age);}return env->NewStringUTF(hello.c_str());
}
上面的代码运行也是一样的效果。
"->“和”(*env)."有啥区别?因为不是很熟悉,还还说不清。
上面不同写法调用方法的参数是有区别的,其实就是函数api的参数要求不同,具体可以看到jni.h的源码。
三、其他
1、C++到Java 相关api函数介绍
上面示例中使用用到的api:
(1)jobject m_object = env->NewGlobalRef(thiz);//创建对象的本地变量
(2)jclass mainActivityCls=env->FindClass("com/demo/jnicallback/MainActivity");//获取类对象(3)jmethodID cppCallBackMethod = env->GetMethodID(mainActivityCls, "cppCallBackMethod", "(Ljava/lang/String;I)V");(4)env->CallVoidMethod(m_object, cppCallBackMethod, env->NewStringUTF(message), age);
上面(1)和(2)是没什么研究价值的,NewGlobalRef和FindClass都是固定的写法。
(3)和(4)的不用方法的调用区别就比较大了,使用不同的api函数还可以修改Java的变量属性。
静态方法和动态方法调用的api函数也不一样,有返回值的方法和没有返回值的方法调用的api函数也是不一样的。
并且Java方法或者变量即使是private修饰的也不影响cpp调用过去,因为反射是不受修饰符影响的。
第3步里面的签名字符串“(Ljava/lang/String;I)V”,表示的是Java的方法和返回值的签名,唯一性;
这里面的签名字符串都是根据Java方法和方法的参数进行变化的。
下面对3、4步的代码相关知识做展开介绍。
2、调用获取不同方法和变量的api
方法、变量修饰类型表格
函数描述 | 描述 | |
---|---|---|
GetFieldID | 得到一个实例的域的ID | |
GetStaticFieldID | 得到一个静态的域的ID | |
GetMethodID | 得到一个实例的方法的ID | |
GetStaticMethodID | 得到一个静态方法的ID |
上面Jni.cpp调用Java代码已经用到部分api方法,并且从字面含义也是比较容易里面这个表格的api的具体作用。
这个表格的用于就是为了获取到方法的修饰类型,比如方法,静态方法,变量,静态变量。
毕竟不同的修饰类型,在编译过程是有差异的。所以要区分。
3、Java签名类型字符串 常用的数据类型及对应字符:
上面示例中的"(Ljava/lang/String;I)V");字符串都是根据Java的方法通过下面这个表格转换来的。
Java 类型 | Jni中表示的符号 | 备注 |
---|---|---|
boolean | Z | 不是类型首字母大写 |
byte | B | |
char | C | |
short | S | |
int | I | |
long | L | |
float | F | |
double | D | |
void | V | |
objects对象 | Lfully-qualified-class-name;L全类名; | 记得最后是有分号的 |
Arrays数组 | [array-type [数组类型 | |
methods方法 | (argument-types)return-type(参数类型)返回类型 |
这个表格是有有啥用?就更多人懵逼了。
其实这些类型符号表示的是Java方法或者属性的一个签名,唯一性,目前就是为了让Jni.cpp调用到Java代码。
举个例子就很容易清楚了:
//XXX.Javaint age;String name;public int add(int number1,int number2){System.out.println("c/C++居然调用了我");return number1+number2;}//jni.cpp 修改Java属性值和调用Java方法示例//获取类对象
jclass mainActivityCls=env->FindClass("com/zmw/jnitest/MainActivity");//获取属性的fieldId,--》这里就用到了签名类型
jfieldID ageFid = env->GetFieldID(mainActivityCls,"age","I");
jfieldID nameFid=env->GetFieldID(mainActivityCls, "name", "Ljava/lang/String;");
//获取属性值
jint age = env->GetIntField(mainActivityThis,ageFid);
jstring name = (jstring)env->GetObjectField(thiz,nameFid);//此处有编码转换问题未解决//修改属性值,C++中修改变量值后,Java重新获取打印发现是修改过的
env->SetIntField(mainActivityThis, ageFid , 11);
env->SetObjectField(thiz, nameFid,Stringvalue);//获取方法的methodId,--》这里就用到了签名类型
jmethodID addMid=env->GetMethodID(mainActivityCls, "add", "(II)I");
int result=env->CallIntMethod(mainActivityThis, addMid, 1, 1); //这里就能获取到2的值。
仔细看一下上面的代码,就大致能理解这个签名表格的具体作用:为了找到Java方法的参数和返回值的形式。
Java签名类型小结:
(1)基础类型签名那些转换都是很容易记住的,基础类型中,特别留意一下boolean类型 是 Z 就行
(2)对象Object类型的转换是:L+全包名(包名直接用 /间隔)+类名+分号
(3)数组类型签名转换:[数组类型,比如[I,表示Java的 int[](4)方法签名的转换:(参数类型)返回类型,中间多个参数类型依此填写就行,
比如:Jni中的代码:env->GetMethodID("add", "(IILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;")
如果不清楚上面的表格转换,看起来就头大,特别是那些有三四个以上参数的情况,但是学习过后就不难了,
查看表格对应关系可以知道,Java中的对应方法是:public String add(int a,int b,String c,String d)
其实就是先看括号后面的返回值,然后再一个个确定括号内的形参变量
共勉: 这短短的一生,我们最终都会过去,你不妨大胆一些,爱一个人、攀一座山、追一个梦
相关文章:
Android JNI复杂用法,回调,C++中调用Java方法
Android JNI复杂用法,回调,C中调用Java方法 一、前言 Android JNI的 普通用法估计很多人都会,但是C中调用Java方法很多人不熟悉,并且网上很多介绍都是片段的。 虽然C/C调用Java不常用,但是掌握多一点还是有好处的。…...
C++从零开始的打怪升级之路(day41)
这是关于一个普通双非本科大一学生的C的学习记录贴 在此前,我学了一点点C语言还有简单的数据结构,如果有小伙伴想和我一起学习的,可以私信我交流分享学习资料 那么开启正题 今天分享的是关于继承的知识点 1.派生类的默认成员函数 首先我…...
uni-app app实现web-view H5图片长按下载
问题和使用场景描述: uniapp app web-view中图片无法长按保存,IOS下是正常的,但是Android下长按无反应 解决方案: 下载mui.min.js,放到项目中的static下(下载见最上面的压缩包) 在static目录下新建script.js mui.…...
全量知识系统问题及SmartChat给出的答复 之5
Q15. 支持前端(知识表征)的自然语言能力 需要一个 元语言注释工具 以及两个库(叙词库和语料库)和主题词表。请 1)设计 两个库和主题词表的结构 ,2)分别设计它们的接口,3)通过调用它们…...

DolphinScheduler——工作流实例的生命周期
目录 一、DolphinScheduler架构原理 1.1 系统架构图 1.2 DolphinScheduler核心概念 1.2 创建工作流 1.2.1 如何触发一个工作流实例 1.2.2 任务调度链路监控 1.2.3 Workflow-DAG解析 DAG解析 Dispatch分发流程 Master和Worker的交互过程 1.3 任务运行状态 该篇文章主…...
阻塞和非阻塞网络io有什么区别,分别有哪些应用场景?
阻塞(Blocking)和非阻塞(Non-blocking)网络I/O是两种不同的I/O模型,它们在处理I/O操作时的行为和特点有所不同。 阻塞式网络I/O(Blocking I/O): 在阻塞式网络I/O中,当应…...

面试数据库篇(mysql)- 12分库分表
拆分策略 垂直分库 垂直分库:以表为依据,根据业务将不同表拆分到不同库中。 特点: 按业务对数据分级管理、维护、监控、扩展在高并发下,提高磁盘IO和数据量连接数垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中。 特点: 1,冷热数据分离 2,减少IO过渡争…...

LaTeX中的多行数学公式
目录 参考链接 一、gather以及gather*环境编排公式 1、 gather环境 2、 gather*环境 3、 阻止编号 二、align以及align*环境设定公式对齐方式 1、align环境 2、align*环境 三、split环境实现一个公式多行排版 四、cases环境实现分段函数 参考链接 LaTeX中的多行数学…...
绕过5秒盾Cloudflare和DDoS-GUARD
绕过5秒盾Cloudflare和DDoS-GUARD 5秒盾的特点免费版5秒盾的绕过方法付费版5秒盾的绕过方法 5秒盾的特点 <title>Just a moment...</title>例如: <!DOCTYPE html><html lang"en-US"><head><title>Just a moment...</title&…...

react 原理揭秘
1.目标 A. 能够知道setState()更新数据是异步的 B. 能够知道JSX语法的转化过程 C. 能够说出React组件的更新机制 D. 能够对组件进行性能优化 E. 能够说出虚拟DOM和Diff算法 2.目录 A. setState()的说明 B. JSX语法的转化过程 C. 组件更新机制 D. 组件性能优化 E. 虚拟DOM和D…...

el-table实现转置表格
vue版本:vue2.6.10 elementui版本:2.15.14 实现效果:el-table实现行列互换 代码: <template><div class"app-container"><span>原始数据</span><el-table:data"datas"border>…...

(3)(3.1) FlightDeck FrSky发射器应用程序
文章目录 前言 1 概述 2 Turnkey Packages 3 参数说明 前言 Craft and Theory 的 FlightDeck 可让你轻松查看飞行模式、高度、速度、姿态和关键系统警报,包括故障保护和电池错误,如电池不平衡警告和发射机低电量警报。 1 概述 Craft and Theory 的…...

【Unity】导入IAP插件后依赖冲突问题 com.android.billingclient冲突
【Unity】Attribute meta-data#com.google.android.play.billingclient.version 多版本库冲突_unity billingclient-CSDN博客 打开mainTemplate.gradle 找到dependencies { } 在里面末尾加上如下: configurations.all {exclude group: com.android.billingclien…...

docker 转为docker-compose(composerize 命令)
可以使用Composerize将Docker命令转换为Docker Compose文件。 例如:将docker run命令转换为Docker Compose格式,只需用Composerize运行它,如下所示: composerize docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/…...

【Golang切片】
切片 切片的引入内存分析切片的定义切片的遍历切片注意事项 切片的引入 【1】切片(slice)是golang中一种特有的数据类型 【2】数组有特定的用处,但是却有一些呆板(数组长度固定不可变),所以在Go语言的代码…...

React-router的创建和第一个组件
需要先学react框架 首先:找到一个文件夹,在文件夹出打开cmd窗口,输入如下图的口令 npx create-react-app demo 然后等待安装 安装完成 接下来进入创建的demo实例 cd demo 然后可以用如下方式打开vscode code . 注意:不要忽略点号与…...

计算机设计大赛 深度学习猫狗分类 - python opencv cnn
文章目录 0 前言1 课题背景2 使用CNN进行猫狗分类3 数据集处理4 神经网络的编写5 Tensorflow计算图的构建6 模型的训练和测试7 预测效果8 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 **基于深度学习猫狗分类 ** 该项目较为新颖&a…...
Linux服务器磁盘及内存用量监控Python脚本(推送钉钉群通知)
文章目录 Python 脚本钉钉推送通知定时任务 Python 脚本 # -*- coding: utf-8 -*- import subprocessdef get_disk_usage():# 执行 df 命令获取磁盘使用情况df_process subprocess.Popen([df, -h, /], stdoutsubprocess.PIPE)output, _ df_process.communicate()output out…...

Android13 Audio框架
一、Android 13音频代码结构 1、framework: android/frameworks/base 1.AudioManager.java :音频管理器,音量调节、音量UI、设置和获取参数等控制流的对外API 2.AudioService.java :音频系统服务(java层),…...

kafka消费者接收不到消息
背景: 对kafka消息进行监听,生产者发了消息,但是消费端没有接到消息,监听代码 消费端,kafka配置 spring.kafka.bootstrap-serverskafka.cestc.dmp:9591 spring.kafka.properties.sasl.jaas.configorg.apache.kafka.…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...

MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...

jdbc查询mysql数据库时,出现id顺序错误的情况
我在repository中的查询语句如下所示,即传入一个List<intager>的数据,返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致,会导致返回的id是从小到大排列的,但我不希望这样。 Query("SELECT NEW com…...