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),组…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
