Android JNI/NDK 入门从一到二
1. 前言
最基础的创建JNI接口的操作,可以直接看这篇文章 : 第一个Android JNI工程,
本文会基于掌握创建JNI接口的操作的基础之上,来入门JNI/NDK。
2. 在JNI中打印日志
2.1 添加log模块
记得CMake中有log模块,不然编译不过
target_link_libraries(#...省略androidlog)
2.2 添加头文件
#include <android/log.h>
2.3 定义Log方法
#define LOG_TAG "CPPLOG"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG , __VA_ARGS__) // 定义LOGD类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG , __VA_ARGS__) // 定义LOGE类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG , __VA_ARGS__) // 定义LOGE类型
2.4 进行调用
LOGD("java int value is %p", value);
3. 基础类型转换
JNI和Java基础类型可以直接进行转换
在jni.h中我们可以看到JNI的基础类型有这些,比如jint其实就是对应C++中的int32_t类型
/* Primitive types that match up with Java equivalents. */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
在C++中,_t是一种命名约定,表示某个类型。通常在命名中使用_t作为类型的后缀,以便区分该名称是一个类型而不是其他实体(例如变量或函数)。
我把它整理成了一个表格,Java基础类型和JNI基础类型相对应
| Java | Native |
|---|---|
| boolean | jboolean |
| byte | jbyte |
| char | jchar |
| short | jshort |
| int | jint |
| long | jlong |
| float | jfloat |
| double | jdouble |
3.1 编写JNI方法
在Java类中编写JNI方法
external fun callNativeInt(value:Int) : Intexternal fun callNativeByte(value:Byte) : Byteexternal fun callNativeChar(value:Char) : Charexternal fun callNativeLong(value:Long) : Longexternal fun callNativeFloat(value:Float) : Floatexternal fun callNativeDouble(value:Double) : Double
3.2 C++中编写对应的方法
extern "C"
JNIEXPORT jint JNICALL
Java_com_heiko_myopencvtest2023_NativeLib_callNativeInt(JNIEnv *env, jobject thiz, jint value) {LOGD("value:%d", value);return value + 1;
}
extern "C"
JNIEXPORT jbyte JNICALL
Java_com_heiko_myopencvtest2023_NativeLib_callNativeByte(JNIEnv *env, jobject thiz, jbyte value) {LOGD("value:%d", value);return value + 1;
}
extern "C"
JNIEXPORT jchar JNICALL
Java_com_heiko_myopencvtest2023_NativeLib_callNativeChar(JNIEnv *env, jobject thiz, jchar value) {LOGD("value:%d", value);return value + 1;
}
extern "C"
JNIEXPORT jlong JNICALL
Java_com_heiko_myopencvtest2023_NativeLib_callNativeLong(JNIEnv *env, jobject thiz, jlong value) {LOGD("value:%d", value);return value + 1;
}
extern "C"
JNIEXPORT jfloat JNICALL
Java_com_heiko_myopencvtest2023_NativeLib_callNativeFloat(JNIEnv *env, jobject thiz, jfloat value) {LOGD("value:%f", value);return value + 1.0;
}
extern "C"
JNIEXPORT jdouble JNICALL
Java_com_heiko_myopencvtest2023_NativeLib_callNativeDouble(JNIEnv *env, jobject thiz,jdouble value) {LOGD("value:%f", value);return value + 1.0;
}
3.3. 进行调用
Log.i(TAG, "result:${nativeLib.callNativeInt(1)}")
Log.i(TAG, "result:${nativeLib.callNativeByte(2)}")
Log.i(TAG, "result:${nativeLib.callNativeChar('c')}")
Log.i(TAG, "result:${nativeLib.callNativeLong(4)}")
Log.i(TAG, "result:${nativeLib.callNativeFloat(5F)}")
Log.i(TAG, "result:${nativeLib.callNativeDouble(6.0)}")
3.4 运行项目
打印日志如下
10:16:36.815 D value:1
10:16:36.815 I result:2
10:16:36.815 D value:2
10:16:36.815 I result:3
10:16:36.815 D value:99
10:16:36.815 I result:d
10:16:36.815 D value:4
10:16:36.815 I result:5
10:16:36.815 D value:5.000000
10:16:36.815 I result:6.0
10:16:36.816 D value:6.000000
10:16:36.816 I result:7.0
4. 字符串
Java字符串转成Native的字符串,并不能直接做转换,需要调用env->GetStringUTFChars(),
对应的,需要调用env->ReleaseStringUTFChars()来释放资源。
默认情况下,Java都是UTF编码,如果不是UTF编码,则需要调用
env->GetStringChars()
4.1 Java/Native字符串转换
external fun callNativeString(value:String) : String
extern "C"
JNIEXPORT jstring JNICALL
Java_com_heiko_myopencvtest2023_NativeLib_callNativeString(JNIEnv *env, jobject thiz,jstring value) {//Java字符串转成Native的字符串,并不能直接做转换const char *str = env->GetStringUTFChars(value, NULL); //Java的字符串是UTF编码的//env->GetStringChars(); //如果不是UTF编码,就用这个LOGD("str:%s", str);env->ReleaseStringUTFChars(value, str);jstring result = env->NewStringUTF("hello world!");return result;
}
进行调用
Log.i(TAG, "result:${nativeLib.callNativeString("你好呀")}")
nativeLib.stringMethod("hello world!")
执行结果
10:45:45.849 D str:你好呀
10:45:45.849 I result:hello world!
4.2 C++ 字符串的使用
定义JNI接口
external fun stringMethod(value:String)
实现C++方法
extern "C"
JNIEXPORT void JNICALL
Java_com_heiko_myopencvtest2023_NativeLib_stringMethod(JNIEnv *env, jobject thiz, jstring value) {const char *str = env->GetStringUTFChars(value, 0);int length = env->GetStringLength(value);LOGD("length:%d", length);char buf[256];env->GetStringUTFRegion(value, 0, length, buf); //拷贝字符串数据到char[]中LOGD("text:%s", buf);env->ReleaseStringUTFChars(value, str);
}
进行调用
Log.i(TAG, "result:${nativeLib.callNativeString("你好呀")}")
nativeLib.stringMethod("hello world!")
执行结果
10:45:45.849 D length:12
10:45:45.849 D text:hello world!
5. 引用类型的使用
这里列出了Java引用类型和JNI应用类型的对应关系。
值得注意的是,不是所有的Java引用类型都有对应的JNI的引用类型。
比如Java中的字符串数组String[],就没有相对应的JNI的引用类型,这种情况下,都会统一归类为jobject。
| Java Reference | Native |
|---|---|
| All objects | jobject |
| java.lang.Class | jclass |
| java.lang.String | jstring |
| Object[] | jobjectArray |
| boolean[] | jbooleanArray |
| byte[] | jbyteArray |
| java.lang.Throwable | jthrowable |
| char[] | jcharArray |
| short[] | jshortArray |
| int[] | jintArray |
| long[] | jlongArray |
| float[] | jfloatArray |
| double[] | jdoubleArray |
5.1 传递字符串数据
Java层传递一个字符串数组,然后C++层接收到后,获取这个字符串数组的第一个字符串,并打印出来。
定义JNI接口
external fun callNativeStringArray(array:Array<String>)
实现C++方法,这里因为是字符串数组,JNI中没有相对应的类型,所以需要先通过env->GetObjectArrayElement()获取到Object数组中的第一个索引的Object,再将其强转为jstring类型。
如果是JNI有对应类型的,按直接调用相关API就可以了,比如env->GetIntArrayElements()、env->GetFloatArrayElements()
extern "C"
JNIEXPORT void JNICALL
Java_com_heiko_myopencvtest2023_NativeLib_callNativeStringArray(JNIEnv *env, jobject thiz,jobjectArray array) {int len = env->GetArrayLength(array);LOGD("len:%d",len);//env->GetIntArrayElements() //获取Int数组//env->GetFloatArrayElements() //获得Float数组//env->GetObjectArrayElement() //获得JNI数组jstring result = static_cast<jstring>(env->GetObjectArrayElement(array, 0)); //获取index为0的值const char * str = env->GetStringUTFChars(result,NULL);LOGD("text[0]:%s",str);env->ReleaseStringUTFChars(result,str);
}
static_cast是进行类型的强转
进行调用
val array = arrayOf("ABC", "DEF", "GHI", "JKL", "MNO")
nativeLib.callNativeStringArray(array)
执行结果
13:27:06.865 D len:5
13:27:06.865 D text[0]:ABC
6. 传递Bitmap
这里我们以镜像Bitmap图片为例,传递Bitmap图片到JNI层,然后进行镜像操作,并将镜像后的Bitmap图片返回给Java层
6.1 获取Bitamp的信息
调用AndroidBitmap_getInfo(),用来获取Bitmap的信息。
AndroidBitmapInfo bitmapInfo;
AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);
6.2 获取Bitmap像素内容
调用AndroidBitmap_lockPixels(),用来获取Bitmap的像素内容。
同时,记得需要调用AndroidBitmap_unlockPixels()来释放资源,这两个API是配对使用的。
//拿到像素内容
void *bitmapPixels;
AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels);//释放资源
AndroidBitmap_unlockPixels(env, bitmap);
6.3 JNI中创建Bitamp
直接复制这个封装好的方法,进行调用就好
jobject generateBitmap(JNIEnv *env, uint32_t width, uint32_t height) {// 获取Bitmap类引用jclass bitmapCls = env->FindClass("android/graphics/Bitmap");// 获取Bitmap构造方法的引用jmethodID createBitmapFunction = env->GetStaticMethodID(bitmapCls, "createBitmap","(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");jstring configName = env->NewStringUTF("ARGB_8888");jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config");jmethodID valueOfBitmapConfigFunction = env->GetStaticMethodID(bitmapConfigClass, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");jobject bitmapConfig = env->CallStaticObjectMethod(bitmapConfigClass,valueOfBitmapConfigFunction, configName);jobject newBitmap = env->CallStaticObjectMethod(bitmapCls, createBitmapFunction, width, height,bitmapConfig);return newBitmap;
}
6.4 实现Bitmap镜像操作
定义JNI
external fun mirrorBitmap(bitmap: Bitmap) : Bitmap
实现C++代码
extern "C"
JNIEXPORT jobject JNICALL
Java_com_heiko_myncnnlib_NcnnNativeLib_mirrorBitmap(JNIEnv *env, jobject thiz, jobject bitmap) {AndroidBitmapInfo bitmapInfo;AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);__android_log_print(ANDROID_LOG_DEBUG, "jniBitmap", "width:%d,height:%d", bitmapInfo.width,bitmapInfo.height);//拿到像素内容void *bitmapPixels;AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels);uint32_t newWidth = bitmapInfo.width;uint32_t newHeight = bitmapInfo.height;uint32_t *newBitmapPixels = new uint32_t[newWidth * newHeight];int index = 0;//遍历Bitmap像素,将左右的像素进行互换 (镜像操作)for (int y = 0; y < newHeight; y++) {for (int x = newWidth - 1; x >= 0; x--) {uint32_t pixel = ((uint32_t *) bitmapPixels)[index++];newBitmapPixels[newWidth * y + x] = pixel;}}AndroidBitmap_unlockPixels(env, bitmap);//生成新的Bitmapjobject newBitmap = generateBitmap(env, newWidth, newHeight);void *resultBitmapPixels;AndroidBitmap_lockPixels(env, newBitmap, &resultBitmapPixels);//拷贝memcpy((uint32_t *)resultBitmapPixels, newBitmapPixels, sizeof(uint32_t) * newWidth * newHeight);AndroidBitmap_unlockPixels(env,newBitmap);delete [] newBitmapPixels;return newBitmap;
}
进行调用
var bitmap = BitmapFactory.decodeResource(resources,R.drawable.img_test)
binding.img1.setImageBitmap(bitmap)binding.btnMirrorImage.setOnClickListener {bitmap = nativeLib.mirrorBitmap(bitmap)binding.img1.setImageBitmap(bitmap)
}
进行程序,点击Button,可以发现图片执行了镜像操作。

