当前位置: 首页 > news >正文

学习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.lryc.cn/news/584649.html

相关文章:

  • 机器学习1
  • Java线程池原理概述
  • Spring Boot:将应用部署到Kubernetes的完整指南
  • 什么?不知道 MyBatisPlus 多数据源(动态数据源)干什么的,怎么使用,看这篇文章就够了。
  • Windows安装DevEco Studio
  • 深入理解oracle ADG和RAC
  • 高并发导致重复key问题--org.springframework.dao.DuplicateKeyException
  • 企业电商平台搭建:ZKmall开源商城服务器部署与容灾方案
  • Java中实现线程安全的几种方式
  • 华为OD 周末爬山
  • 模块三:现代C++工程实践(4篇)第二篇《性能调优:Profile驱动优化与汇编级分析》
  • 关于k8s Kubernetes的10个面试题
  • 【牛客刷题】跳台阶(三种解法深度分析)
  • Java 21 核心技术:虚拟线程与结构化并发实战
  • Django专家成长路线知识点——AI教你学Django
  • Spring Boot + Javacv-platform:解锁音视频处理的多元场景
  • 处理Web请求路径参数
  • 小程序开发平台,自主开发小程序源码系统,多端适配,带完整的部署教程
  • GitHub上优秀的开源播放器项目介绍及优劣对比
  • PPT 倒计时工具:把控节奏,掌握时间,超简单超实用让演示游刃有余
  • 电脑息屏工具,一键黑屏超方便
  • C语言——预处理详解
  • ADVANTEST R4131 SPECTRUM ANALYZER 光谱分析仪
  • arm架构,arm内核,处理器之间的关系
  • 【JVM|垃圾回收】第二天
  • AI时代的接口调试与文档生成:Apipost 与 Apifox 的表现对比
  • 发现和发明浅谈
  • IDEA运行Spring项目报错:java: 警告: 源发行版 17 需要目标发行版 17,java: 无效的目标发行版: 17
  • 零基础入门物联网-远程门禁开关:云平台创建
  • 【洛谷题单】--顺序结构(二)