文章目錄
I . jintArray 類型說明
1 . C ++ 環境類型定義 : 下面是 jintArray 類型的定義 , jintArray 的本質是一個 _jobject 類對象指針 ;
class _jobject {}; // 定義 _jobject 類 , 這是一個空類
class _jarray : public _jobject {}; // 定義 _jarray 類 繼承 _jobject 類
class _jintArray : public _jarray {}; // 定義 _jintArray 類 , 繼承 _jarray 類
typedef _jintArray* jintArray; // 定義 _jintArray* 別名 , jintArray
public 繼承 : 父類成員在子類中訪問級別不變
2 . jintArray 類型本質 : jintArray 是一個 _jintArray 類的指針 , 其 _jintArray 類 對象存儲在內存中 , _jintArray * 指針指向該內存 ;
爲 _jintArray * 指針變量類型 定義別名 jintArray 類型 ;
3 . ( jintArray -> jint * ) 類型轉換 : 這是 Java 中的 int 數組的內存地址 , 如果要在 C/C++ 環境中使用 , 要將該 jintArray 類型變量轉爲 jint* 類型的變量 ;
使用 jint* GetIntArrayElements(jintArray array, jboolean* isCopy) 方法 , 可以實現上述轉化 ( jintArray -> jint * ) ;
II . jboolean 類型說明
1 . C ++ 環境類型定義 : 下面是 jboolean 類型的定義 , jboolean 本質是 無符號 char 類型 ;
typedef unsigned char __uint8_t; // 定義 char 類型別名 __uint8_t
typedef __uint8_t uint8_t; // 定義 __uint8_t 類型別名 uint8_t
typedef uint8_t jboolean; // 定義 uint8_t 類型別名 jboolean
2 . jboolean 類型取值 : jboolean 的取值只能是 0 和 1 , 也可以使用 JNI_FALSE 和 JNI_TRUE 宏定義 ;
#define JNI_FALSE 0
#define JNI_TRUE 1
III . GetIntArrayElements 方法解析 ( jintArray -> jint* | int* )
1 . GetIntArrayElements 函數作用 : 將 Java 環境的 int 數組類型變量 ( jintArray 類型 ) , 轉爲 C/C++ 環境中的 jint 數組指針 , 返回一個指針指向 jint 數組首元素地址 ;
jint 本質就是 int 類型 , GetIntArrayElements 函數作用就是將 jintArray 轉爲 int* 指針 ;
2 . GetIntArrayElements 函數原型 :
struct _JNIEnv {
/* _JNIEnv 結構體中封裝了 JNINativeInterface 結構體指針 */
const struct JNINativeInterface* functions;
...
jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
{
// 調用 JNINativeInterface 結構體中封裝的 GetIntArrayElements 方法
return functions->GetIntArrayElements(this, array, isCopy);
}
...
}
3 . jintArray array 參數 : 該參數是從 Java 層調用傳入的參數 , jintArray 的本質是一個 _jobject 類對象指針 ;
4 . jboolean* isCopy 參數 : 該參數用於指定將 jintArray 類型的變量 , 轉爲 jint * 指針類型的變量 , 新的指針變量的生成方式 ;
① 將 該參數設置成指向 JNI_TRUE 的指針 : 將 int 數組數據拷貝到一個新的內存空間中 , 並將該內存空間首地址返回 ;
② 將 該參數設置成指向 JNI_FALSE 的指針 : 直接使用 java 中的 int 數組地址 , 返回 java 中的 int 數組的首地址 ;
③ 將 該參數設置成 NULL ( 推薦 ) : 表示不關心如何實現 , 讓系統自動選擇指針生成方式 , 一般情況下都不關心該生成方式 ;
5 . 代碼示例 :
① jboolean* isCopy 參數 準備 代碼示例 : jboolean 類型變量可取值 JNI_FALSE 0 和 JNI_TRUE 1 兩個值 ;
jboolean isCopy = JNI_TRUE;
② GetIntArrayElements 方法調用代碼示例 :
intArray_ 是 Java 層傳入的 jintArray intArray_ 參數變量 ;
JNIEnv *env 是 JNI 方法的默認參數 , 這裏是 C++ 環境中的 JNIEnv 指針類型 ;
jboolean* isCopy 設置成 NULL 參數表示 不關心 jint* 類型變量的生成方式 ;
jint *intArray = env->GetIntArrayElements(intArray_, NULL);
如果是其它基礎數據類型的數組 , 將 Get***ArrayElements 方法名中的 基礎數據類型修改一下即可 ;
- 如果是布爾類型的數組 , 使用 GetBooleanArrayElements 方法 ;
- 如果是浮點型的數組 , 使用 GetFloatArrayElements 方法 ;
- 如果是字符型的數組 , 使用 GetCharArrayElements 方法 ;
IV . jarray 類型說明
1 . jarray 類型 : 該類型的本質是一個指針 , 指向一個空對象地址 , 這個對象一般是從 Java 層傳遞進來 ;
class _jobject {}; // 定義 _jobject 空類
class _jarray : public _jobject {}; // 定義 _jarray 類 , 繼承 _jobject 類
typedef _jarray* jarray; // 定義 _jarray* 指針類型 別名爲 jarray
2 . _jarray 類子類 : 下面定義的 9 個類 , 都是 _jarray 子類 , 都可以使用 GetArrayLength 方法獲取數組長度 ;
class _jarray : public _jobject {};
//下面定義的都是 jarray 子類
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 {};
3 . Java 傳入的數組類型別名 : 下面定義的 9 個別名 , 本質上都是 _jarray 類型對象 或者 其子類對象的 指針 , 即 _jarray* 類型 ;
typedef _jarray* jarray;
//下面是 9 個是 Java 傳入的數組類型別名
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;
V . GetArrayLength 方法解析 ( 獲取 jarray 數組長度 )
1 . 函數作用 : 獲取 jarray 數組長度 , 該 jarray 類型可以是下面定義的類型 ;
typedef _jarray* jarray;
//下面是 9 個是 Java 傳入的數組類型別名
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;
2 . 函數原型 :
struct _JNIEnv {
/* _JNIEnv 結構體中封裝了 JNINativeInterface 結構體指針 */
const struct JNINativeInterface* functions;
...
jsize GetArrayLength(jarray array)
{
//// 調用 JNINativeInterface 結構體中封裝的 GetArrayLength 方法
return functions->GetArrayLength(this, array);
}
...
}
3 . 返回值類型說明 : jsize 類型本質還是 int 類型 ;
typedef int __int32_t;
typedef __int32_t int32_t;
typedef int32_t jint;
typedef jint jsize;
4 . 函數調用示例 : 其中的 jintArray intArray_ 是 Java 層傳入的參數 ;
jsize len = env->GetArrayLength(intArray_);
VI . 日誌打印
1 . 日誌庫配置 :
① 導入日誌庫 : #include <android/log.h>
② CMake 設置日誌庫 : add_library 設置動態庫名稱 , find_library 中爲 查找日誌庫 , target_link_libraries 連接日誌庫 ;
add_library(
native-lib
SHARED
native-lib.cpp)
find_library(
log-lib
log)
target_link_libraries(
native-lib
${log-lib})
2 . 日誌打印函數函數原型 :
int __android_log_print(int prio, const char* tag, const char* fmt, ...)
3 . 日誌打印函數參數說明 :
- ① int prio 參數 : 日誌的等級 , 定義在 jni.h 的 android_LogPriority 枚舉中 ;
ANDROID_LOG_VERBOSE
ANDROID_LOG_DEBUG
ANDROID_LOG_INFO
ANDROID_LOG_WARN
ANDROID_LOG_ERROR
- ② const char* tag 參數 : 日誌打印的 TAG 標籤 , 這是一個 C/C++ char* 類型字符串 ;
- ③ const char* fmt, … 參數 : 可變參數 ;
4 . 日誌打印函數代碼示例 :
/*
__android_log_print 打印 Android 日誌函數
函數原型 : int __android_log_print(int prio, const char* tag, const char* fmt, ...)
int prio 參數 : 日誌的等級 , 定義在 jni.h 的 android_LogPriority 枚舉中
ANDROID_LOG_VERBOSE
ANDROID_LOG_DEBUG
ANDROID_LOG_INFO
ANDROID_LOG_WARN
ANDROID_LOG_ERROR
const char* tag 參數 : 日誌打印的 TAG 標籤 , 這是一個 C/C++ char* 類型字符串
const char* fmt, ... 參數 : 可變參數
*/
__android_log_print(ANDROID_LOG_INFO, "JNI_TAG" , "%d . %d" , i , *num);
VII . 遍歷 int 數組
1 . 使用指針遍歷 jint 數組 : jint *intArray ;
intArray 是數組首元素地址
intArray + 1 是第 1 個元素的首地址
intArray + k 是第 k 個元素的首地址
2 . 函數調用 代碼示例 :
/*
使用指針進行訪問
intArray 是數組首元素地址
intArray + 1 是第 1 個元素的首地址
intArray + k 是第 k 個元素的首地址
使用 *(intArray + k) 可以獲取第 k 個元素的值
*/
for(int i = 0; i < len; i ++){
//獲取第 i 個元素的首地址 , 使用 *num 可以獲取第 i 個元素的值
int *num = intArray + i;
/*
__android_log_print 打印 Android 日誌函數
函數原型 : int __android_log_print(int prio, const char* tag, const char* fmt, ...)
int prio 參數 : 日誌的等級 , 定義在 jni.h 的 android_LogPriority 枚舉中
ANDROID_LOG_VERBOSE
ANDROID_LOG_DEBUG
ANDROID_LOG_INFO
ANDROID_LOG_WARN
ANDROID_LOG_ERROR
const char* tag 參數 : 日誌打印的 TAG 標籤 , 這是一個 C/C++ char* 類型字符串
const char* fmt, ... 參數 : 可變參數
*/
__android_log_print(ANDROID_LOG_INFO, "JNI_TAG" , "%d . %d" , i , *num);
//修改數組中的值
*num = 8888;
}
VIII . ReleaseIntArrayElements 方法說明 ( 釋放 C/C++ 中的 int 數組 )
1 . 函數作用 : 釋放 C/C++ 中的 jint 數組 , 設置 jintArray array 類型的返回模式 ;
2 . 函數原型 :
struct _JNIEnv {
/* _JNIEnv 結構體中封裝了 JNINativeInterface 結構體指針 */
const struct JNINativeInterface* functions;
...
void ReleaseIntArrayElements(jintArray array, jint* elems,
jint mode)
//調用的是 JNINativeInterface 結構體中封裝的 ReleaseIntArrayElements 方法
{ functions->ReleaseIntArrayElements(this, array, elems, mode); }
...
}
3 . ReleaseIntArrayElements 參數解析 :
① jintArray array 參數 : Java 層傳入的 數組參數 ;
② jint* elems 參數 : 使用 GetIntArrayElements 方法 Java 的 int 數組 C/C++ 中
③ jint mode 參數 : 設置處理模式 , 有三種處理模式 ;
4 . ReleaseIntArrayElements 方法 jint mode 參數 詳解 :
① 模式 0 : 刷新 Java 數組 , 釋放 C/C++ 數組
② 模式 1 ( JNI_COMMIT ) : 刷新 Java 數組 , 不釋放 C/C ++ 數組
③ 模式 2 ( JNI_ABORT ) : 不刷新 Java 數組 , 釋放 C/C++ 數組
下面是 jni.h 中的定義的模式 :
#define JNI_COMMIT 1
#define JNI_ABORT 2
如果設置 0 和 1 , 那麼 如果修改了 int 數組的值 , 那麼最終 Java 層的值會被修改
如果設置 2 , 那麼 如果修改了 int 數組的值 , 那麼最終 Java 層的值不會被修改
IX . 完整代碼示例
1 . Java 代碼 :
package kim.hsl.jni;
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 {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1 . 系統生成的方法
// 調用 stringFromJNI 方法 , 顯示從 Native 層傳入的字符串
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
//2 . 測試 字符串
jniTest(888 , "字符串測試");
//3 . 測試 int 數組 和 字符串數組
//準備 int 數組 和 String 數組
int[] intArray = {1 , 2 , 666 , 888 , 95555};
String[] stringArray = {"Hello" , "World" , "Hanshuliang"};
jniArrayTest(intArray, stringArray);
//打印 int 數組
/*
void ReleaseIntArrayElements(jintArray array, jint* elems, jint mode)
第三個參數 mode :
① 如果設置 0 和 1 , 那麼 如果修改了 int 數組的值 , 那麼最終 Java 層的值會被修改
② 如果設置 2 , 那麼 如果修改了 int 數組的值 , 那麼最終 Java 層的值不會被修改
*/
Log.i("JNI_TAG" , "Java 層 jniArrayTest 執行完畢後 , int[] intArray 數組內容 : " + Arrays.toString(intArray) );
}
/**
* 系統自動生成的 JNI 方法
*/
public native String stringFromJNI();
/**
* 傳入基本類型參數 和 字符串類型參數
* @param i
* @param s
*/
public native void jniTest(int i, String s);
/**
* 傳入數組對象給 Native 層
* @param intArray
* @param stringArray
*/
public native void jniArrayTest(int[] intArray , String[] stringArray);
}
2 . C++ 代碼 :
#include <jni.h>
#include <string>
//導入日誌庫
#include <android/log.h>
//定義日誌宏 , 其中的 __VA_ARGS__ 表示可變參數
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"JNI",__VA_ARGS__);
extern "C"
JNIEXPORT jstring JNICALL
Java_kim_hsl_jni_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
// 創建 C++ 字符串
std::string hello = "Hello from C++";
// 返回 jstring 類型的字符串
// 將 C/C++ 的 char* 字符串轉爲 Java 中的 jstring 類型字符串
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_jni_MainActivity_jniTest(JNIEnv *env, jobject instance, jint i, jstring s_) {
// 將 jstring 類型數據轉爲 char 類型數據
const char *s = env->GetStringUTFChars(s_, 0);
// 釋放
env->ReleaseStringUTFChars(s_, s);
}
extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_jni_MainActivity_jniArrayTest(JNIEnv *env, jobject instance, jintArray intArray_,
jobjectArray stringArray) {
// I . 基本類型數組操作
// 1 . jboolean 類型
/*
jboolean 類型的值可以設置成 true 或 false , 也可以不設置
如果將值傳遞給 GetIntArrayElements 方法 , 需要將 isCopy 的地址放在第二個參數位置
當做參數的格式 : env->GetIntArrayElements(intArray_, &isCopy);
可取值 JNI_FALSE 0 和 JNI_TRUE 1 兩個值
*/
jboolean isCopy = JNI_TRUE;
//2 . GetIntArrayElements 方法參數解析
/*
GetIntArrayElements 方法參數解析
方法作用 : 將 Java 的 int 數組 , 轉爲 jint 數組 , 返回一個指針指向 jint 數組首元素地址
函數原型 : jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
第一個參數 : jintArray array 是參數中的 jintArray 類型變量
jintArray 類型說明 :
class _jobject {}; C ++ 中定義了 _jobject 類
class _jarray : public _jobject {}; 定義 _jarray 類 繼承 _jobject 類
public 繼承 : 父類成員在子類中訪問級別不變
class _jintArray : public _jarray {}; 定義 _jintArray 類 繼承 _jarray 類
typedef _jintArray* jintArray; 將 _jintArray* 類型 聲明成 jintArray 類型
第二個參數 : jboolean* isCopy
該參數用於指定將 jintArray 類型的變量 , 轉爲 jint * 指針類型的變量 , 新的指針變量的生成方式
將 該參數設置成指向 JNI_TRUE 的指針 : 將 int 數組數據拷貝到一個新的內存空間中 , 並將該內存空間首地址返回
將 該參數設置成指向 JNI_FALSE 的指針 : 直接使用 java 中的 int 數組地址 , 返回 java 中的 int 數組的首地址
將 該參數設置成 NULL ( 推薦 ) : 表示不關心如何實現 , 讓系統自動選擇指針生成方式 , 一般情況下都不關心該生成方式
注意如果是 其它類型的數組
如果是布爾類型的數組 , 使用 GetBooleanArrayElements 方法
如果是浮點型的數組 , 使用 GetFloatArrayElements 方法
如果是字符型的數組 , 使用 GetCharArrayElements 方法
...
*/
jint *intArray = env->GetIntArrayElements(intArray_, NULL);
//3 . 操作 jint * 指針變量 , 循環獲取數組中每個元素的值
/*
獲取數組長度
函數原型 : jsize GetArrayLength(jarray array)
返回值類型 jsize :
jsize 類型 : 由下面可知 jsize 只是 int 類型的別名
typedef jint jsize;
typedef int32_t jint;
typedef __int32_t int32_t;
typedef int __int32_t;
*/
jsize len = env->GetArrayLength(intArray_);
//4 . 循環打印 int 數組中的元素
/*
使用指針進行訪問
intArray 是數組首元素地址
intArray + 1 是第 1 個元素的首地址
intArray + k 是第 k 個元素的首地址
使用 *(intArray + k) 可以獲取第 k 個元素的值
*/
for(int i = 0; i < len; i ++){
//獲取第 i 個元素的首地址 , 使用 *num 可以獲取第 i 個元素的值
int *num = intArray + i;
/*
__android_log_print 打印 Android 日誌函數
函數原型 : int __android_log_print(int prio, const char* tag, const char* fmt, ...)
int prio 參數 : 日誌的等級 , 定義在 jni.h 的 android_LogPriority 枚舉中
ANDROID_LOG_VERBOSE
ANDROID_LOG_DEBUG
ANDROID_LOG_INFO
ANDROID_LOG_WARN
ANDROID_LOG_ERROR
const char* tag 參數 : 日誌打印的 TAG 標籤 , 這是一個 C/C++ char* 類型字符串
const char* fmt, ... 參數 : 可變參數
*/
__android_log_print(ANDROID_LOG_INFO, "JNI_TAG" , "%d . %d" , i , *num);
//修改數組中的值
*num = 8888;
}
//5 . 釋放 jint * 類型的指針變量
/*
函數原型 : void ReleaseIntArrayElements(jintArray array, jint* elems, jint mode)
第一參數 jintArray array : 是 Java 層傳入的 int 數組 參數 , 即 Native 層的調用函數的參數
第二參數 jint* elems : 通過 GetIntArrayElements 方法將 jintArray 變量轉成的 jint* 變量
第三參數 jint mode : 設置處理模式 , 有三種處理模式
模式 0 : 刷新 Java 數組 , 釋放 C/C++ 數組
模式 1 ( JNI_COMMIT ) : 刷新 Java 數組 , 不釋放 C/C ++ 數組
模式 2 ( JNI_ABORT ) : 不刷新 Java 數組 , 釋放 C/C++ 數組
下面是 jni.h 中的定義的模式 :
#define JNI_COMMIT 1 copy content, do not free buffer
#define JNI_ABORT 2 free buffer w/o copying back
如果設置 0 和 1 , 那麼 如果修改了 int 數組的值 , 那麼最終 Java 層的值會被修改
如果設置 2 , 那麼 如果修改了 int 數組的值 , 那麼最終 Java 層的值不會被修改
*/
env->ReleaseIntArrayElements(intArray_, intArray, 0);
}
3 . 執行結果
01-12 16:51:56.594 7411-7411/kim.hsl.jni I/JNI_TAG: 0 . 1
01-12 16:51:56.594 7411-7411/kim.hsl.jni I/JNI_TAG: 1 . 2
01-12 16:51:56.594 7411-7411/kim.hsl.jni I/JNI_TAG: 2 . 666
01-12 16:51:56.594 7411-7411/kim.hsl.jni I/JNI_TAG: 3 . 888
01-12 16:51:56.594 7411-7411/kim.hsl.jni I/JNI_TAG: 4 . 95555
01-12 16:51:56.594 7411-7411/kim.hsl.jni I/JNI_TAG: Java 層 jniArrayTest 執行完畢後 , int[] intArray 數組內容 : [8888, 8888, 8888, 8888, 8888]