Android高級開發第二篇 - JNI 參數傳遞與 Java → C → Java 雙向調用

文章目錄

  • Android高級開發第二篇 - JNI 參數傳遞與 Java → C → Java 雙向調用
    • 引言
    • JNI基礎回顧
    • JNI中的參數傳遞
      • 基本數據類型傳遞
      • 字符串傳遞
      • 數組傳遞
      • 對象傳遞
    • Java → C → Java 雙向調用
      • 從C/C++調用Java方法
      • 實現一個完整的回調機制
    • 內存管理與注意事項
    • 性能優化提示
    • 結論
    • 參考資源

Android高級開發第二篇 - JNI 參數傳遞與 Java → C → Java 雙向調用

引言

在Android開發中,JNI (Java Native Interface) 是連接Java代碼和本地C/C++代碼的橋梁。通過JNI,我們可以利用C/C++的高性能特性來處理計算密集型任務,同時保持Java的跨平臺優勢。本文將深入探討JNI參數傳遞機制以及Java和C之間的雙向調用實現。

JNI基礎回顧

在深入參數傳遞之前,讓我們先回顧JNI的基本概念:

  • JNI: Java本地接口,允許Java代碼調用C/C++等本地語言編寫的函數
  • JNIEnv: 提供大多數JNI函數的接口指針
  • jobject: 表示Java對象的引用
  • jclass: 表示Java類的引用

JNI中的參數傳遞

基本數據類型傳遞

JNI提供了一系列與Java基本數據類型對應的C數據類型:

Java類型JNI類型C/C++類型
booleanjbooleanunsigned char
bytejbytesigned char
charjcharunsigned short
shortjshortshort
intjintint
longjlonglong long
floatjfloatfloat
doublejdoubledouble

示例代碼:

// Java代碼
public native int calculateSum(int a, int b);
// C代碼
JNIEXPORT jint JNICALL
Java_com_example_MyClass_calculateSum(JNIEnv *env, jobject thiz, jint a, jint b) {return a + b;
}

字符串傳遞

字符串是最常見的復雜參數類型之一。在JNI中,我們需要在Java的String和C的字符數組之間進行轉換:

// Java代碼
public native String reverseString(String input);
// C代碼
JNIEXPORT jstring JNICALL
Java_com_example_MyClass_reverseString(JNIEnv *env, jobject thiz, jstring input) {// 將Java字符串轉換為C字符串const char* str = (*env)->GetStringUTFChars(env, input, NULL);// 處理字符串(例如反轉)int len = strlen(str);char* reversed = malloc(len + 1);for (int i = 0; i < len; i++) {reversed[i] = str[len - i - 1];}reversed[len] = '\0';// 釋放資源(*env)->ReleaseStringUTFChars(env, input, str);// 將C字符串轉換回Java字符串jstring result = (*env)->NewStringUTF(env, reversed);free(reversed);return result;
}

數組傳遞

JNI提供了訪問和修改Java數組的方法:

// Java代碼
public native void processIntArray(int[] array);
// C代碼
JNIEXPORT void JNICALL
Java_com_example_MyClass_processIntArray(JNIEnv *env, jobject thiz, jintArray array) {// 獲取數組長度jsize length = (*env)->GetArrayLength(env, array);// 獲取數組元素jint* elements = (*env)->GetIntArrayElements(env, array, NULL);// 處理數組for (int i = 0; i < length; i++) {elements[i] *= 2; // 每個元素乘以2}// 更新Java數組并釋放資源(*env)->ReleaseIntArrayElements(env, array, elements, 0);
}

對象傳遞

在JNI中傳遞Java對象需要使用反射機制:

// Java類
public class Person {private String name;private int age;// getter和setter方法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; }
}// Java接口
public native void updatePerson(Person person);
// C代碼
JNIEXPORT void JNICALL
Java_com_example_MyClass_updatePerson(JNIEnv *env, jobject thiz, jobject person) {// 獲取Person類jclass personClass = (*env)->GetObjectClass(env, person);// 獲取setName方法IDjmethodID setNameMethod = (*env)->GetMethodID(env, personClass, "setName", "(Ljava/lang/String;)V");// 調用setName方法jstring newName = (*env)->NewStringUTF(env, "Updated from JNI");(*env)->CallVoidMethod(env, person, setNameMethod, newName);// 獲取setAge方法IDjmethodID setAgeMethod = (*env)->GetMethodID(env, personClass, "setAge", "(I)V");// 調用setAge方法(*env)->CallVoidMethod(env, person, setAgeMethod, 30);
}

Java → C → Java 雙向調用

JNI最強大的特性之一是支持雙向調用:不僅可以從Java調用C/C++代碼,還可以從C/C++回調Java方法。

