Android JNI/NDK 入门从一到二

1. 前言

最基础的创建JNI接口的操作,可以直接看这篇文章 : 第一个Android JNI工程

本文会基于掌握创建JNI接口的操作的基础之上,来入门JNI/NDK

2. 在JNI中打印日志

2.1 添加log模块

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

cpp 复制代码
target_link_libraries(
		#...省略
        android
        log)
2.2 添加头文件
cpp 复制代码
#include <android/log.h>
2.3 定义Log方法
cpp 复制代码
#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 进行调用
cpp 复制代码
LOGD("java int value is %p", value);

3. 基础类型转换

JNIJava基础类型可以直接进行转换

jni.h中我们可以看到JNI的基础类型有这些,比如jint其实就是对应C++中的int32_t类型

cpp 复制代码
/* 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方法

kotlin 复制代码
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++中编写对应的方法
cpp 复制代码
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. 进行调用
kotlin 复制代码
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字符串转换
kotlin 复制代码
external fun callNativeString(value:String) : String
cpp 复制代码
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;
}

进行调用

kotlin 复制代码
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接口

kotlin 复制代码
external fun stringMethod(value:String)

实现C++方法

cpp 复制代码
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);
}

进行调用

kotlin 复制代码
Log.i(TAG, "result:${nativeLib.callNativeString("你好呀")}")
nativeLib.stringMethod("hello world!")

执行结果

cpp 复制代码
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接口

kotlin 复制代码
external fun callNativeStringArray(array:Array<String>)

实现C++方法,这里因为是字符串数组,JNI中没有相对应的类型,所以需要先通过env->GetObjectArrayElement()获取到Object数组中的第一个索引的Object,再将其强转为jstring类型。

如果是JNI有对应类型的,按直接调用相关API就可以了,比如env->GetIntArrayElements()env->GetFloatArrayElements()

cpp 复制代码
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是进行类型的强转

进行调用

cpp 复制代码
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的信息。

cpp 复制代码
AndroidBitmapInfo bitmapInfo;
AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);
6.2 获取Bitmap像素内容

调用AndroidBitmap_lockPixels(),用来获取Bitmap的像素内容。

同时,记得需要调用AndroidBitmap_unlockPixels()来释放资源,这两个API是配对使用的。

cpp 复制代码
//拿到像素内容
void *bitmapPixels;
AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels);

//释放资源
AndroidBitmap_unlockPixels(env, bitmap);
6.3 JNI中创建Bitamp

直接复制这个封装好的方法,进行调用就好

cpp 复制代码
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

kotlin 复制代码
external fun mirrorBitmap(bitmap: Bitmap) : Bitmap

实现C++代码

kotlin 复制代码
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;
}

进行调用

kotlin 复制代码
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实践基础

相关推荐
似霰11 分钟前
安卓adb shell串口基础指令
android·adb
fatiaozhang95272 小时前
中兴云电脑W102D_晶晨S905X2_2+16G_mt7661无线_安卓9.0_线刷固件包
android·adb·电视盒子·魔百盒刷机·魔百盒固件
CYRUS_STUDIO3 小时前
Android APP 热修复原理
android·app·hotfix
鸿蒙布道师4 小时前
鸿蒙NEXT开发通知工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师4 小时前
鸿蒙NEXT开发网络相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
大耳猫4 小时前
【解决】Android Gradle Sync 报错 Could not read workspace metadata
android·gradle·android studio
ta叫我小白4 小时前
实现 Android 图片信息获取和 EXIF 坐标解析
android·exif·经纬度
dpxiaolong5 小时前
RK3588平台用v4l工具调试USB摄像头实践(亮度,饱和度,对比度,色相等)
android·windows
tangweiguo030519877 小时前
Android 混合开发实战:统一 View 与 Compose 的浅色/深色主题方案
android
老狼孩111227 小时前
2025新版懒人精灵零基础及各板块核心系统视频教程-全分辨率免ROOT自动化开发
android·机器人·自动化·lua·脚本开发·懒人精灵·免root开发