學習JNI 二

創建一個名為Learn1項目(Android Studio)。

一、項目結構

?二、配置 build.gradle

build.gradle.kts(:app)

plugins {alias(libs.plugins.android.application)alias(libs.plugins.jetbrains.kotlin.android)
}android {namespace = "com.demo.learn1"compileSdk = 35defaultConfig {applicationId = "com.demo.learn1"minSdk = 30targetSdk = 34versionCode = 1versionName = "1.0"testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"vectorDrawables {useSupportLibrary = true}externalNativeBuild {cmake {cppFlags.add("-std=c++17") // 推薦使用C++17標準// 現代Android設備主要支持arm64-v8a,可以精簡ABIabiFilters.add("arm64-v8a")}}}externalNativeBuild {cmake {path = file("src/main/cpp/CMakeLists.txt")version = "3.22.1"}}buildTypes {release {isMinifyEnabled = falseproguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"),"proguard-rules.pro")}}compileOptions {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8}kotlinOptions {jvmTarget = "1.8"}buildFeatures {compose = true}composeOptions {kotlinCompilerExtensionVersion = "1.5.1"}packaging {resources {excludes += "/META-INF/{AL2.0,LGPL2.1}"}}sourceSets {getByName("main") {jni {srcDirs("src\\main\\jni", "src\\main\\jni")}}}
}dependencies {implementation(libs.androidx.core.ktx)implementation(libs.androidx.lifecycle.runtime.ktx)implementation(libs.androidx.activity.compose)implementation(platform(libs.androidx.compose.bom))implementation(libs.androidx.ui)implementation(libs.androidx.ui.graphics)implementation(libs.androidx.ui.tooling.preview)implementation(libs.androidx.material3)implementation(libs.androidx.appcompat)implementation(libs.androidx.media3.common.ktx)testImplementation(libs.junit)androidTestImplementation(libs.androidx.junit)androidTestImplementation(libs.androidx.espresso.core)androidTestImplementation(platform(libs.androidx.compose.bom))androidTestImplementation(libs.androidx.ui.test.junit4)debugImplementation(libs.androidx.ui.tooling)debugImplementation(libs.androidx.ui.test.manifest)
}

settings.gradle.kts(Learn1)

pluginManagement {repositories {google {content {includeGroupByRegex("com\\.android.*")includeGroupByRegex("com\\.google.*")includeGroupByRegex("androidx.*")}}mavenCentral()gradlePluginPortal()}
}
dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {google()mavenCentral()}
}rootProject.name = "Learn1"
include(":app")