從C/C++調用Java方法

// Java類
public class Callback {// 這個方法將被C代碼調用public void onProgress(int progress) {System.out.println("Progress: " + progress + "%");}// JNI方法public native void startLongTask();
}
// C代碼
JNIEXPORT void JNICALL
Java_com_example_Callback_startLongTask(JNIEnv *env, jobject thiz) {// 獲取Callback類jclass callbackClass = (*env)->GetObjectClass(env, thiz);// 獲取onProgress方法IDjmethodID onProgressMethod = (*env)->GetMethodID(env, callbackClass, "onProgress", "(I)V");// 模擬一個長時間運行的任務for (int i = 0; i <= 100; i += 10) {// 執行一些工作...// 調用Java的回調方法(*env)->CallVoidMethod(env, thiz, onProgressMethod, i);// 模擬延遲usleep(500000); // 500毫秒}
}

實現一個完整的回調機制

下面是一個更完整的例子,展示了如何實現一個回調接口:

// Java回調接口
public interface TaskCallback {void onStart();void onProgress(int progress);void onComplete(String result);
}// Java類
public class NativeTask {private TaskCallback callback;public NativeTask(TaskCallback callback) {this.callback = callback;}// JNI方法public native void executeTask();// 靜態代碼塊加載本地庫static {System.loadLibrary("nativetask");}
}
// C代碼
JNIEXPORT void JNICALL
Java_com_example_NativeTask_executeTask(JNIEnv *env, jobject thiz) {// 獲取NativeTask類jclass taskClass = (*env)->GetObjectClass(env, thiz);// 獲取callback字段IDjfieldID callbackField = (*env)->GetFieldID(env, taskClass, "callback", "Lcom/example/TaskCallback;");// 獲取callback對象jobject callback = (*env)->GetObjectField(env, thiz, callbackField);// 獲取TaskCallback接口的類引用jclass callbackClass = (*env)->FindClass(env, "com/example/TaskCallback");// 獲取接口方法IDjmethodID onStartMethod = (*env)->GetMethodID(env, callbackClass, "onStart", "()V");jmethodID onProgressMethod = (*env)->GetMethodID(env, callbackClass, "onProgress", "(I)V");jmethodID onCompleteMethod = (*env)->GetMethodID(env, callbackClass, "onComplete", "(Ljava/lang/String;)V");// 調用onStart(*env)->CallVoidMethod(env, callback, onStartMethod);// 模擬任務進度for (int i = 0; i <= 100; i += 10) {// 執行一些工作...// 調用onProgress(*env)->CallVoidMethod(env, callback, onProgressMethod, i);// 模擬延遲usleep(200000); // 200毫秒}// 調用onCompletejstring result = (*env)->NewStringUTF(env, "Task completed successfully!");(*env)->CallVoidMethod(env, callback, onCompleteMethod, result);// 釋放局部引用(*env)->DeleteLocalRef(env, result);
}

在實際應用中的使用示例:

// 使用示例
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);TaskCallback callback = new TaskCallback() {@Overridepublic void onStart() {Log.d("NativeTask", "Task started");}@Overridepublic void onProgress(int progress) {Log.d("NativeTask", "Progress: " + progress + "%");// 更新UI進度條}@Overridepublic void onComplete(String result) {Log.d("NativeTask", "Task completed: " + result);// 顯示結果}};NativeTask task = new NativeTask(callback);new Thread(() -> task.executeTask()).start();}
}

內存管理與注意事項

在JNI編程中,內存管理是一個關鍵問題:

  1. 局部引用: 每次JNI調用返回后自動釋放,但在復雜函數中應使用DeleteLocalRef手動釋放
  2. 全局引用: 必須手動創建和釋放,使用NewGlobalRefDeleteGlobalRef
  3. 弱全局引用: 不會阻止垃圾回收,使用NewWeakGlobalRefDeleteWeakGlobalRef
// 創建全局引用示例
jobject globalCallback;JNIEXPORT void JNICALL
Java_com_example_NativeTask_initialize(JNIEnv *env, jobject thiz, jobject callback) {// 創建全局引用globalCallback = (*env)->NewGlobalRef(env, callback);
}JNIEXPORT void JNICALL
Java_com_example_NativeTask_cleanup(JNIEnv *env, jobject thiz) {// 釋放全局引用if (globalCallback != NULL) {(*env)->DeleteGlobalRef(env, globalCallback);globalCallback = NULL;}
}

性能優化提示

  1. 最小化JNI調用次數: 每次跨越JNI邊界都有開銷
  2. 批量處理數據: 一次傳遞大量數據比多次傳遞少量數據更高效
  3. 直接緩沖區: 使用ByteBuffer.allocateDirect()創建直接緩沖區,減少復制
  4. 保持引用: 重復使用的類和方法ID應該緩存起來
  5. 合理釋放資源: 及時釋放不再需要的引用和本地資源

結論

JNI參數傳遞和雙向調用是Android高級開發中的關鍵技能。掌握這些技術可以讓你充分利用Java和C/C++的各自優勢,構建高性能的Android應用。然而,JNI編程也帶來了額外的復雜性和潛在的內存管理問題,因此需要謹慎使用并遵循最佳實踐。

在實際開發中,可以考慮使用一些現代化的工具如DjinniSWIG來簡化JNI開發過程,減少樣板代碼并提高開發效率。另外,Android NDK還提供了許多有用的庫和工具,幫助開發者更輕松地進行本地開發。

參考資源

  • Android NDK 官方文檔
  • JNI 規范
  • Android JNI 提示

希望本文對你理解和應用JNI參數傳遞與雙向調用有所幫助。在下一篇文章中,我們將探討如何在JNI中處理異常和線程安全問題。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/84962.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/84962.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/84962.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

2025-05-28 Python深度學習8——優化器

文章目錄 1 工作原理2 常見優化器2.1 SGD2.2 Adam 3 優化器參數4 學習率5 使用最佳實踐 本文環境&#xff1a; Pycharm 2025.1Python 3.12.9Pytorch 2.6.0cu124 ? 優化器 (Optimizer) 是深度學習中的核心組件&#xff0c;負責根據損失函數的梯度來更新模型的參數&#xff0c;使…

Web攻防-SQL注入增刪改查HTTP頭UAXFFRefererCookie無回顯報錯

知識點&#xff1a; 1、Web攻防-SQL注入-操作方法&增刪改查 2、Web攻防-SQL注入-HTTP頭&UA&Cookie 3、Web攻防-SQL注入-HTTP頭&XFF&Referer 案例說明&#xff1a; 在應用中&#xff0c;存在增刪改查數據的操作&#xff0c;其中SQL語句結構不一導致注入語句…

Windows MongoDB C++驅動安裝

MongoDB驅動下載 MongoDB 官網MongoDB C驅動程序入門MongoDB C驅動程序入門 安裝環境 安裝CMAKE安裝Visual Studio 編譯MongoDB C驅動 C驅動依賴C驅動&#xff0c;需要先編譯C驅動 下載MongoDB C驅動源碼 打開CMAKE(cmake-gui) 選擇源碼及輸出路徑,然后點擊configure …

使用 C/C++ 和 OpenCV 調用攝像頭

使用 C/C 和 OpenCV 調用攝像頭 &#x1f4f8; OpenCV 是一個強大的計算機視覺庫&#xff0c;它使得從攝像頭捕獲和處理視頻流變得非常簡單。本文將指導你如何使用 C/C 和 OpenCV 來調用攝像頭、讀取視頻幀并進行顯示。 準備工作 在開始之前&#xff0c;請確保你已經正確安裝…

使用微軟最近開源的WSL在Windows上優雅的運行Linux

install wsl https://github.com/microsoft/WSL/releases/download/2.4.13/wsl.2.4.13.0.x64.msi install any distribution from microsoft store, such as kali-linux from Kali office website list of distribution PS C:\Users\50240> wsl -l -o 以下是可安裝的有…

Win11安裝Dify

1、打開Virtual Machine Platform功能 電腦系統為&#xff1a;Windows 11 家庭中文版24H2版本。 打開控制面板&#xff0c;點擊“程序”&#xff0c;點擊“啟用或關閉Windows功能”。 下圖標記的“Virtual Machine Platform”、“適用于 Linux 的 Windows 子系統”、“Windows…

C++模板類深度解析與氣象領域應用指南

支持開源&#xff0c;為了更好的后來者 ————————————————————————————————————————————————————By 我說的 C模板類深度解析與氣象領域應用指南 一、模板類核心概念 1.1 模板類定義 模板類是C泛型編程的核心機制&#x…

MongoDB(七) - MongoDB副本集安裝與配置

文章目錄 前言一、下載MongoDB1. 下載MongoDB2. 上傳安裝包3. 創建相關目錄 二、安裝配置MongoDB1. 解壓MongoDB安裝包2. 重命名MongoDB文件夾名稱3. 修改配置文件4. 分發MongoDB文件夾5. 配置環境變量6. 啟動副本集7. 進入MongoDB客戶端8. 初始化副本集8.1 初始化副本集8.2 添…

mac筆記本如何快捷鍵截圖后自動復制到粘貼板

前提&#xff1a;之前只會進行部分區域截圖操作&#xff08;commandshift4&#xff09;操作&#xff0c;截圖后發現未自動保存在剪貼板&#xff0c;還要進行一步手動復制到剪貼板的操作。 mac筆記本如何快捷鍵截圖后自動復制到粘貼板 截取 Mac 屏幕的一部分并將其自動復制到剪…

WPF 按鈕點擊音效實現

WPF 按鈕點擊音效實現 下面我將為您提供一個完整的 WPF 按鈕點擊音效實現方案&#xff0c;包含多種實現方式和高級功能&#xff1a; 完整實現方案 MainWindow.xaml <Window x:Class"ButtonClickSound.MainWindow"xmlns"http://schemas.microsoft.com/win…

C++ list基礎概念、list初始化、list賦值操作、list大小操作、list數據插入

list基礎概念&#xff1a;list中的每一部分是一個Node&#xff0c;由三部分組成&#xff1a;val、next、prev&#xff08;指向上一個節點的指針&#xff09; list初始化的代碼&#xff0c;見下 #include<iostream> #include<list>using namespace std;void printL…

【Pandas】pandas DataFrame equals

Pandas2.2 DataFrame Reindexing selection label manipulation 方法描述DataFrame.add_prefix(prefix[, axis])用于在 DataFrame 的行標簽或列標簽前添加指定前綴的方法DataFrame.add_suffix(suffix[, axis])用于在 DataFrame 的行標簽或列標簽后添加指定后綴的方法DataFram…

【ROS2】創建單獨的launch包

【ROS】郭老二博文之:ROS目錄 1、簡述 項目中,可以創建單獨的launch包來管理所有的節點啟動 2、示例 1)創建launch包(python) ros2 pkg create --build-type ament_python laoer_launch --license Apache-2.02)創建啟動文件 先創建目錄:launch 在目錄中創建文件:r…