7. 其他
7.1 CMake
关于CMake可以看我的另一篇博客 : Android NDK CMakeLists.txt 常用命令说明
7.2 参考
感谢 Android CMake以及NDK实践基础
相关文章:
Android JNI/NDK 入门从一到二
1. 前言 最基础的创建JNI接口的操作,可以直接看这篇文章 : 第一个Android JNI工程, 本文会基于掌握创建JNI接口的操作的基础之上,来入门JNI/NDK。 2. 在JNI中打印日志 2.1 添加log模块 记得CMake中有log模块,不然编译不过 ta…...
吃瓜教程3|决策树
ID3算法 假定当前样本集合D中第k类样本所占比例为pk,则样本集合D的信息熵定义为 信息增益 C4.5算法 ID3算法存在一个问题,就是偏向于取值数目较多的属性,因此C4.5算法使用了“增益率”(gain ratio)来选择划分属性 CA…...
springboot动态数据源【非伪数据源】
说明:本文章的数据源不是在配置文件中配置两个或多个数据源,在业务方面对这些数据源来回切换,本文章中的数据源是可以动态添加,修改,切换的,废话不多说。 先看工程图: 1.pom.xml文件 <?x…...
如何改善设备综合效率(OEE)并提高工厂的生产力
在现代制造业中,提高设备综合效率(Overall Equipment Efficiency,OEE)是企业追求高效生产和优化生产能力的重要目标之一。OEE是一个关键的绩效指标,可以帮助企业评估设备的利用效率、生产效率和质量水平。本文将从三个…...
一文接入Android阿里Sophix热更新
最近公司项目渐趋成熟,已经不需要经常更新版本,并且更新版本对客户的影响特别大,但是日常维护难免需要更新代码,因此热修复的技术,就比较迫切了。 经过一段时间的对比,我们最终决定使用阿里的Sophix方案&am…...
【高阶数据结构】并查集和图
目录 1.数据结构--并查集 2.数据结构--图 1.图的基础概念 2.图的简单实现 2.1.邻接矩阵的图实现 2.2.邻接表的图实现 2.3.图的DFS和BFS 2.4.最小生成树 2.4.1.Kruskal(克鲁斯卡尔算法) 2.4.2.Prim(普里姆算法) 2.5.最短路径 2.5.1.Dijkstra(…...
Git 提交时提示 GPG 签名错误
本来应该一切都是正常的,但今天提交的时候提示 GPG 签名错误。 错误的信息就是 GPG 签名失败。 gpg: skipped "942395299055675C": No secret key gpg: signing failed: No secret key error: gpg failed to sign the data fatal: failed to write commi…...
vite+vue3实现 tomcat 的本地部署
背景: 很多开发小伙伴在本地开发完前端项目后,碍于服务端环境配置麻烦,想先试试在本地部署,已开发好的前端项目,由于很多文章都是文字性描述,不太直观,为了给大多数新手提供一个教程,…...
docker+playwright
windows10 docker playwright 难点在于windows下docker的安装,以及官方hub被墙的困难。 wsl2 wsl2 ubuntu docker git clone https://gitee.com/lineuman/lcs_playwright.git npm install npx playwright test docker端口怎么映射到主机上面? 设置重…...
php框架路由实现
在PHP中也有很多框架(如Laravel、CodeIgniter)提供了路由功能。下面是一个简单的PHP路由实现原理和示例代码: 路由实现原理: 客户端发起请求,请求的URL会被传递给Web服务器。Web服务器将请求传递给PHP解释器ÿ…...
在CentOS 7中手工打造和运行xml文件配置的Servlet,然后使用curl、浏览器、telnet等三种工具各自测试
下载Openjdk并配置环境变量 https://jdk.java.net/java-se-ri/11-MR2是官网下载Openjdk 11的地方。 sudo wget https://download.java.net/openjdk/jdk11.0.0.1/ri/openjdk-11.0.0.1_linux-x64_bin.tar.gz下载openjdk 11。 sudo mkdir -p /usr/openjdk11创建目录ÿ…...
单例模式.
目录 ♫什么是单例模式 ♫饿汉式单例模式 ♫懒汉式单例模式 ♫单例模式的线程安全问题 ♪原子性 ♪内存可见性与指令重排序 ♫什么是单例模式 单例模式是一种设计模式,通过巧用Java的现有语法,实现一个只能被创建一个实例的类,并提供一个全…...
2023年MathorCup高校数学建模挑战赛大数据挑战赛赛题浅析
比赛时长为期7天的妈杯大数据挑战赛如期开赛,为了帮助大家更好的选题,首先给大家带来赛题浅析,为了方便大家更好的选题。 赛道 A:基于计算机视觉的坑洼道路检测和识别 A题,图像处理类题目。这种题目的难度数模独一档…...
c++小惊喜——stringstream
当需要读取一行字符串时,我们通常会有将这个字符串分开的想法 #include<iostream> #include<sstream> using namespace std;int main() {string str;getline(cin, str);stringstream ssin(str);string s[10];int cnt 0;while (ssin >> s[cnt]) …...
ubuntu 18.04 编译安装flexpart 10.4(2023年) —— 筑梦之路
2023年10月29日 环境说明 操作系统版本:ubuntu 18.04 python版本:3.6.9 gcc版本:7.5.0 编译安装路径:/usr/local cmake: 3.10.2 所需要的源码包我已经打包放到我的资源。 2021年1月份已经写过一篇Ubuntu 编译安装的帖子F…...
深度学习(生成式模型)——DDIM:Denoising Diffusion Implicit Models
文章目录 前言为什么DDPM的反向过程与前向过程步数绑定DDIM如何减少DDPM反向过程步数DDIM的优化目标DDIM的训练与测试 前言 上一篇博文介绍了DDIM的前身DDPM。DDPM的反向过程与前向过程步数一一对应,例如前向过程有1000步,那么反向过程也需要有1000步&a…...
HashMap的遍历方式 -- 好几次差点记不起来总结了一下
public class HashMapDemo {public static void main(String[] args) {// 创建一个HashMap并添加一些键值对Map<String, Integer> hashMap new HashMap<>();hashMap.put("Alice", 25);hashMap.put("Bob", 30);hashMap.put("Charlie"…...
PostgreSQL 两表关联更新sql
PostgreSQL两表关联更新SQL如下: UPDATE user SET username ft.name, age ft.age FROM userinfo WHERE user.id ft.id; user 要更新的表 userinfo数据来源表...
R2R 的一些小tip
批次间控制器(Run-to-run Controller),以应对高混合生产的挑战。将最优配方参数与各种工业特征相关联的模型是根据历史数据离线训练的。预测的最优配方参数在线用于调整工艺条件。 批次控制(R2R control)是一种先进的工艺控制技术,可在运行(如批次或晶圆…...
UML中类之间的六种主要关系
UML中类之间的六种主要关系: 继承(泛化)(Inheritance、Generalization), 实现(Realization),关联(Association),聚合(Aggregation),组…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
