Android Studio C++/JNI/Kotlin 示例 一

发布于:2025-07-15 ⋅ 阅读:(22) ⋅ 点赞:(0)

创建一个名为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 = 35

    defaultConfig {
        applicationId = "com.demo.learn1"
        minSdk = 30
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary = true
        }

        externalNativeBuild {
            cmake {
                cppFlags.add("-std=c++17") // 推荐使用C++17标准
                // 现代Android设备主要支持arm64-v8a,可以精简ABI
                abiFilters.add("arm64-v8a")
            }
        }
    }

    externalNativeBuild {
        cmake {
            path = file("src/main/cpp/CMakeLists.txt")
            version = "3.22.1"
        }
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = 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-lib
        SHARED
        native-lib.cpp
        utils.cpp
        decoder/audio_decoder.cpp  # 支持子目录
)
(2) 添加头文件路径

如果头文件在 cpp/include 目录:

# 添加头文件搜索路径
target_include_directories(
        native-lib
        PRIVATE
        ${CMAKE_SOURCE_DIR}/cpp/include
)
(3) 链接第三方库

假设你通过 NDK 编译了一个静态库 (libfoo.a):

# 添加静态库路径
add_library(foo STATIC IMPORTED)
set_target_properties(
        foo
        PROPERTIES 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++ 类型
jboolean boolean unsigned char
jint int int32_t
jlong long int64_t
jfloat float float
jdouble double double
jstring String const char*
jobject Object void*
jobjectArray Object[] jobject[]

5. 内存管理与注意事项

  1. 局部引用 vs 全局引用

    • 局部引用(如 jstring)在函数返回后会自动释放,但大量创建时需手动调用 DeleteLocalRef()

    • 全局引用需显式创建(NewGlobalRef())和释放(DeleteGlobalRef())。

  2. 字符串转换

    • GetStringUTFChars() 和 GetStringChars() 返回的字符串必须调用对应的 Release 方法。

    • NewStringUTF() 创建的 jstring 无需手动释放。

五、编写 Kotlin 代码 

MainActivity.kt

package com.demo.learn1

import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity

class MainActivity : ComponentActivity() {

    // 加载原生库
    init {
        System.loadLibrary("native_code")
    }

    // 声明原生方法
    private external fun stringFromJNI(): String
    private external fun addNumbers(a: Int, b: Int): Int
    private external fun processStringArray(array: Array<String>): String

    override 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)。

六、结果打印


网站公告

今日签到

点亮在社区的每一天
去签到