一.概述
上一篇博文《Android:JNI實(shí)戰(zhàn),加載三方庫、編譯C/C++》講解了如何搭建一個(gè)可以加載和鏈接第三方庫、編譯C/C++文件的Jni Demo?App。
這篇博文在這個(gè)Jni Demo App的基礎(chǔ)上,從實(shí)戰(zhàn)出發(fā)詳細(xì)講解?Jni?開發(fā)語法。
接下來,先用一小節(jié)將Jni開發(fā)比較重要的理論知識(shí)點(diǎn)過一下,然后進(jìn)行代碼實(shí)戰(zhàn)演練。
二.理論
2.1?JavaVM 和 JNIEnv
JavaVM 和 JNIEnv 是定義在 jni.h 頭文件中最關(guān)鍵的兩個(gè)結(jié)構(gòu)體:
- JavaVM: 代表 Java 虛擬機(jī),每個(gè) Java進(jìn)程有且僅有一個(gè)全局的 JavaVM 對(duì)象,JavaVM 可以跨線程共享;
- JNIEnv: 代表 Java運(yùn)行環(huán)境,每個(gè) Java線程都有各自獨(dú)立的 JNIEnv 對(duì)象,JNIEnv 不可以跨線程共享。
JavaVM 和 JNIEnv 的類型定義在 C 和 C++ 中略有不同,但本質(zhì)上是相同的,內(nèi)部由一系列指向虛擬機(jī)內(nèi)部的函數(shù)指針組成。
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
結(jié)構(gòu)體詳細(xì)定義可翻閱 Jni.h 頭文件,在此不作代碼列舉
2.2.Jni 基礎(chǔ)數(shù)據(jù)類型:
Jni 的數(shù)據(jù)類型都在?jni.h 頭文件中定義,包括基礎(chǔ)數(shù)據(jù)類型(int 等)和引用數(shù)據(jù)類型(Object、Class、數(shù)組等)
(1).C/C++基礎(chǔ)數(shù)據(jù)類型:
/* 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 */
/* "cardinal indices and sizes" */
typedef jint jsize;
(2).C++引用數(shù)據(jù)類型:
#ifdef __cplusplus
/*
* Reference types, in C++
*/
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;
(3).C引用數(shù)據(jù)類型:
/*
* Reference types, in C.
*/
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;
(4).與Java數(shù)據(jù)類型映射表:
Java 類型 | JNI 類型 | 描述 | 長度(字節(jié)) |
---|---|---|---|
boolean | jboolean | unsigned char | 1 |
byte | jbyte | signed char | 1 |
char | jchar | unsigned short | 2 |
short | jshort | signed short | 2 |
int | jint、jsize | signed int | 4 |
long | jlong | signed long | 8 |
float | jfloat | signed float | 4 |
double | jdouble | signed double | 8 |
Class | jclass | Class 類對(duì)象 | 1 |
String | jstrting | 字符串對(duì)象 | / |
Object | jobject | 對(duì)象 | / |
Throwable | jthrowable | 異常對(duì)象 | / |
boolean[] | jbooleanArray | 布爾數(shù)組 | / |
byte[] | jbyteArray | byte 數(shù)組 | / |
char[] | jcharArray | char 數(shù)組 | / |
short[] | jshortArray | short 數(shù)組 | / |
int[] | jinitArray | int 數(shù)組 | / |
long[] | jlongArray | long 數(shù)組 | / |
float[] | jfloatArray | float 數(shù)組 | / |
double[] | jdoubleArray | double 數(shù)組 | / |
2.3 JNI 訪問 Java 字段 (成員變量)
Jni?訪問 Java 字段的流程分為 2 步:
-
1.通過 jclass 獲取字段 ID。例:
Fid = env->GetFieldId(clz, "name", "Ljava/lang/String;");
-
2.通過字段 ID 訪問字段。例:
Jstr = env->GetObjectField(thiz, Fid);
Java 字段分為靜態(tài)字段和非靜態(tài)字段,相關(guān)方法如下:
- GetFieldId:獲取非靜態(tài)字段 ID
- GetStaticFieldId:獲取靜態(tài)字段 ID
- GetField:獲取類型為 Type 的非靜態(tài)字段(例如 GetIntField)
- SetField:設(shè)置類型為 Type 的非靜態(tài)字段(例如 SetIntField)
- GetStaticField:獲取類型為 Type 的靜態(tài)字段(例如 GetStaticIntField)
- SetStaticField:設(shè)置類型為 Type 的靜態(tài)字段(例如 SetStaticIntField)
2.4 Jni?調(diào)用 Java 方法
Jni?訪問 Java 方法與訪問 Java 字段類似,訪問流程分為 2 步:
-
1、通過 jclass 獲取「方法 ID」。例:
Mid = env->GetMethodID(jclass, "helloJava", "()V");
-
2、通過方法 ID 調(diào)用方法。例:
env->CallVoidMethod(thiz, Mid);
Java 方法分為靜態(tài)方法和非靜態(tài)方法,相關(guān)方法如下:
- GetMethodId:獲取非靜態(tài)方法 ID
- GetStaticMethodId:獲取靜態(tài)方法 ID
- CallMethod:調(diào)用返回類型為 Type 的非靜態(tài)方法(例如 GetVoidMethod)
- CallStaticMethod:調(diào)用返回類型為 Type 的靜態(tài)方法(例如 CallStaticVoidMethod)
- CallNonvirtualMethod:調(diào)用返回類型為 Type 的父類方法(例如 CallNonvirtualVoidMethod)
2.5?描述符:
Jni在調(diào)用Java 字段(成員變量)或函數(shù)時(shí),需要用描述符對(duì)變量、函數(shù)參數(shù) 、函數(shù)返回值的類型進(jìn)行簽名描述。
字段描述符:描述字段(成員變量)的類型。
JVM 對(duì)每種基礎(chǔ)數(shù)據(jù)類型定義了固定的描述符,而引用類型則是以 L 開頭的形式:
Java 類型 | 描述符 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
floag | F |
double | D |
void | V |
引用類型 | 以 L 開頭 ; 結(jié)尾,中間是 / 分隔的包名和類名。例如 String 的字段描述符為 Ljava/lang/String; |
方法描述符:?描述方法的返回值類型和參數(shù)表類型
參數(shù)類型用一對(duì)圓括號(hào)括起來,按照參數(shù)聲明順序列舉參數(shù)類型,返回值出現(xiàn)在括號(hào)后面。
例如方法 void fun()?的名稱為 fun,方法描述符為 ()V
三.實(shí)戰(zhàn)
上一篇博文中Jni Demo App的代碼結(jié)構(gòu)圖
3.1 Java調(diào)用Jni?
(1).Java從Jni?獲取一個(gè)String
這也是AndroidStudio默認(rèn)創(chuàng)建的Native C++ Demo里的
jnidemo.cpp
Jni函數(shù)名前綴要與Java聲明Native函數(shù)所在文件的包名+文件名對(duì)應(yīng)
extern "C" {
JNIEXPORT jstring JNICALL
Java_com_android_demo_jni_JNIDEMO_JavaGetStringFromJNI(JNIEnv *env, jobject instance) {
string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
}
JNIDEMO.java
public class JNIDEMO {
//Java從Jni獲取String
public native String JavaGetStringFromJNI();
}
JniActivity.java
通過JNIDEMO實(shí)例對(duì)象調(diào)用聲明的Native方法,實(shí)現(xiàn)對(duì)Jni函數(shù)的調(diào)用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
String jniStr = mJniDemo.JavaGetStringFromJNI();
Log.v(TAG, "jniStr:" + jniStr);
}
}
日志打印:
(2).Java傳一個(gè)Int[]到Jni,Jni直接處理Int[]數(shù)據(jù)
jnidemo.cpp
extern "C" {
/***** java傳遞一個(gè)Int[]到Jni, Jni賦值后再返回給Java *****/
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JavaJniTransIntArray(JNIEnv *env, jobject instance,
jintArray javaArr) {
//獲取Java數(shù)組長度
int lenght = env->GetArrayLength(javaArr);
//GetIntArrayElements() 函數(shù)作用就是將 jintArray 轉(zhuǎn)為 int* 指針; 將本地指針指向含有Java端數(shù)組的內(nèi)存地址
//依賴Jvm的具體實(shí)現(xiàn),可能是鎖住Java端的那段數(shù)組不被回收(增加引用計(jì)數(shù)),也可能所Jvm在堆上對(duì)該數(shù)組的一份拷貝,速度和效率比GetIntArrayRegion方法要高很多
int *arrp = env->GetIntArrayElements(javaArr, 0);
//對(duì)數(shù)組元素進(jìn)行處理
for (int i = 0; i < lenght; i++) {
*(arrp + i) += i;
}
//將C數(shù)組種的元素拷貝到Java數(shù)組中
env->SetIntArrayRegion(javaArr, 0, lenght, arrp);
//如果需要,可以返回?cái)?shù)組
//return javaArr;
}
}
JNIDEMO.java
public class JNIDEMO {
//Java傳一個(gè)Int[]到Jni進(jìn)行數(shù)據(jù)處理
public native void JavaJniTransIntArray(int[] arrInt);
}
JniActivity.java
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//Java傳一個(gè)int[]到Jni,Jni對(duì)int[]元素進(jìn)行修改
int[] arrInt = new int[10];
Log.v(TAG, "BeforCallJni arrInt[]:" + Arrays.toString(arrInt));
mJniDemo.JavaJniTransIntArray(arrInt);
Log.v(TAG, "AfterCallJni arrInt[]:" + Arrays.toString(arrInt));
}
}
日志打印:
可以看到在調(diào)用Jni函數(shù)之前,數(shù)組元素都是初始值0,經(jīng)過Jni處理之后數(shù)值就改變了
(3).Java傳一個(gè)byte[]到Jni,Jni直接處理byte[]數(shù)據(jù)
jnidemo.cpp
extern "C" {
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JavaJniTransByteArray1(JNIEnv *env, jobject instance,
jbyteArray javaArr) {
//獲取Java數(shù)組長度
int lenght = env->GetArrayLength(javaArr);
//將 jbyteArray 轉(zhuǎn)為 int* 指針,使用本地指針指向含有Java端數(shù)組的內(nèi)存地址
//依賴Jvm的具體實(shí)現(xiàn),可能是鎖住Java端的那段數(shù)組不被回收(增加引用計(jì)數(shù)),也可能所Jvm在堆上對(duì)該數(shù)組的一份拷貝,速度和效率比GetIntArrayRegion方法要高很多
jbyte *arrp = env->GetByteArrayElements(javaArr, 0);
//另外兩種方式
//signed char jbp1[lenght];
//signed char *jbp2 = env->GetByteArrayElements(javaArr, 0);
//對(duì)數(shù)組元素進(jìn)行處理
for (int i = 0; i < lenght; i++) {
*(arrp + i) += i;
}
//將C數(shù)組中的元素拷貝到Java數(shù)組中
env->SetByteArrayRegion(javaArr, 0, lenght, arrp);
//如果需要,可以返回?cái)?shù)組
//return javaArr;
}
}
JNIDEMO.java
public class JNIDEMO {
//Java傳一個(gè)byte[]到Jni,Jni對(duì)byte[]數(shù)據(jù)處理
public native void JavaJniTransByteArray1(byte[] arrByte);
}
JniActivity.java
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//Java傳一個(gè)byte[]到Jni,Jni對(duì)int[]元素進(jìn)行修改
byte[] arrbyte1 = new byte[10];
Log.v(TAG, "BeforCallJni arrbyte1[]:" + Arrays.toString(arrbyte1));
mJniDemo.JavaJniTransByteArray1(arrbyte1);
Log.v(TAG, "AfterCallJni arrbyte1[]:" + Arrays.toString(arrbyte1));
}
}
日志打印:
(4).Java傳一個(gè)byte[]到Jni,Jni拷貝數(shù)據(jù)到native byte[],處理數(shù)據(jù)后再返回native byte[]給Java
jnidemo.cpp
extern "C" {
JNIEXPORT jbyteArray JNICALL
Java_com_android_demo_jni_JNIDEMO_JavaJniTransByteArray2(JNIEnv *env, jobject instance,
jbyteArray javaArr) {
//獲取Java數(shù)組長度
int lenght = env->GetArrayLength(javaArr);
//新建一個(gè)jni byte指針,指向一塊 byte數(shù)據(jù)內(nèi)存
jbyte *jbp = (jbyte *) malloc(lenght * sizeof(jbyte));
//直接將Java端的數(shù)組拷貝到本地jni內(nèi)存中
env->GetByteArrayRegion(javaArr, 0, lenght, jbp);
//對(duì)數(shù)組元素進(jìn)行處理
for (int i = 0; i < lenght; i++) {
*(jbp + i) += i;
}
//新建一個(gè)jni byte數(shù)組
jbyteArray arrjb = env->NewByteArray(lenght);
//將jni byte指針?biāo)竷?nèi)存中的元素拷貝到生成的C數(shù)組中,然后返回
env->SetByteArrayRegion(arrjb, 0, lenght, jbp);
//如果需要,可以返回?cái)?shù)組
return arrjb;
}
}
JNIDEMO.java
public class JNIDEMO {
//Java傳一個(gè)byte[]到Jni,Jni拷貝數(shù)據(jù)到native byte[],處理數(shù)據(jù)后再返回native byte[]給Java
public native byte[] JavaJniTransByteArray2(byte[] arrByte);
}
JniActivity.java
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
byte[] arrbyte2 = new byte[10];
byte[] arrbyte3 = new byte[10];
Log.v(TAG, "BeforCallJni arrbyte2[]:" + Arrays.toString(arrbyte2));
Log.v(TAG, "BeforCallJni arrbyte3[]:" + Arrays.toString(arrbyte3));
arrbyte3 = mJniDemo.JavaJniTransByteArray2(arrbyte2);
Log.v(TAG, "AfterCallJni arrbyte2[]:" + Arrays.toString(arrbyte2));
Log.v(TAG, "BeforCallJni arrbyte3[]:" + Arrays.toString(arrbyte3));
}
}
日志打?。?/strong>
新建了兩個(gè)byte[],arrbyte2[]?和 arrbyte3[]?。
arrbyte2[]?作為參數(shù)傳遞到Jni,arrbyte3[]?用于被Jni返回的Native byte[]賦值
可以看到,arrbyte2[]?,arrbyte3[]?在調(diào)用JavaJniTransByteArray2()之前,都是初始值0,在調(diào)用之后,由于arrbyte2[]在Jni中其元素并沒有被改變,所以打印出來仍然都是0,而arrbyte3[]元素值則是Jni返回的Native byte[]的元素值。
(5).Java調(diào)用Jni啟動(dòng)一個(gè)線程
jnidemo.h
extern "C" {
bool running = false;
void *JniThreadStartByJava(void *arg);
}
jnidemo.cpp
extern "C" {
/***** java 啟動(dòng)一個(gè) jni 線程 *****/
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JavaStartJNIThread(JNIEnv *env, jobject instance) {
pthread_t myThread;
int res = pthread_create(&myThread, NULL, JniThreadStartByJava, NULL);
if (res != 0) {
LOGW("JniThreadStartByJava create failed!");
return;
}
}
void *JniThreadStartByJava(void *arg) {
LOGW("JniThreadStartByJava create success!");
while (running) {
//do the thread thing...
}
return NULL;
}
}
JNIDEMO.java
public class JNIDEMO {
//Java啟動(dòng)一個(gè)Jni線程
public native void JavaStartJNIThread();
}
JniActivity.java
通過JNIDEMO實(shí)例對(duì)象調(diào)用聲明的Native方法,實(shí)現(xiàn)對(duì)Jni函數(shù)的調(diào)用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//java啟動(dòng)一個(gè)jni線程
mJniDemo.JavaStartJNIThread();
}
}
日志打印:
可以看到,在?Java?成功的創(chuàng)建和啟動(dòng)?Jni?中的一個(gè)線程
3.2 Jni調(diào)用Java
(1).Jni調(diào)用Java非靜態(tài)成員變量
jnidemo.cpp
extern "C" {
/**** jni訪問java非靜態(tài)成員變量 ****/
/* 1.使用 GetObjectClass、 FindClass獲取調(diào)用對(duì)象的類
* 2.使用 GetFieldID 獲取字段的ID。這里需要傳入字段類型的簽名描述。
* 3.使用 GetIntField、 GetObjectField等方法,獲取字段的值。
* 4.使用 SetIntField、 SetObjectField等方法,設(shè)置字段的值。
* 注意:即使字段是 private也照樣可以正常訪問。*/
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JniCallJavaNoStaticField(JNIEnv *env, jobject instance) {
//獲取jclass
jclass j_class = env->GetObjectClass(instance);
//獲取jfieldID
jfieldID j_fid = env->GetFieldID(j_class, "mNoStaticField", "I");
//獲取java成員變量int值
jint j_int = env->GetIntField(instance, j_fid);
//noStaticField==0
LOGI("noStaticField==%d", j_int);
//Set<Type>Field 修改noStaticKeyValue的值改為111
env->SetIntField(instance, j_fid, 111);
}
}
JNIDEMO.java
public class JNIDEMO {
//非靜態(tài)成員變量
public int mNoStaticField;
//Jni調(diào)用Java非靜態(tài)成員變量
public native void JniCallJavaNoStaticField();
}
JniActivity.java
通過JNIDEMO實(shí)例對(duì)象調(diào)用聲明的Native方法,實(shí)現(xiàn)對(duì)Jni函數(shù)的調(diào)用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//jni調(diào)用Java非靜態(tài)成員變量
Log.v(TAG, "BeforCallJni mJni.mNoStaticField:" + mJniDemo.mNoStaticField);
mJniDemo.JniCallJavaNoStaticField();
Log.v(TAG, "AfterCallJni mJni.mNoStaticField:" + mJniDemo.mNoStaticField);
}
}
日志打?。?/strong>
可以看到,在Jni中對(duì)Java的非靜態(tài)成員變量的值進(jìn)行了改變
(2).Jni調(diào)用Java靜態(tài)成員變量
jnidemo.cpp
Jni函數(shù)名前綴要與Java聲明Native函數(shù)所在文件的包名+文件名對(duì)應(yīng)
extern "C" {
/**** jni訪問java靜態(tài)成員變量 ****/
/* 1.使用 GetObjectClass、 FindClass獲取調(diào)用對(duì)象的類
* 2.使用 GetStaticFieldID 獲取字段的ID。這里需要傳入字段類型的簽名描述。
* 3.使用 GetStaticIntField、 GetStaticObjectField 等方法,獲取字段的值。
* 4.使用 SetStaticIntField、 SetStaticObjectField 等方法,設(shè)置字段的值。
* 注意:即使字段是 private也照樣可以正常訪問。*/
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JniCallJavaStaticField(JNIEnv *env, jobject instance) {
//獲取jclass
jclass j_class = env->GetObjectClass(instance);
//獲取jfieldID
jfieldID j_fid = env->GetStaticFieldID(j_class, "mStaticField", "I");
//獲取java成員變量int值
jint j_int = env->GetStaticIntField(j_class, j_fid);
//noStaticField==0
LOGI("StaticField==%d", j_int);
//Set<Type>Field 修改noStaticKeyValue的值改為666
env->SetStaticIntField(j_class, j_fid, 222);
}
}
JNIDEMO.java
public class JNIDEMO {
//靜態(tài)成員變量
public static int mStaticField;
//Jni調(diào)用Java靜態(tài)成員變量
public native void JniCallJavaStaticField();
}
JniActivity.java
通過JNIDEMO實(shí)例對(duì)象調(diào)用聲明的Native方法,實(shí)現(xiàn)對(duì)Jni函數(shù)的調(diào)用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//Jni調(diào)用Java靜態(tài)成員變量
Log.v(TAG, "BeforCallJni mJni.mStaticField:" + mJniDemo.mStaticField);
mJniDemo.JniCallJavaStaticField();
Log.v(TAG, "AfterCallJni mJni.mStaticField:" + mJniDemo.mStaticField);
}
}
日志打?。?/strong>
可以看到,在Jni中對(duì)Java的靜態(tài)成員變量的值進(jìn)行了改變
(3).Jni調(diào)用Java非靜態(tài)成員方法
jnidemo.cpp
Jni函數(shù)名前綴要與Java聲明Native函數(shù)所在文件的包名+文件名對(duì)應(yīng)
extern "C" {
/**** jni調(diào)用java非靜態(tài)成員方法 ****/
/* 1.使用 GetObjectClass、 FindClass獲取調(diào)用對(duì)象的類
* 2.使用 GetMethodID獲取方法的ID。這里需要傳入方法的簽名描述。
* 3.使用 CallVoidMethod執(zhí)行無返回值的方法
* 4.使用 CallIntMethod、 CallBooleanMethod、CallStringMethod等執(zhí)行有返回值的方法。
* 注意:即使字段是 private也照樣可以正常訪問。*/
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JniCallJavaNoStaticMethod(JNIEnv *env, jobject instance) {
//回調(diào)JNI.java中的noParamMethod
jclass clazz = env->FindClass("com/android/demo/jni/JNIDEMO");
if (clazz == NULL) {
printf("find class Error");
return;
}
jmethodID method = env->GetMethodID(clazz, "noStaticMethod", "(I)I");
if (method == NULL) {
printf("find method Error");
return;
}
env->CallIntMethod(instance, method, 333);
}
}
JNIDEMO.java
public class JNIDEMO {
//非靜態(tài)成員方法
private int noStaticMethod(int number) {
Log.v(TAG,"noStaticMethod() number: "+number);
return number;
}
//Jni調(diào)用Java非靜態(tài)成員方法
public native void JniCallJavaNoStaticMethod();
}
JniActivity.java
通過JNIDEMO實(shí)例對(duì)象調(diào)用聲明的Native方法,實(shí)現(xiàn)對(duì)Jni函數(shù)的調(diào)用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//Jni調(diào)用Java非靜態(tài)成員方法
mJniDemo.JniCallJavaNoStaticMethod();
}
}
日志打?。?/strong>
可以看到,Jni調(diào)用一個(gè)帶整型參數(shù)的Java非靜態(tài)成員方法,并且在Jni給這個(gè)方法傳參333
運(yùn)行后,這個(gè)Java非靜態(tài)成員方法中的Log打印如下:
(4).Jni調(diào)用Java靜態(tài)成員方法
jnidemo.cpp
Jni函數(shù)名前綴要與Java聲明Native函數(shù)所在文件的包名+文件名對(duì)應(yīng)
extern "C" {
/**** jni調(diào)用java靜態(tài)成員方法 ****/
/*1.使用 GetObjectClass、 FindClass獲取調(diào)用對(duì)象的類
* 2.使用 GetStaticMethodID 獲取方法的ID。這里需要傳入方法的簽名描述。
* 3.使用 CallStaticVoidMethod 執(zhí)行無返回值的方法。
* 4.使用 CallStaticIntMethod、 CallStaticBooleanMethod 等執(zhí)行有返回值的方法。
* 注意:即使字段是 private也照樣可以正常訪問。*/
JNIEXPORT void JNICALL
Java_com_android_demo_jni_JNIDEMO_JniCallJavaStaticMethod(JNIEnv *env, jobject instance) {
//回調(diào)JNI.java中的noParamMethod
jclass clazz = env->FindClass("com/android/demo/jni/JNIDEMO");
if (clazz == NULL) {
printf("find class Error");
return;
}
jmethodID method = env->GetStaticMethodID(clazz, "staticMethod", "(I)I");
if (method == NULL) {
printf("find method Error");
return;
}
env->CallStaticIntMethod(clazz, method, 444);
}
}
JNIDEMO.java
public class JNIDEMO {
//靜態(tài)成員方法
private static int staticMethod(int number) {
Log.v(TAG,"staticMethod() number: "+number);
return number;
}
//Jni調(diào)用Java靜態(tài)成員方法
public native void JniCallJavaStaticMethod();
}
JniActivity.java
通過JNIDEMO實(shí)例對(duì)象調(diào)用聲明的Native方法,實(shí)現(xiàn)對(duì)Jni函數(shù)的調(diào)用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//jni調(diào)用Java靜態(tài)成員方法
mJniDemo.JniCallJavaStaticMethod();
}
}
日志打?。?/strong>
可以看到,Jni調(diào)用一個(gè)帶整型參數(shù)的Java非靜態(tài)成員方法,并且在Jni給這個(gè)方法傳參333
運(yùn)行后,這個(gè)Java非靜態(tài)成員方法中的Log打印如下:
(5).Jni調(diào)用Java類的構(gòu)造函數(shù)
新建一個(gè)JNIConstruct.java類,其中包含一個(gè)Int和一個(gè)String成員變量。
在Jni調(diào)用JNIConstruct.java類的構(gòu)造函數(shù),實(shí)現(xiàn)通過Jni調(diào)用構(gòu)造函數(shù)傳參,給這兩個(gè)成員變量賦值
JNIConstruct.java
package com.android.demo.jni;
import android.util.Log;
public class JNIConstruct {
private final String TAG = "JNIConstruct";
private int paramInt = 0;
private String paramStr = null;
public JNIConstruct(int intp, String strp) {
paramInt = intp;
paramStr = strp;
}
public void printf() {
Log.v(TAG, "printf() paramInt:" + paramInt + " paramStr:" + paramStr);
}
}
jnidemo.cpp
Jni函數(shù)名前綴要與Java聲明Native函數(shù)所在文件的包名+文件名對(duì)應(yīng)
extern "C" {
/***** jni調(diào)用java構(gòu)造方法 *****/
/* 1.使用 FindClass 獲取需要構(gòu)造的類
* 2.使用 GetMethodID 獲取構(gòu)造方法的ID。方法名為 <init>, 這里需要傳入方法的簽名描述。
* 3.使用 NewObject 執(zhí)行創(chuàng)建對(duì)象。*/
JNIEXPORT jobject JNICALL
Java_com_android_demo_jni_JNIDEMO_JniCallJavaConstructMethod(JNIEnv *env, jobject instance) {
// 1、獲取 JNIConstruct 類的 class 引用
jclass cls_jniCons = env->FindClass("com/android/demo/jni/JNIConstruct");
if (cls_jniCons == NULL) {
return NULL;
}
// 2、獲取 JNIConstruct 的構(gòu)造方法ID (構(gòu)造方法的名稱統(tǒng)一為:<init>)
jmethodID med_jniCons = env->GetMethodID(cls_jniCons, "<init>", "(ILjava/lang/String;)V");
if (med_jniCons == NULL) {
return NULL; // 沒有找到參數(shù)為int和String的構(gòu)造方法
}
// 3、創(chuàng)建JNIConstruct對(duì)象的實(shí)例(調(diào)用對(duì)象的構(gòu)造方法并初始化對(duì)象), env->NewStringUTF("") 創(chuàng)建一個(gè) String 對(duì)象,作為構(gòu)造方法的第二個(gè) String 類型參數(shù)
jobject obj_jniCons = env->NewObject(cls_jniCons, med_jniCons, 555,
env->NewStringUTF("Jni Construct!"));
if (obj_jniCons == NULL) {
return NULL;
}
return obj_jniCons;
}
}
JNIDEMO.java
public class JNIDEMO {
//Jni調(diào)用Java類的構(gòu)造函數(shù)
public native JNIConstruct JniCallJavaConstructMethod();
}
JniActivity.java
通過JNIDEMO實(shí)例對(duì)象調(diào)用聲明的Native方法,實(shí)現(xiàn)對(duì)Jni函數(shù)的調(diào)用
public class JniActivity{
private JNIDEMO mJniDemo = new JNIDEMO();
publice void JavaJniFun(){
//jni調(diào)用Java構(gòu)造函數(shù)
JNIConstruct jniConstruct = mJniDemo.JniCallJavaConstructMethod();
jniConstruct.printf();
}
}
日志打?。?/strong>
可以看到,Jni調(diào)用了JNIConstruct.java的構(gòu)造函數(shù),并傳遞兩個(gè)參數(shù):
- int paramInt = 555;
- String paramStr = Jni Construct!;
四.結(jié)束語?
Jni 實(shí)戰(zhàn)開發(fā)到此講解完畢,篇幅有限,無法所有場(chǎng)景都實(shí)戰(zhàn)涉及,但是萬變不離其宗。文章來源:http://www.zghlxwxcb.cn/news/detail-818648.html
掌握了Jni理論基礎(chǔ),實(shí)踐了多種類型Jni與Java互相調(diào)用后,其他都只是在此基礎(chǔ)上的擴(kuò)展了。文章來源地址http://www.zghlxwxcb.cn/news/detail-818648.html
到了這里,關(guān)于Android:JNI實(shí)戰(zhàn),理論詳解、Java與Jni數(shù)據(jù)調(diào)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!