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实践基础

相关推荐
水瓶丫头站住4 小时前
安卓APP如何适配不同的手机分辨率
android·智能手机
xvch4 小时前
Kotlin 2.1.0 入门教程(五)
android·kotlin
不会飞的小龙人6 小时前
Docker Compose创建镜像服务
linux·运维·docker·容器·镜像
xvch8 小时前
Kotlin 2.1.0 入门教程(七)
android·kotlin
望风的懒蜗牛8 小时前
编译Android平台使用的FFmpeg库
android
浩宇软件开发9 小时前
Android开发,待办事项提醒App的设计与实现(个人中心页)
android·android studio·android开发
ac-er88889 小时前
Yii框架中的多语言支持:如何实现国际化
android·开发语言·php
苏金标10 小时前
The maximum compatible Gradle JVM version is 17.
android
zhangphil10 小时前
Android BitmapShader简洁实现马赛克,Kotlin(一)
android·kotlin
iofomo15 小时前
Android平台从上到下,无需ROOT/解锁/刷机,应用级拦截框架的最后一环,SVC系统调用拦截。
android