image.png
1. JNI函數(shù)詳解
java中native方法在C++代碼中一般如下:
extern "C" JNIEXPORT jstring JNICALL
Java_com_lucky_jnidemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
1. extern "C"
說明
表示下面的代碼雏掠,都采用C的編譯方式蔚出。
之所以用C的編譯方式杀赢,是因?yàn)?JNIEnv 是C語言代碼寫的旦万,避免一些函數(shù)重載等C語言中不支持的
查看JNIEnv
源碼,進(jìn)入到jni.h
萎坷,可以看到:
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv; // 如果是C++代碼范抓,用這個(gè)宏定義
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv; //如果是C代碼,用這個(gè)宏定義
typedef const struct JNIInvokeInterface* JavaVM;
#endif
繼續(xù)跟蹤 _JNIEnv
食铐,可以看到:
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
因此,env的調(diào)用方式如下:
// C++的情況如下:
Java_com_lucky_jnidemo_MainActivity_stringFromJNI(JNIEnv * env, ...)
{
env->NewStringUTF(); //詳細(xì)看源碼中的結(jié)構(gòu)體定義
}
C的情況如下:
Java_com_lucky_jnidemo_MainActivity_stringFromJNI(JNIEnv * env, ...)
{
(*env)->NewStringUTF(); //二級(jí)指針
}
2. JNIEXPORT
宏定義說明
針對Linux平臺(tái):該聲明的作用是保證在本動(dòng)態(tài)庫中聲明的方法 , 能夠在其他項(xiàng)目中可以被調(diào)用 ;
3. JNICALL
宏定義說明
此宏定義為空僧鲁,用來表示函數(shù)的調(diào)用規(guī)范虐呻。
4. jobject
與 jclass
jobject // java傳遞下來的對象象泵,就是本項(xiàng)目中 MainActivity 對象
jclass // java傳遞下來的 class 對象,就是本項(xiàng)目中的 MainActivity class
2. JNI中函數(shù)操作
1. 在C層中修改Java/Kotlin層String變量(引用類型遍歷)
Kotlin代碼:
//即將要修改的代碼熟悉
private var mName: String = "Lucky"
//native 修改String name
external fun changeStringName()
//點(diǎn)擊按鈕進(jìn)行修改
binding.btn1.setOnClickListener {
Log.i("MainActivity", "修改前:$mName")
changeStringName()
Log.i("MainActivity", "修改后:$mName")
}
C層中的代碼
extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_changeStringName(JNIEnv *env, jobject thiz) {
//獲取Class方式一
jclass mainActivityCls = env->FindClass("com/lucky/jnidemo/MainActivity");
//獲取Class方式二
//jclass mainActivityCls = env->GetObjectClass(thiz);
// jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
jfieldID nameFid = env->GetFieldID(mainActivityCls, "mName", "Ljava/lang/String;");
// void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
jstring value = env->NewStringUTF("Update Lucky");
//void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
env->SetObjectField(thiz, nameFid, value); //修改值
}
打印結(jié)果:
com.lucky.jnidemo I/MainActivity: 修改前:Lucky
com.lucky.jnidemo I/MainActivity: 修改后:Update Lucky
2. 在C層中修改Kotlin層靜態(tài)Int變量
通過apk查看編譯后的MainActivity 類斟叼,可以看到靜態(tài)mAege是在MainActivity 中
image.png
因此偶惠,這里跟修改MainActivity中的變量一樣的操作
Kotlin代碼:
companion object {
private var mAge: Int = 666
init {
System.loadLibrary("jnidemo")
}
external fun changeIntAge() //修改Int age
}
binding.btn2.setOnClickListener {
Log.i("MainActivity", "修改前:${mAge}")
changeIntAge()
Log.i("MainActivity", "修改后:${mAge}")
}
C層中的代碼
extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_00024Companion_changeIntAge(JNIEnv *env, jobject thiz) {
// 這里要注意用FindClass方式獲取,不然拿到的jcalss會(huì)是 "com/lucky/jnidemo/MainActivity$Companion"
jclass mainActivityCls = env->FindClass("com/lucky/jnidemo/MainActivity");
//jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
jfieldID ageFid = env->GetStaticFieldID(mainActivityCls, "mAge", "I");
if (ageFid == NULL) {
LOGI("%s", "no field mAge ");
return;
}
// jint 背后就是int,所以可以直接用朗涩, 但是String 必須用 jstring
int age = env->GetStaticIntField(mainActivityCls, ageFid); // 獲取之前的age
// void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
env->SetStaticIntField(mainActivityCls, ageFid, age + 10); //修改值+10
}
打印結(jié)果:
com.lucky.jnidemo I/MainActivity: 修改前:666
com.lucky.jnidemo I/MainActivity: 修改后:676
3. 在C層中修改Kotlin層double變量
kotlin層代碼:
private var mHeight: Double = 175.0
binding.btn3.setOnClickListener {
Log.i("MainActivity", "修改前:${mHeight}")
changeDoubleHeight()
Log.i("MainActivity", "修改后:${mHeight}")
}
external fun changeDoubleHeight() //修改Double height
C層中的代碼
extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_changeDoubleHeight(JNIEnv *env, jobject thiz) {
jclass mainActivityCls = env->GetObjectClass(thiz);
// jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
jfieldID numberFid = env->GetFieldID(mainActivityCls, "mHeight", "D");
// void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value)
env->SetDoubleField(thiz, numberFid, 178.55);
}
打印結(jié)果:
com.lucky.jnidemo I/MainActivity: 修改前:175.0
com.lucky.jnidemo I/MainActivity: 修改后:178.55
4. 在C層中調(diào)用Kotlin層方法
kotlin層代碼:
/***
* "(II)I"
* 被C層調(diào)用的方法
*/
fun add(number1: Int, number2: Int): Int {
return number1 + number2
}
/***
* 被C調(diào)用的方法 (Ljava/lang/String;I) Ljava/lang/String;
*/
fun showString(str: String, num: Int): String {
Log.i("MainActivity", "Value from C++ str: $str num: $num")
return "【$str】"
}
external fun callKotlinMethod() //在C層中調(diào)用Kotlin代碼
binding.btn4.setOnClickListener {
callKotlinMethod() //點(diǎn)擊按鈕
}
C層中的代碼
extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_callKotlinMethod(JNIEnv *env, jobject thiz) {
jclass mainActivitCls = env->GetObjectClass(thiz);
// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID addMid = env->GetMethodID(mainActivitCls, "add", "(II)I"); //kotlin層 fun add(number1: Int, number2: Int): Int
// jint CallIntMethod(jobject obj, jmethodID methodID, ...)
int result = env->CallIntMethod(thiz, addMid, 10, 10); //調(diào)用方法
LOGD("result:%d\n", result);
// ++++++++++++++++++++++ C調(diào)用 fun showString(str: String, num: Int): String 函數(shù)
jmethodID showStringMid = env->GetMethodID(mainActivitCls, "showString", "(Ljava/lang/String;I)Ljava/lang/String;");
// jobject (*CallObjectMethod)(jobject, jmethodID, ...);
jstring value = env->NewStringUTF("C語言調(diào)用Kotlin");
jstring resultStrJ = (jstring) env->CallObjectMethod(thiz, showStringMid, value, 10086);
const char *resultStr = env->GetStringUTFChars(resultStrJ, NULL);
LOGD("result2:%s\n", resultStr);
}
打印結(jié)果:
//C中的打印
com.lucky.jnidemo D/Lucky: result:20
com.lucky.jnidemo D/Lucky: result2:【C語言調(diào)用Kotlin】
//Kotlin中的打印
com.lucky.jnidemo I/MainActivity: Value from C++ str: C語言調(diào)用Kotlin num: 10086
5. 在C層中修改Kotlin層數(shù)組
kotlin層代碼:
binding.btn5.setOnClickListener {
val ints = intArrayOf(1, 2, 3, 4, 5, 6)
val strs = arrayOf("李小龍", "李連杰", "李元霸")
testArrayAction(99, "你好", ints, strs)
for (anInt in ints) {
Log.d("MainActivity", "(Kotlin)修改后: ints:$anInt")
}
for (str in strs) {
Log.e("MainActivity", "(Kotlin)修改后: strs:$str")
}
}
//在C層中修改數(shù)組
external fun testArrayAction(count: Int, textInfo: String, ints: IntArray, strs: Array<String>)
C層中的代碼
extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_testArrayAction(JNIEnv *env, jobject thiz, jint count, jstring text_info, jintArray ints, jobjectArray strs) {
//++++++++++++++++++++++++++ 操作Int數(shù)組
LOGD("count:%d\n", count);
const char *_textInfoStr = env->GetStringUTFChars(text_info, NULL);
LOGD("text_info:%s\n", _textInfoStr);
env->ReleaseStringUTFChars(text_info, _textInfoStr); //釋放
int intsLen = env->GetArrayLength(ints); //獲取數(shù)組個(gè)數(shù)
for (int i = 0; i < intsLen; ++i) {
jint *_ints = env->GetIntArrayElements(ints, NULL);
*(_ints + i) = (i + 1000001); //修改數(shù)組的值
LOGD("C++ _ints item:%d\n", *(_ints + i));
// JNI_OK 0 == 代表 先用操縱桿刷新到JVM忽孽,JVM會(huì)更新上層代碼。再釋放C++層數(shù)組
// JNI_COMMIT 1 == 代表 用操縱桿刷新到JVM谢床,JVM會(huì)更新上層代碼
// JNI_ABORT 2 == 代表 釋放C++層數(shù)組
env->ReleaseIntArrayElements(ints, _ints, JNI_OK); //在數(shù)組循環(huán)中兄一,一定要記得釋放
}
//++++++++++++++++++++++++++ 操作String數(shù)組
int strsLen = env->GetArrayLength(strs);
for (int i = 0; i < strsLen; ++i) {
jobject item = env->GetObjectArrayElement(strs, i); //獲取每一個(gè)值
jstring itemStr = (jstring) item;
const char *itemStr_1 = env->GetStringUTFChars(itemStr, NULL);
LOGI("C++ 修改前itemStr_:%s\n", itemStr_1);
env->ReleaseStringUTFChars(itemStr, itemStr_1);
jstring value = env->NewStringUTF("AAAAAAA");
env->SetObjectArrayElement(strs, i, value); //修改數(shù)組值
jobject item2 = env->GetObjectArrayElement(strs, i);
jstring itemStr2 = (jstring) item2;
const char *itemStr_2 = env->GetStringUTFChars(itemStr2, NULL);
LOGI("C++ 修改后itemStr_2:%s\n", itemStr_2);
env->ReleaseStringUTFChars(itemStr2, itemStr_2);
}
}
打印日志:
com.lucky.jnidemo D/Lucky: count:99
com.lucky.jnidemo D/Lucky: text_info:你好
com.lucky.jnidemo D/Lucky: C++ _ints item:1000001
com.lucky.jnidemo D/Lucky: C++ _ints item:1000002
com.lucky.jnidemo D/Lucky: C++ _ints item:1000003
com.lucky.jnidemo D/Lucky: C++ _ints item:1000004
com.lucky.jnidemo D/Lucky: C++ _ints item:1000005
com.lucky.jnidemo D/Lucky: C++ _ints item:1000006
com.lucky.jnidemo I/Lucky: C++ 修改前itemStr_:李小龍
com.lucky.jnidemo I/Lucky: C++ 修改后itemStr_2:AAAAAAA
com.lucky.jnidemo I/Lucky: C++ 修改前itemStr_:李連杰
com.lucky.jnidemo I/Lucky: C++ 修改后itemStr_2:AAAAAAA
com.lucky.jnidemo I/Lucky: C++ 修改前itemStr_:李元霸
com.lucky.jnidemo I/Lucky: C++ 修改后itemStr_2:AAAAAAA
com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000001
com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000002
com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000003
com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000004
com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000005
com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000006
com.lucky.jnidemo E/MainActivity: (Kotlin)修改后: strs:AAAAAAA
com.lucky.jnidemo E/MainActivity: (Kotlin)修改后: strs:AAAAAAA
6. 在C層中修改Kotlin層類
修改實(shí)體類,我們這里用Java類识腿,這樣修改int 變量不需要kotlin中這么麻煩出革,需要轉(zhuǎn)換Integer
kotlin層代碼:
Person類
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
binding.btn6.setOnClickListener {
val person = Person()
person.name = "張三"
person.age = 18
putObject(person, "Kotlin文本")
Log.i("MainActivity", "(Kotlin)修改后, person:$person")
}
// 只玩Student對象里面的成員
external fun putObject(person: Person, str: String) // 傳遞引用類型,傳遞對象
C層中的代碼
extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_putObject(JNIEnv *env, jobject thiz, jobject person, jstring str) {
const char *_str = env->GetStringUTFChars(str, NULL);
LOGD("_str:%s\n", _str);
env->ReleaseStringUTFChars(str, _str);
jclass mStudentClass = env->GetObjectClass(person);
// toString
jmethodID toStringMethod = env->GetMethodID(mStudentClass, "toString", "()Ljava/lang/String;");
jstring results = (jstring) env->CallObjectMethod(person, toStringMethod);
const char *result = env->GetStringUTFChars(results, NULL);
LOGD("C++ toString:%s\n", result);
env->ReleaseStringUTFChars(results, result);
// setName
jmethodID setNameMethod = env->GetMethodID(mStudentClass, "setName", "(Ljava/lang/String;)V");
jstring nameS = env->NewStringUTF("李四");
env->CallVoidMethod(person, setNameMethod, nameS);
// getName
jmethodID getNameMethod = env->GetMethodID(mStudentClass, "getName", "()Ljava/lang/String;");
jstring nameS2 = (jstring) env->CallObjectMethod(person, getNameMethod);
const char *nameS3 = env->GetStringUTFChars(nameS2, NULL);
LOGD("C++ getName:%s\n", nameS3);
env->ReleaseStringUTFChars(nameS2, nameS3);
// setAge
jmethodID setAgeMethod = env->GetMethodID(mStudentClass, "setAge", "(I)V");
env->CallVoidMethod(person, setAgeMethod, 99);
// getAge
jmethodID getAgeMethod = env->GetMethodID(mStudentClass, "getAge", "()I");
int age = env->CallIntMethod(person, getAgeMethod);
LOGD("C++ getAge:%d\n", age);
env->DeleteLocalRef(mStudentClass);
}
7. 在C層中創(chuàng)建類
Student類
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Person 類
public class Person {
private String name;
private int age;
private static Student student;
public static void putStudent(Student student) {
Person.student = student;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
Person.student = student;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", student=" + student +
'}';
}
}
kotlin層中的代碼
binding.btn7.setOnClickListener {
insertObject()
}
external fun insertObject()
C層中的代碼
extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_insertObject(JNIEnv *env, jobject thiz) {
// @Student對象
jclass studentClass = env->FindClass("com/lucky/jnidemo/Student");
jobject student = env->AllocObject(studentClass);
// setName
jmethodID setNameMethod = env->GetMethodID(studentClass, "setName", "(Ljava/lang/String;)V");
jstring value1 = env->NewStringUTF("張三");
env->CallVoidMethod(student, setNameMethod, value1);
// setAge
jmethodID setAgeMethod = env->GetMethodID(studentClass, "setAge", "(I)V");
env->CallVoidMethod(student, setAgeMethod, 99);
// @Person對象
jclass personClass = env->FindClass("com/lucky/jnidemo/Person");
jobject person = env->AllocObject(personClass); // C++ 分配一個(gè)對象出來渡讼,不會(huì)調(diào)用此對象的構(gòu)造函數(shù)
// env->NewObject(); // C++ 實(shí)例化一個(gè)對象出來骂束,會(huì)調(diào)用此對象的構(gòu)造函數(shù),相當(dāng)于: new XXX();
// void CallVoidMethod(person obj, jmethodID methodID, ...)
jmethodID setStudent = env->GetMethodID(personClass, "setStudent", "(Lcom/lucky/jnidemo/Student;)V");
env->CallVoidMethod(person, setStudent, student);
// static void putStudent
jmethodID putStudent = env->GetStaticMethodID(personClass, "putStudent", "(Lcom/lucky/jnidemo/Student;)V");
env->CallStaticVoidMethod(personClass, putStudent, student);
// TODO 釋放工作
// 釋放方式一
env->DeleteLocalRef(personClass);
env->DeleteLocalRef(student);
env->DeleteLocalRef(value1);
env->DeleteLocalRef(personClass);
env->DeleteLocalRef(person);
// 釋放方式二
/*env->GetStringUTFChars()
env->ReleaseStringUTFChars()*/
// 釋放方式三
/*new StudentCPP對象
delete StudentCPP對象*/
}