Android JNI/NDK 入门从一到二

1. 前言

最基础的创建JNI接口的操作,可以直接看这篇文章 : 第一个Android JNI工程,
本文会基于掌握创建JNI接口的操作的基础之上,来入门JNI/NDK

2. 在JNI中打印日志

2.1 添加log模块

记得CMake中有log模块,不然编译不过

target_link_libraries(
		#...省略
        android
        log)
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. 基础类型转换

JNIJava基础类型可以直接进行转换
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基础类型相对应

JavaNative
booleanjboolean
bytejbyte
charjchar
shortjshort
intjint
longjlong
floatjfloat
doublejdouble
3.1 编写JNI方法

在Java类中编写JNI方法

external fun callNativeInt(value:Int) : Int

external fun callNativeByte(value:Byte) : Byte

external fun callNativeChar(value:Char) : Char

external fun callNativeLong(value:Long) : Long

external fun callNativeFloat(value:Float) : Float

external 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 ReferenceNative
All objectsjobject
java.lang.Classjclass
java.lang.Stringjstring
Object[]jobjectArray
boolean[]jbooleanArray
byte[]jbyteArray
java.lang.Throwablejthrowable
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);
	
	//生成新的Bitmap
    jobject 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实践基础

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

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

相关文章

大模型之十九-对话机器人

大语言模型的最早应用是Chatbot&#xff0c;其实我最早接触语义理解在2014年&#xff0c;2014年做智能音箱的时候&#xff0c;那时也是国内第一批做智能音箱的&#xff0c;在现在看起来当时的智能音箱比较傻&#xff0c;很多问题无法回答&#xff0c;长下文效果也不好&#xff…

etcd的mvcc源码剖析

mvcc简介 悲观锁 在对于一些临界资源进行读写的时候&#xff0c;为了防止其他人进行同步的修改数据&#xff0c;直接将当前的数据锁住&#xff0c;不让别人使用&#xff0c;来实现并发安全 乐观锁 在对临界资源进行操作的时候&#xff0c;不锁住数据&#xff0c;实现独占&…

网络协议--TCP的交互数据流

19.1 引言 前一章我们介绍了TCP连接的建立与释放&#xff0c;现在来介绍使用TCP进行数据传输的有关问题。 一些有关TCP通信量的研究如[Caceres et al. 1991]发现&#xff0c;如果按照分组数量计算&#xff0c;约有一半的TCP报文段包含成块数据&#xff08;如FTP、电子邮件和U…

Elasticsearch跨集群检索配置

跨集群检索字面意思&#xff0c;同一个检索语句&#xff0c;可以检索到多个ES集群中的数据&#xff0c;ES集群默认是支持跨集群检索的&#xff0c;只需要动态的增加入节点即可&#xff0c;下面跟我一起来体验下ES的跨集群检索的魅力。 Elasticsearch 跨集群检索推荐的是不同集群…

linux中nginx配置https

一、版本适配 版本一定要适配&#xff0c;否则会报错各种参数定位不到不识别的错误&#xff0c;以下是版本适配信息&#xff0c;各位观客自行按照以下信息匹配版本。 Nginx 1.11.5及以上版本与OpenSSL 1.0.2及以上版本兼容。Nginx 1.15.2及以上版本与OpenSSL 1.1.1及以上版本兼…

redis的bitmap(面试题,待补充)

位图简介 如果我们需要记录某一用户在一年中每天是否有登录我们的系统这一需求该如何完成呢&#xff1f;如果使用KV存储&#xff0c;每个用户需要记录365个&#xff0c;当用户量上亿时&#xff0c;这所需要的存储空间是惊人的。 Redis 为我们提供了位图这一数据结构&#xff…

图神经网络及其在知识图谱的应用

一 应用领域 道路交通&#xff0c;动态预测 自动驾驶&#xff0c;无人机场景 化学&#xff0c;医疗等场景 物理模型相关 二 图基本模块定义 V Vertex点 E Edge 边&#xff08;向量&#xff09; U Global 图 &#xff08;例如&#xff1a;全局向量&#xff09; 无论事…

msigdbr hallmarks gsea broad研究所

使用msigdbr r包 #BiocManager::install("msigdb") #https://www.gsea-msigdb.org/gsea/msigdb #https://cran.r-project.org/web/packages/msigdbr/vignettes/msigdbr-intro.html #https://bioconductor.org/packages/release/data/experiment/vignettes/msigdb/ins…

RPC远程调用加密方法获取返回值

前言 从混淆的加密JS中还原了加密参数的具体生成流程&#xff0c;结果想从JS转python的过程中第一步就卡住了。开头密钥JS代码如下&#xff0c;但是水平有限不知道如何转为python实现(如果有大佬知道希望可以评论指点)。利用execjsjsdom来执行简化还原后的JS代码依旧无法实现。…