GitHub 趨勢日報 (2025年05月23日)

本日報由 TrendForge 系統生成 https://trendforge.devlive.org/ &#x1f310; 本日報中的項目描述已自動翻譯為中文 &#x1f4c8; 今日整體趨勢 Top 10 排名項目名稱項目描述今日獲星總星數語言1All-Hands-AI/OpenHands&#x1f64c;開放式&#xff1a;少代碼&#xff0c;做…

鴻蒙OSUniApp 實現的數據可視化圖表組件#三方框架 #Uniapp

UniApp 實現的數據可視化圖表組件 前言 在移動互聯網時代&#xff0c;數據可視化已成為產品展示和決策分析的重要手段。無論是運營后臺、健康監測、還是電商分析&#xff0c;圖表組件都能讓數據一目了然。UniApp 作為一款優秀的跨平臺開發框架&#xff0c;支持在鴻蒙&#xf…

[ctfshow web入門] web124

信息收集 error_reporting(0); //聽說你很喜歡數學&#xff0c;不知道你是否愛它勝過愛flag if(!isset($_GET[c])){show_source(__FILE__); }else{//例子 c20-1$content $_GET[c];// 長度不允許超過80個字符if (strlen($content) > 80) {die("太長了不會算");}/…

Vue 技術文檔

一、引言 Vue 是一款用于構建用戶界面的漸進式 JavaScript 框架&#xff0c;具有易上手、高性能、靈活等特點&#xff0c;能夠幫助開發者快速開發出響應式的單頁面應用。本技術文檔旨在全面介紹 Vue 的相關技術知識&#xff0c;為開發人員提供參考和指導。 二、環境搭建 2.1…

Nodejs+http-server 使用 http-server 快速搭建本地圖片訪問服務

在開發過程中&#xff0c;我們經常需要臨時查看或分享本地的圖片資源&#xff0c;比如設計稿、截圖、素材等。雖然可以通過壓縮發送&#xff0c;但效率不高。本文將教你使用 Node.js 的一個輕量級工具 —— http-server&#xff0c;快速搭建一個本地 HTTP 圖片預覽服務&#xf…

通義智文開源QwenLong-L1: 邁向長上下文大推理模型的強化學習

&#x1f389; 動態 2025年5月26日: &#x1f525; 我們正式發布&#x1f917;QwenLong-L1-32B——首個采用強化學習訓練、專攻長文本推理的LRM模型。在七項長文本文檔問答基準測試中&#xff0c;QwenLong-L1-32B性能超越OpenAI-o3-mini和Qwen3-235B-A22B等旗艦LRM&#xff0c…

學習如何設計大規模系統,為系統設計面試做準備!

前言 在當今快速發展的技術時代&#xff0c;系統設計能力已成為衡量一名軟件工程師專業素養的重要標尺。隨著云計算、大數據、人工智能等領域的興起&#xff0c;構建高性能、可擴展且穩定的系統已成為企業成功的關鍵。然而&#xff0c;對于許多工程師而言&#xff0c;如何有效…