文章目录
- Android JNI 复杂数据demo ,字符串,数组,对象等数据操作
- 一、前言
- 二、Jni复制数据demo
- 1、MainActivity.java代码
- 2、native-lib.cpp 代码
- 3、Demo代码运行后日志打印
- 三、其他
- 1、示例中cpp代码调用的主要api函数
- 2、string 类型的转换和打印
- jstring打印示例代码:
- 指针数据合并示例
- 3、 Android jni cpp代码中接收jintArray并返回jintArray数据示例代码
- 4、ArrayList\
数据类型处理
- 共勉: 生活不是等待暴风雨过去,而是学会在雨中跳舞 。
一、前言
Android JNI复杂数据的传输,比如数组,字符串等数据传给底层处理,对于JNI使用不多的开发者,可能开发会比较吃力。
本文主要介绍JNI复制数据的传递和打印,不是很复杂的JNI Demo代码,但是对于复杂数据的处理的使用是很有帮助的。
Android JNI的基础知识介绍,之前已经有介绍,不熟悉的可以先看看:
Android Jni的介绍和简单Demo实现:
https://blog.csdn.net/wenzhi20102321/article/details/136291126
二、Jni复制数据demo
1、MainActivity.java代码
package com.demo.jniobject;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import java.util.Arrays;
public class MainActivity extends AppCompatActivity {
String TAG = "MainActivity.java";
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i(TAG, "onCreate");
TextView tv = findViewById(R.id.sample_text);
//返回cpp的字符串
String jniString = stringFromJNI();
Log.i(TAG, "onCreate jniString = " + jniString);
tv.setText(jniString);
//传递int值,cpp 返回int值
int intToJNIBack = intToJNI(16);
Log.i(TAG, "onCreate intToJNIBack = " + intToJNIBack);
//传递String值,cpp 返回String值
String stringToJNIBack = stringToJNI("liwenzhi");
Log.i(TAG, "onCreate stringToJNIBack = " + stringToJNIBack);
//传递String和int值,cpp返回String值
String stringAndIntToJNIBack = stringAndIntToJNI("陈wang",18);
Log.i(TAG, "onCreate stringAndIntToJNIBack = " + stringAndIntToJNIBack);
//传递String数值和int数组值,cpp返回String数值的值
String[] listStringAndListIntToJNIBack = listStringAndListIntToJNI(new String[] {"姚pengtao", "朱dejiu","周fuping"},new int[] {20,21,30});
Log.i(TAG, "onCreate listStringAndListIntToJNIBack = " + Arrays.asList(listStringAndListIntToJNIBack));
Log.i(TAG, "onCreate End jniString = " + jniString);
}
//Java到cpp并且获取返回数据方法
public native String stringFromJNI(); //返回cpp的字符串
public native int intToJNI(int age); //传递int值,cpp 返回int值
public native String stringToJNI(String name); //传递String值,cpp 返回String值
public native String stringAndIntToJNI(String name, int age); //传递String和int值,cpp返回String值
public native String[] listStringAndListIntToJNI(String[] names, int[] ages); //传递String数值和int数组值,cpp返回String数值的值
}
上面的方法包含了传递int,String,数组和返回int,String,数值的示例方法调用。
2、native-lib.cpp 代码
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOG_TAG "native-lib.cpp"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
//Java 方法: public native String stringFromJNI()
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_jniobject_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
LOGI("stringFromJNI hello = %s", hello.c_str());
return env->NewStringUTF(hello.c_str());
}
//Java 方法: public native int intToJNI(int age)
extern "C" JNIEXPORT jint JNICALL
Java_com_demo_jniobject_MainActivity_intToJNI(JNIEnv *env, jobject thiz, jint age) {
int cppAge = age +10;
return cppAge;
}
//Java 方法:public native String stringToJNI(String name)
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_jniobject_MainActivity_stringToJNI(JNIEnv *env, jobject thiz, jstring name) {
return name;//这里直接返回,想要修改字符串内容可以看看下面代码
}
//Java 方法:public native String stringAndIntToJNI(String name, int age)
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_jniobject_MainActivity_stringAndIntToJNI(JNIEnv *env, jobject thiz, jstring name,jint age) {
// 将jstring转换为C字符串
const char* c_str1 = env->GetStringUTFChars(name, nullptr);
// 进行字符串拼接,不能像Java一样,"" + 5,这样会报错,要先转换类型
// 将jint转换为C++字符串
std::string numStr = std::to_string(age);
std::string result = std::string(c_str1) + "cpp 拼接 age = " + numStr;
LOGD("stringAndIntToJNI name = %s", c_str1);
// 释放GetStringUTFChars函数申请的资源
env->ReleaseStringUTFChars(name, c_str1);
// 将C字符串转换为jstring
return env->NewStringUTF(result.c_str());
}
//Java 方法:public native String[] listStringAndListIntToJNI(String[] names, int[] ages)
extern "C" JNIEXPORT jobjectArray JNICALL
Java_com_demo_jniobject_MainActivity_listStringAndListIntToJNI(JNIEnv *env, jobject thiz,jobjectArray names, jintArray ages) {
// 获取数组长度
jint length = env->GetArrayLength(names);
// 创建一个新的数组,用于存储修改后的数据
jobjectArray newArray = env->NewObjectArray(length, env->FindClass("java/lang/String"), nullptr);
// 获取原始int数组的指针
jint *originalArray = env->GetIntArrayElements(ages, nullptr);
// 遍历原始数组
for (int i = 0; i < length; i++) {
// 获取原始数组元素
jstring element = (jstring) env->GetObjectArrayElement(names, i);
jint elementInt = originalArray[i] + 2;
std::string numStr = std::to_string(elementInt);
// 将原始字符串转换为新的字符串
const char *c_str = env->GetStringUTFChars(element, nullptr);
std::string modifiedStr = "Modified: ";
modifiedStr += c_str;
modifiedStr += ",age =";
modifiedStr += numStr;
//释放String
env->ReleaseStringUTFChars(element, c_str);
LOGE("listStringAndListIntToJNI modifiedStr = %s", modifiedStr.c_str());
// 创建一个新的字符串对象,并将其设置到新的数组中
jstring newElement = env->NewStringUTF(modifiedStr.c_str());
env->SetObjectArrayElement(newArray, i, newElement);
}
// 释放原始int数组的指针
env->ReleaseIntArrayElements(ages, originalArray, 0);
// 返回修改后的数组
return newArray;
}
里面的代码是不难,但是没写过的,就不知道用哪些api,字符串怎么修改,怎么拼接,打印;
并且这些知识在网上还是比较片段的,很多是待验证的。
3、Demo代码运行后日志打印
2024-03-04 16:24:44.773 I/MainActivity.java: onCreate
2024-03-04 16:24:44.773 I/native-lib.cpp: stringFromJNI hello = Hello from C++
2024-03-04 16:24:44.773 I/MainActivity.java: onCreate jniString = Hello from C++
2024-03-04 16:24:44.773 I/MainActivity.java: onCreate intToJNIBack = 26
2024-03-04 16:24:44.773 I/MainActivity.java: onCreate stringToJNIBack = liwenzhi
2024-03-04 16:24:44.773 D/native-lib.cpp: stringAndIntToJNI name = 陈wang
2024-03-04 16:24:44.773 I/MainActivity.java: onCreate stringAndIntToJNIBack = 陈wangcpp 拼接 age = 18
2024-03-04 16:24:44.773 E/native-lib.cpp: listStringAndListIntToJNI modifiedStr = Modified: 姚pengtao,age =22
2024-03-04 16:24:44.773 E/native-lib.cpp: listStringAndListIntToJNI modifiedStr = Modified: 朱dejiu,age =23
2024-03-04 16:24:44.773 E/native-lib.cpp: listStringAndListIntToJNI modifiedStr = Modified: 周fuping,age =32
2024-03-04 16:24:44.773 I/MainActivity.java: onCreate listStringAndListIntToJNIBack = [Modified: 姚pengtao,age =22, Modified: 朱dejiu,age =23, Modified: 周fuping,age =32]
2024-03-04 16:24:44.773 I/MainActivity.java: onCreate End jniString = Hello from C++
上面日志不仅包含Java代码的日志,同时也包含了cpp文件的日志。
三、其他
1、示例中cpp代码调用的主要api函数
(1)const char* c_str1 = env->GetStringUTFChars(name, nullptr);
把jstring name 数据转换成指针对象。
(2)jint length = env->GetArrayLength(names);
获取 jobjectArray names 数值数据的长度,其他类型的数值数据也是使用这个api就行
(3)jobjectArray newArray = env->NewObjectArray(length, env->FindClass("java/lang/String"),nullptr);
创建一个length长度的,string数组对象
其他类型的数值数据创建就一定不是用NewObjectArray这个函数,
比如创建int数组数据类型就用 jintArray newArray = env->NewIntArray(length);
其他的基础类型数据把Int换成其他类型就行了。
(4)env->SetObjectArrayElement(newArray, i, newElement);
给数组 newArray的序号位置 i,设置string数值 std::string modifiedStr
其他类型的数值数据创建就一定不是用SetObjectArrayElement这个函数,
比如,设置int的数值类型的数据:
jint *originalArray; originalArray[1] = 100; //直接就可以替换
(5)jint *originalArray = env->GetIntArrayElements(ages, nullptr);
获取 jintArray ages的指针对象。
直接使用下标就可以获取到对应的值,比如;int a = originalArray[i]
这里只是举例说明了某些函数api 的使用,如果会使用这些,那么其他的基础类型那些是没有问题的。
至于其他一些复杂类型,比如自定义的类操作处理,这些都是不常用的,有需要的自己摸索看看吧。
2、string 类型的转换和打印
在jni cpp代码中,基础类型是不用转换就可以直接使用的,但是对象类型的数据要一定要转换的。
比如下面的简单赋值示例:
//基础类型,其他类型比如jlong,jchar也是一样的
jint number;
int num = number + 10;//是没有问题的
jstring name;
std::string hello = name; //未转换,直接赋值是会报错的
const char *c_str = name;//类型是不对应的,会报错
jstring打印示例代码:
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_jniobject_MainActivity_stringToJNI(JNIEnv *env, jobject thiz, jstring name) {
// 将jstring转换为C字符串, 打印指针数据
const char *c_str1 = env->GetStringUTFChars(name, nullptr);
LOGD("stringToJNI name = %s", c_str1);
//打印int转换后的string数据
int age = 20;
LOGD("stringToJNI age = %d", age );
std::string numStr = "age = " + std::to_string(age);
LOGD("stringToJNI numStr = %s", numStr.c_str());
return name;
}
cpp中 string类型数据是可以直接使用"+"进行拼接的,拼接后可以直接打印出来;
int或者其他基本数据类型,如果要和string拼接就要先转换成string类型;
numStr.c_str()其实也是转换成了指针类型数据,
所以"%s"打印,打印的是指针数据,知道了指针的第一个位置,字符串后面的数据就都知道了。
如果要char *类型的数据拼接,就没那么简单了,需要自己对指针位置进行控制,比如:
指针数据合并示例
//合并两个指针数据的实现函数
char* concatenateStrings(const char* a, const char* b) {
// 获取两个字符串的长度
size_t lenA = strlen(a);
size_t lenB = strlen(b);
// 分配足够的内存来存储合并后的字符串
char* result = new char[lenA + lenB + 1];
// 将第一个字符串复制到合并后的字符串中
strcpy(result, a);
// 将第二个字符串追加到合并后的字符串的末尾
strcat(result, b);
return result;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_jniobject_MainActivity_stringToJNI(JNIEnv *env, jobject thiz, jstring name) {
// 将jstring转换为C字符串, 打印指针数据
const char *c_str1 = env->GetStringUTFChars(name, nullptr);
LOGD("stringToJNI name = %s", c_str1);
//测试两个指针数据的合并和打印
const char* strA = "Hello, ";
const char* strB = "world!";
// 合并两个字符串
char* mergedString = concatenateStrings(strA, strB);
LOGD("stringToJNI mergedString = %s", mergedString);
}
上面可以看到,指针数据处理需要用到一下api函数。
3、 Android jni cpp代码中接收jintArray并返回jintArray数据示例代码
#include <jni.h>
extern "C" JNIEXPORT jintArray JNICALL Java_com_example_example_MyClass_modifyIntArray(JNIEnv *env, jobject obj, jintArray array) {
// 获取数组长度
jint length = env->GetArrayLength(array);
// 创建一个新的int数组,用于存储修改后的数据
jintArray newArray = env->NewIntArray(length);
// 获取原始int数组的指针
jint *originalArray = env->GetIntArrayElements(array, nullptr);
// 遍历原始数组
for (int i = 0; i < length; i++) {
// 修改原始数组中的元素
originalArray[i] = originalArray[i] * 2;
}
// 将修改后的数据设置到新的int数组中
env->SetIntArrayRegion(newArray, 0, length, originalArray);
// 释放原始int数组的指针
env->ReleaseIntArrayElements(array, originalArray, 0);
// 返回修改后的int数组
return newArray;
}
上面的创建int类型数值列表的函数和设置数值数据的函数与string类型数组的数据的创建和设置函数是不一样的。
4、ArrayList<String> 数据类型处理
比如,一个包含 ArrayList<String> 类型的jni方法:
//返回的也是 ArrayList<String> 数据类型
public native ArrayList<String> modifyArrayList(ArrayList<String> name);
实际代码中,我就不会这样做了,一般用String[] 就比较好处理,返回再转换成ArrayList会简单很多。
但是如果一定要实现也也是可以的,就是要写反射代码。
其实不管是,String还是ArrayList 或者 ArrayList<String>数据,对于cpp代码来说都是object对象。
对象里面的操作是要通过反射进行的,相对来说比较麻烦,
比如要先获取对象调用某个api获取里面的某个数据,再调用某个api设置某个数据。
cpp代码实现:
#include <jni.h>
extern "C" JNIEXPORT jobject JNICALL Java_com_example_example_MyClass_modifyArrayList(JNIEnv *env, jobject obj, jobject arrayList) {
// 获取ArrayList类
jclass arrayListClass = env->GetObjectClass(arrayList);
// 获取ArrayList的size()方法
jmethodID sizeMethod = env->GetMethodID(arrayListClass, "size", "()I");
// 调用size()方法获取ArrayList的大小
jint size = env->CallIntMethod(arrayList, sizeMethod);
// 获取ArrayList的get()方法
jmethodID getMethod = env->GetMethodID(arrayListClass, "get", "(I)Ljava/lang/Object;");
// 获取ArrayList的add()方法
jmethodID addMethod = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
// 创建一个新的ArrayList用于存储修改后的数据
jobject newArray = env->NewObject(arrayListClass, env->GetMethodID(arrayListClass, "<init>", "()V"));
// 遍历原始ArrayList
for (int i = 0; i < size; i++) {
// 调用get()方法获取ArrayList的元素
jstring element = (jstring) env->CallObjectMethod(arrayList, getMethod, i);
// 将原始字符串转换为C字符串
const char *c_str = env->GetStringUTFChars(element, nullptr);
std::string modifiedStr = "Modified: ";
modifiedStr += c_str;
env->ReleaseStringUTFChars(element, c_str);
// 创建一个新的String对象
jstring newElement = env->NewStringUTF(modifiedStr.c_str());
// 调用add()方法将新的String对象添加到新的ArrayList中
env->CallBooleanMethod(newArray, addMethod, newElement);
}
// 返回修改后的ArrayList
return newArray;
}
上述代码定义了一个名为modifyArrayList
的JNI函数,该函数接收一个ArrayList参数,
并对其中的String元素进行修改(在每个元素前添加"Modified: "),然后返回修改后的ArrayList。
在代码中,使用JNI函数来获取ArrayList类和其中的方法,通过调用方法来操作ArrayList和其中的元素。
你可以将上述代码添加到你的CPP文件中,并根据你的需求进行适当修改和调整。
记得使用JNI提供的函数来获取和操作ArrayList中的元素,并创建新的ArrayList来存储修改后的数据。
最后,记得返回新的ArrayList对象。
虽然看完本文不一定能掌握上面的全部知识,但是对于复杂数据处理会有一些概念和认识。
可以先记录、收藏,到时候不明白的时候再看看,就可以大大提高效率了。