三、創建 CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)# 定義庫和源文件
add_library( # 設置庫名稱native_code# 設置庫類型為共享庫SHARED# 提供源文件的相對路徑native_code.cpp )# 查找日志庫
find_library( # 設置路徑變量名稱log-lib# 指定CMake要查找的NDK庫名稱log )# 指定庫應該鏈接到的目標庫
target_link_libraries( # 指定目標庫native_code# 將目標庫鏈接到日志庫${log-lib} )

Android NDK(Native Development Kit)允許你在 Android 應用中使用 C/C++ 代碼,通常用于高性能計算、游戲引擎、音視頻處理等場景。NDK 項目的核心是通過?CMakeLists.txt?或?Android.mk(較舊)來配置本地代碼的編譯。以下是詳細的配置示例和解析:


1. 基礎 NDK 項目配置示例

假設你有一個 JNI 模塊,包含一個 C++ 文件 (native-lib.cpp),需要編譯成動態庫 (.so),并鏈接 Android NDK 提供的日志庫 (liblog)。

CMakeLists.txt?文件內容
# 指定 CMake 最低版本(NDK 推薦至少 3.4.1)
cmake_minimum_required(VERSION 3.4.1)# 定義項目名稱(可選,但建議顯式聲明)
project(native-lib LANGUAGES CXX)# 設置 C++ 標準(NDK 默認支持 C++14,但顯式聲明更安全)
set(CMAKE_CXX_STANDARD 14)# 添加動態庫
add_library(native-lib             # 庫名稱(最終生成 libnative-lib.so)SHARED                 # 類型為動態庫(Android 必須)native-lib.cpp         # 源文件路徑
)# 查找 NDK 提供的日志庫(liblog.so)
find_library(log-lib                # 變量名,保存 liblog 的路徑log                    # 庫名稱
)# 鏈接日志庫到目標庫
target_link_libraries(native-lib            # 目標庫${log-lib}            # 鏈接 liblog
)# 可選:添加其他 NDK 庫(如 OpenMP、zlib 等)
# find_library(zlib-lib z)
# target_link_libraries(native-lib ${zlib-lib})

2. 關鍵配置解析

(1) add_library
  • 作用:定義需要編譯的本地庫。

  • 參數

    • native-lib:庫名稱,最終生成的動態庫在 Android 中會被命名為?libnative-lib.so

    • SHARED:指定為動態庫(Android JNI 必須使用動態庫)。

    • native-lib.cpp:源文件路徑(可添加多個文件,如?file1.cpp file2.cpp)。

(2) find_library
  • 作用:查找 Android NDK 提供的預編譯系統庫(如?libloglibzlibandroid?等)。

  • 參數

    • log-lib:自定義變量名,保存找到的庫路徑。

    • log:要查找的庫名稱(實際查找的是?liblog.so)。

(3) target_link_libraries
  • 作用:將目標庫與依賴庫鏈接。

  • 參數

    • native-lib:目標庫名稱。

    • ${log-lib}:引用之前找到的?liblog?庫路徑。


3. 擴展配置

(1) 添加多個源文件

如果項目有多個 C++ 文件:

add_library(native-libSHAREDnative-lib.cpputils.cppdecoder/audio_decoder.cpp  # 支持子目錄
)
(2) 添加頭文件路徑

如果頭文件在?cpp/include?目錄:

# 添加頭文件搜索路徑
target_include_directories(native-libPRIVATE${CMAKE_SOURCE_DIR}/cpp/include
)
(3) 鏈接第三方庫

假設你通過 NDK 編譯了一個靜態庫 (libfoo.a):

# 添加靜態庫路徑
add_library(foo STATIC IMPORTED)
set_target_properties(fooPROPERTIES IMPORTED_LOCATION${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libfoo.a
)# 鏈接到主庫
target_link_libraries(native-lib foo)
(4) 分平臺配置(ABI 過濾)

針對不同 CPU 架構(armeabi-v7a、arm64-v8a 等):

# 只編譯 arm64-v8a 和 x86_64
if(${ANDROID_ABI} STREQUAL "arm64-v8a" OR ${ANDROID_ABI} STREQUAL "x86_64")add_library(native-lib SHARED native-lib.cpp)
endif()

4. 在?build.gradle?中關聯 CMake

NDK 配置需要通過?build.gradle?告訴 Android Studio 如何使用 CMake:

android {defaultConfig {externalNativeBuild {cmake {cppFlags "-std=c++14 -frtti -fexceptions"  # 可選:自定義編譯標志abiFilters "arm64-v8a", "x86_64"          # 指定 ABI}}}externalNativeBuild {cmake {path "src/main/cpp/CMakeLists.txt"  # CMake 文件路徑version "3.22.1"                   # 指定 CMake 版本}}
}

5. 總結

  • 核心步驟
    add_library?→?find_library?→?target_link_libraries

  • NDK 特性
    必須用?SHARED?庫、通過?find_library?鏈接系統庫(如?liblog)。

  • 擴展能力
    支持多文件、頭文件路徑、ABI 過濾、第三方庫鏈接等。

四、編寫 C++ 代碼

native-lib.cpp

#include "native_code.h"
#include <jni.h>
#include <string>
#include <android/log.h>#define LOG_TAG "NativeCode"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)// 示例1: 返回字符串
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_learn1_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "你好,來自C++";return env->NewStringUTF(hello.c_str());
}// 示例2: 計算兩數之和
extern "C" JNIEXPORT jint JNICALL
Java_com_demo_learn1_MainActivity_addNumbers(JNIEnv* env,jobject /* this */,jint a,jint b) {return a + b;
}// 示例3: 處理字符串數組
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_learn1_MainActivity_processStringArray(JNIEnv* env,jobject /* this */,jobjectArray array) {jsize length = env->GetArrayLength(array);std::string result = "處理結果:\n";for (jsize i = 0; i < length; i++) {jstring str = (jstring)env->GetObjectArrayElement(array, i);const char* cStr = env->GetStringUTFChars(str, nullptr);result += "第" + std::to_string(i) + "項: " + cStr + "\n";env->ReleaseStringUTFChars(str, cStr);env->DeleteLocalRef(str);}return env->NewStringUTF(result.c_str());
}

1. 頭文件和宏定義

#include "native_code.h"  // 自定義頭文件(如果有)
#include <jni.h>          // JNI 核心頭文件
#include <string>         // C++ 字符串庫
#include <android/log.h>  // Android 日志庫#define LOG_TAG "NativeCode"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
  • jni.h:JNI 的核心頭文件,定義了 JNI 函數、數據類型(如?JNIEnvjstring?等)。

  • android/log.h:Android 專用的日志庫,用于在 Logcat 中輸出調試信息(類似 Java 的?Log.i())。

    • LOGI?是一個宏,簡化日志打印(ANDROID_LOG_INFO?表示日志級別為 Info)。


2. JNI 函數的基本結構

所有 JNI 函數都需要遵循固定的命名規則和參數格式:

extern "C" JNIEXPORT 返回值類型 JNICALL
Java_包名_類名_方法名(JNIEnv* env,      // JNI 環境指針jobject thiz,      // Java 調用者對象(如果是靜態方法則為 jclass)[其他參數...]       // Java 方法傳入的參數
) {// 函數實現
}
  • extern "C":確保 C++ 編譯器按 C 風格生成函數名(避免名稱修飾)。

  • JNIEXPORT?和?JNICALL:宏定義,確保函數在動態庫中可見且調用約定正確。

  • 命名規則:函數名必須為?Java_包名_類名_方法名,其中包名的?.?替換為?_


3. 示例解析

示例3:處理字符串數組
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_learn1_MainActivity_processStringArray(JNIEnv* env,jobject /* this */,jobjectArray array) {jsize length = env->GetArrayLength(array);std::string result = "處理結果:\n";for (jsize i = 0; i < length; i++) {jstring str = (jstring)env->GetObjectArrayElement(array, i);const char* cStr = env->GetStringUTFChars(str, nullptr);result += "第" + std::to_string(i) + "項: " + cStr + "\n";env->ReleaseStringUTFChars(str, cStr);  // 釋放資源env->DeleteLocalRef(str);              // 刪除局部引用}return env->NewStringUTF(result.c_str());//.c_str() 是 C++ 中 std::string 類的一個成員函數。它返回一個指向以 null 結尾的 C 風格字符串(也稱為 C 字符串或字符數組)的指針。這個 C 風格字符串是 std::string 對象內部存儲的內容的一個常量視圖(即你不能通過這個指針修改 std::string 的內容)。
}
  • 功能:接收一個 Java 字符串數組,拼接所有元素后返回結果字符串。

  • 關鍵點

    • jstring?是 JNI 的字符串類型,對應 Java 的?String

    • jobjectArray:JNI 的數組類型,對應 Java 的?Object[](這里是?String[])。

    • env->GetArrayLength():獲取數組長度。

    • env->GetObjectArrayElement():獲取數組中的元素(返回?jstring)。

    • env->GetStringUTFChars():將?jstring?轉換為 C 風格的字符串(const char*)。

    • env->NewStringUTF():將 C 風格的字符串(const char*)轉換為 Java 可識別的?jstring

    • 必須釋放資源

      • ReleaseStringUTFChars():釋放由?GetStringUTFChars?分配的字符串內存。

      • DeleteLocalRef():刪除局部引用,避免內存泄漏(JNI 的局部引用有數量限制)。


4. JNI 數據類型對照表

JNI 類型Java 類型C/C++ 類型
jbooleanbooleanunsigned char
jintintint32_t
jlonglongint64_t
jfloatfloatfloat
jdoubledoubledouble
jstringStringconst char*
jobjectObjectvoid*
jobjectArrayObject[]jobject[]

5. 內存管理與注意事項

  1. 局部引用 vs 全局引用

    • 局部引用(如?jstring)在函數返回后會自動釋放,但大量創建時需手動調用?DeleteLocalRef()

    • 全局引用需顯式創建(NewGlobalRef())和釋放(DeleteGlobalRef())。

  2. 字符串轉換

    • GetStringUTFChars()?和?GetStringChars()?返回的字符串必須調用對應的?Release?方法。

    • NewStringUTF()?創建的?jstring?無需手動釋放。

五、編寫 Kotlin 代碼?

MainActivity.kt

package com.demo.learn1import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivityclass MainActivity : ComponentActivity() {// 加載原生庫init {System.loadLibrary("native_code")}// 聲明原生方法private external fun stringFromJNI(): Stringprivate external fun addNumbers(a: Int, b: Int): Intprivate external fun processStringArray(array: Array<String>): Stringoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 示例1: 調用返回字符串的native方法val helloFromCpp = stringFromJNI()Log.d("MainActivity", "String from C++: $helloFromCpp")// 示例2: 調用計算兩個數和的native方法val sum = addNumbers(5, 7)Log.d("MainActivity", "Sum from C++: $sum")// 示例3: 處理字符串數組val strings = arrayOf("Kotlin", "Java", "C++", "JNI")val processedStrings = processStringArray(strings)Log.d("MainActivity", "Processed strings:\n$processedStrings")}
}

1. 加載原生庫

init {System.loadLibrary("native_code")
}
  • 作用:在類初始化時加載名為?native_code?的本地動態庫(.so?文件)。

  • 關鍵點

    • native_code?對應?CMakeLists.txt?中定義的庫名(add_library(native_code SHARED ...))。

    • 必須在使用任何?external(native)方法之前加載,否則會拋出?UnsatisfiedLinkError


2. 聲明原生方法

private external fun stringFromJNI(): String
private external fun addNumbers(a: Int, b: Int): Int
private external fun processStringArray(array: Array<String>): String
  • external?關鍵字:表示這些方法在本地代碼(C/C++)中實現,而非 Kotlin/Java。

  • 方法簽名

    • stringFromJNI()?→ 對應 C++ 的?Java_com_demo_learn1_MainActivity_stringFromJNI

    • addNumbers(a: Int, b: Int)?→ 對應 C++ 的?Java_com_demo_learn1_MainActivity_addNumbers

    • processStringArray(array: Array<String>)?→ 對應 C++ 的?Java_com_demo_learn1_MainActivity_processStringArray

  • JNI 規則

    • 方法名必須嚴格匹配?Java_包名_類名_方法名(包名的?.?替換為?_)。

    • 參數和返回類型要對應 JNI 類型(如?Int?→?jintString?→?jstring)。

六、結果打印

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

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

相關文章

基于Spring Boot+Vue的DIY手工社預約管理系統(Echarts圖形化、騰訊地圖API)

2.10 視頻課程管理功能實現2.11手工互動&#xff08;視頻彈幕&#xff09;2.8預約設置管理功能實現&#x1f388;系統亮點&#xff1a;Echarts圖形化、騰訊地圖API&#xff1b;文檔包含功能結構圖、系統架構圖、用例圖、實體屬性圖、E-R圖。一.系統開發工具與環境搭建1.系統設計…

leetcode 每日一題 1353. 最多可以參加的會議數目

更多技術訪問 我的個人網站 &#xff08;免費服務器&#xff0c;沒有80/443端口&#xff09; 1353. 最多可以參加的會議數目 給你一個數組 events&#xff0c;其中 events[i] [startDayi, endDayi] &#xff0c;表示會議 i 開始于 startDayi &#xff0c;結束于 endDayi 。 …

AI+智慧園區 | 事件處置自動化——大模型重構園區治理邏輯

在智慧園區的建設浪潮中&#xff0c;事件管理一直是園區高效運營的關鍵環節。考拉悠然所推出的大模型 智慧園區解決方案&#xff0c;在事件智能閉環管理方面獨樹一幟&#xff0c;為園區的日常運營編織了一張嚴密、高效、智能的管理網絡&#xff0c;實現了從事件感知到處置的全…

FFmpeg Windows安裝

FFmpeg 用于音頻文件轉換 Builds - CODEX FFMPEG gyan.dev ffmpeg-release-full.7z 下載完成之后 zip解壓 大概就是 ffmpeg/ └── bin/ └── ffmpeg.exe 配置環境變量 ffmpeg -version 有可能idea還是找不到命令 就把命令路徑寫在程序里 例如

【2025/07/10】GitHub 今日熱門項目

GitHub 今日熱門項目 &#x1f680; 每日精選優質開源項目 | 發現優質開源項目&#xff0c;跟上技術發展趨勢 &#x1f4cb; 報告概覽 &#x1f4ca; 統計項&#x1f4c8; 數值&#x1f4dd; 說明&#x1f4c5; 報告日期2025-07-10 (周四)GitHub Trending 每日快照&#x1f55…

JVM 基礎 - JVM 內存結構

前言 本文主要對JVM 內存結構進行講解&#xff0c;注意不要和Java內存模型混淆了。 運行時數據區 內存是非常重要的系統資源&#xff0c;是硬盤和 CPU 的中間倉庫及橋梁&#xff0c;承載著操作系統和應用程序的實時運行。JVM 內存布局規定了 Java 在運行過程中內存申請、分配…

【案例】二手車交易價格預測-472

二手車交易價格預測 數據來源數據特征探索構建模型參考數據來源 天池 https://tianchi.aliyun.com/competition/entrance/231784/information 數據特征探索 目標特征工程做好之后,能同時進行 lightgbm catboost 神經網絡等模型,所以盡量都轉換為數值類特征。 如果僅僅是使用…

【Spring】Java SPI機制及Spring Boot使用實例

目錄 一、SPI是什么 1.1 SPI 和 API 有什么區別&#xff1f; 二、使用場景 三、使用介紹 四、Spring Boot實例運用 五、總結 一、SPI是什么 SPI全稱Service Provider Interface&#xff0c;是Java提供的一套用來被第三方實現或者擴展的API&#xff0c;它可以用來啟用框架…

多維度數據資產測繪技術在安全管控平臺中的應用實踐

一、數據資產治理困境&#xff1a;從 “黑箱” 到 “可見性” 的行業挑戰在數字化轉型加速的當下&#xff0c;企業數據資產呈現爆發式增長&#xff0c;而傳統資產梳理手段因維度單一、時效性差&#xff0c;導致 “資產黑箱” 問題頻發。某省級運營商曾在安全評估中發現&#xf…

搭建react18+項目過程中遇到的問題(vite)

問題1. 頁面中使用import.meta.env獲取環境變量有紅色波浪線提示錯誤按提示給ts.config.ts文件中的compilerOptions增加了"module": “esnext” (es2020 | es2022 | system)這幾個也不行 但是另一個問題出現了安裝的第三方庫引入報錯了 按照提示我們將module改成了’…

Linux epoll簡介與C++TCP服務器代碼示例

Linux epoll 簡介與示例 TCP 服務器 1. 為什么要用 epoll select/poll 每次調用都把全部文件描述符從用戶態拷貝到內核態,隨連接數增長而線性變慢;epoll 采用事件驅動+就緒隊列的方式,內核只把“已就緒”的描述符返回給用戶態,O(1) 規模擴展;支持 邊沿觸發 Edge-Triggere…

IPv4和IPv6雙棧配置

根據IPv6的學習&#xff0c;完成以下一個簡單的雙棧配置案例&#xff0c;具體結構如下圖所示。PC1的 IPv4&#xff1a;192.168.2.1/24 、IPv6&#xff1a;2001:db8:2::2/64&#xff0c;PC2的 IPv4&#xff1a;192.168.3.1/24 、IPv6&#xff1a;2001:db8:3::2/64總共需要兩臺PC…

Robyn高性能Web框架系列08:使用 Rust 擴展 Robyn

使用 Rust 擴展 RobynPyO3 Bridge示例&#xff1a;一個簡單的Rust擴展1、安裝必須的組件2、初始化Rust項目3、編寫Rust代碼4、在Robyn中使用Rust代碼在“Robyn高性能Web框架系列07&#xff1a;多進程、性能調優”一節中&#xff0c;我們講解了Robyn豐富的性能調優方式&#xff…

利用Pandas進行條件替換與向前填充

目錄一、需求二、實現代碼案例代碼詳細解釋1. 導入庫和創建數據2. 條件替換與填充a. 條件掩碼 - mask()b. 向前填充 - ffill()c. 類型轉換 - astype(int)3. 打印結果三、實際應用場景四、可能的變體五、總結一、需求 示例數據&#xff1a; 項 目 0 1 0 1 0 1 2 0 2 3 …

springboot數據脫敏(接口級別)

文章目錄自定義脫敏注解脫敏注解接口脫敏注解反射AOP實現字段脫敏切面定義脫敏策略脫敏策略的接口電話號碼脫敏策略郵箱脫敏不脫敏姓名脫敏身份證號脫敏JacksonAOP實現脫敏定義序列化序列化實現脫敏切面定義JacksonThreadLocal攔截器實現脫敏定義ThreadLocal自定義序列化序列化…

Spring核心原理的快速入門:快速了解IoC與DI

IoC IoC&#xff1a;Inversion of Control(控制反轉) Spring是一個包含了眾多工具的IoC容器(即bean&#xff1a;spring管理的對象),也就是說Spring 是一個“控制反轉”的容器。 之前是對象本身管理自己的生命周期等等&#xff0c;現在交給spring來管理對象的生命周期 IoC介紹 …

ffmpeg 中config 文件一些理解

依賴檢查 config中看到最多的是&#xff1a; ... nvenc_deps"ffnvcodec" nvenc_deps_any"libdl LoadLibrary" nvenc_encoder_deps"nvenc" ... h264_crystalhd_decoder_select"crystalhd h264_mp4toannexb_bsf h264_parser" h264_cuvid…

Digital Rainwater Collection System (v1.0)

The law doesn’t punish the masses. If only one guy runs his own rainwater system, he gets fined for “illegal mining.” But if millions of households self-host their “digital wells,” the whole centralized model collapses. Cloud providers and regulators …

NFS文件存儲及部署論壇(小白的“升級打怪”成長之路)

目錄 一、概述 NFS掛載原理 NFS工作原理 RPC與NFS通訊過程 二、NFS服務安裝與啟停 NFS服務安裝 NFS服務啟停 三、NFS服務配置文件 四、NFS文件共享配置文件 配置參數說明 五、命令解析 六、客戶端訪問 七、客戶端掛載 實戰案例 部署NFS文件存儲及discuz論壇應用 …

JavaScript 對象創建:new 操作符全解析

引言 在 JavaScript 中&#xff0c;new 操作符是實現面向對象編程的??核心機制??之一。本文將從原理層面對 new 操作符進行深度剖析&#xff0c;探討其工作機制、內部實現和實際應用場景。無論您是 JavaScript 初學者還是資深開發者&#xff0c;都能從本文獲得以下知識和技…