MGRE环境下的OSPF

实验拓扑 需求 1 R6为ISP只能配置IP地址&#xff0c;R1-R5的环回为私有网段 2 R1/4/5为全连的MGRE结构&#xff0c;R1/2/3为星型的拓扑结构&#xff0c;R1为中心站点 3 所有私有网段可以互相通讯&#xff0c;私有网段使用OSPF完成。 IP规划 配置IP R1 # interface GigabitEt…

Flutter FittedBox

&#x1f525; 英文单词FittedBox &#x1f525; Fitted 通过有道翻译如下 &#xff1a; Box 通过有道翻译如下 &#xff1a; 对 FittedBox 的理解 我们可以将 FittedBox 理解为合适的盒子&#xff0c;将其它布局放到FittedBox这样一个盒子中&#xff0c;从而实现 盒子里面的…

C++ list 的使用

目录 1. 构造函数 1.1 list () 1.2 list (size_t n, const T& val T()) 1.3 list (InputIterator first, InputIterator last) 2. bool empty() const 3. size_type size() const 4. T& front() 4. T& back() 5. void push_front (const T& val) 6.…

Elasticsearch核心技术与实战-05-elasticsearch的安装与简单配置-Windows

首先下载elasticsearch的zip包&#xff1a;下载地址 网络不通的解决方法&#xff1a;国内镜像站 es、kibana、logstash均可在华为云开元镜像站自行选择版本下载&#xff1a;下载地址 下载插件包&#xff1a; .\bin\elasticsearch-plugin install analysis-icu .\bin\elasti…

pgbackrest归档目录满,清理后写入仍报错,分析及处理

一、 背景 pgbackrest配置的归档目录/backup被写满 归档报错 No space left on device&#xff0c;wal日志堆积 解决方法直接查看第三部分 二、 问题分析及处理 1. 目录清理 首先想到的就是清理/backup目录&#xff0c;清理后剩余6T空间 但发现pgbackrest归档依旧在报错 No …

dc-5 靶机

1.扫描ip地址 2.网页 3.dirb 爆破目录 没有用 4.爆破端口 没有用 5. 文件上传漏洞 上传点 写一句话木马 蚁剑连接 1.shell反弹 蚁剑反弹 提权 使用命令 命令"find / -perm -us -type f 2>/dev/null"在整个文件系统 ("/") 中搜索设置了SUID权…

基于白鲸优化算法BWO优化的VMD-KELM光伏发电短期功率预测MATLAB代码(含详细算法介绍)

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; VMD适用于处理非线性和非平稳信号&#xff0c;例如振动信号、生物信号、地震信号、图像信号等。它在信号处理、振动分析、图像处理等领域有广泛的应用&#xff0c;特别是在提取信号中的隐含信息和去除噪声方面…

Capacitor 打包 h5 到 Android 应用,uniapp https http net::ERR_CLEARTEXT_NOT_PERMITTED

Capacitor 打包 h5 到 Android 应用&#xff0c;uniapp https http net::ERR_CLEARTEXT_NOT_PERMITTED capacitor 官网&#xff1a; https://capacitorjs.com/docs/ 项目上需要做一个 app&#xff0c;而这个 app 是用 uniapp 做的&#xff0c;里面用到了一个依赖 dom 的库&…

【Mysql】数据库三大范式

数据库三范式 &#xff1a;数据库三范式是指关系型数据库设计中的三种规范化设计原则&#xff0c;旨在减少数据冗余、提高数据一致性和可维护性。 第一范式&#xff1a;规定表中的每一列都应该是不可分割的最小单元。 为什么要这样实现呢&#xff1f; &#xff1a;举个栗子…

Kotlin(八) 数据类、单例

目录 一&#xff1a;创建数据类 二&#xff1a;单例类 一&#xff1a;创建数据类 和Java的不同&#xff0c;kotlin的数据类比较简单&#xff0c;New→Kotlin File/Class&#xff0c;在弹出的对话框中输入“Book”&#xff0c;创建类型选择“Data”。如图&#xff1a; 然后编…

VMware Horizon 8 2309 Enterprise虚拟桌面

VMware Horizon 8 2309 Enterprise虚拟桌面 一、虚拟桌面二、产品发布三、VMware Horizon 8 2309 Enterprise1.VMware Horizon 8 2309 Enterprise产品清单2.安装部署3. 优化工具总结 一、虚拟桌面 利用虚拟桌面和应用随时随地进行访问。 从云端进行管理 使用云端控制台和 Saa…