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

发布于:2025-07-17 ⋅ 阅读:(18) ⋅ 点赞:(0)

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 computeFactorial(n: Int): Int
    private external fun computeFibonacci(n: Int): Int

    // 字符串处理
    private external fun reverseString(input: String): String
    private external fun countVowels(input: String): Int

    // 数组处理
    private external fun sumIntArray(array: IntArray): Int

    // 复杂对象处理
    data class User(val name: String, val age: Int)
    private external fun processUserData(user: User): String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 计算5的阶乘和斐波那契数列第10项
        Log.d("Native", "5! = ${computeFactorial(5)}")
        Log.d("Native", "fib(10) = ${computeFibonacci(10)}")

        // 测试字符串反转和元音计数功能
        val testStr = "Hello, JNI!"
        Log.d("Native", "Reversed: ${reverseString(testStr)}")
        Log.d("Native", "Vowel count: ${countVowels(testStr)}")

        // 计算数组元素的和
        val numbers = intArrayOf(1, 2, 3, 4, 5)
        Log.d("Native", "Array sum: ${sumIntArray(numbers)}")

        // 创建User对象并传递给本地方法处理
        val user = User("张三", 25)
        Log.d("Native", processUserData(user))
    }
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1) #指定 CMake 的最低版本要求(这里是 3.4.1,Android NDK 的常见要求)
set(CMAKE_CXX_STANDARD 11)  # 启用C++11支持

# 定义库和源文件
add_library( # 设置库名称
        native_code

        # 设置库类型为共享库
        SHARED

        # 提供源文件的相对路径
        jni_interface.cpp
        math_operations.cpp
        string_processor.cpp
        data_converter.cpp
)

# 查找日志库
find_library( # 设置路径变量名称
        log-lib

        # 指定CMake要查找的NDK库名称
        log )

# 指定库应该链接到的目标库
target_link_libraries( # 指定目标库
        native_code

        # 将目标库链接到日志库
        ${log-lib} )

jni_interface.cpp

这段代码是用C++实现的JNI(Java Native Interface)接口,它连接了Java/Kotlin代码和本地C++代码。

#include "jni_interface.h"
#include <jni.h> //JNI核心头文件,提供与Java交互的所有必要定义
#include <string> //C++标准字符串库
#include <android/log.h> //Android日志输出功能
// 包含自定义类头文件
#include "math_operations.h"
#include "string_processor.h"
#include "data_converter.h"
//定义了日志宏LOGI,方便输出调试信息
#define LOG_TAG "NativeCode"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

extern "C" {

// 数学运算示例
JNIEXPORT jint JNICALL
Java_com_demo_learn1_MainActivity_computeFactorial(
        JNIEnv* env,
        jobject /* this */,
        jint n) {
    return math_operations::factorial(n);
}

JNIEXPORT jint JNICALL
Java_com_demo_learn1_MainActivity_computeFibonacci(
        JNIEnv* env,
        jobject /* this */,
        jint n) {
    return math_operations::fibonacci(n);
}

// 字符串处理示例
JNIEXPORT jstring JNICALL
Java_com_demo_learn1_MainActivity_reverseString(
        JNIEnv* env,
        jobject /* this */,
        jstring input) {
    std::string cppStr = data_converter::convertJavaStringToCpp(env, input);
    std::string reversed = string_processor::reverseString(cppStr);
    return env->NewStringUTF(reversed.c_str());
}

JNIEXPORT jint JNICALL
Java_com_demo_learn1_MainActivity_countVowels(
        JNIEnv* env,
        jobject /* this */,
        jstring input) {
    std::string cppStr = data_converter::convertJavaStringToCpp(env, input);
    return string_processor::countVowels(cppStr);
}

// 数组处理示例
JNIEXPORT jint JNICALL
Java_com_demo_learn1_MainActivity_sumIntArray(
        JNIEnv* env,
        jobject /* this */,
        jintArray array) {
    std::vector<int> numbers = data_converter::convertJavaArrayToVector(env, array);
    int sum = 0;
    for (int num : numbers) {
        sum += num;
    }
    return sum;
}

// 复杂对象示例
JNIEXPORT jstring JNICALL
Java_com_demo_learn1_MainActivity_processUserData(
        JNIEnv* env,
        jobject /* this */,
        jobject user) {
    jclass userClass = env->GetObjectClass(user);

    // 获取字段ID
    jfieldID nameField = env->GetFieldID(userClass, "name", "Ljava/lang/String;");
    jfieldID ageField = env->GetFieldID(userClass, "age", "I");

    // 获取字段值
    jstring name = (jstring)env->GetObjectField(user, nameField);
    jint age = env->GetIntField(user, ageField);

    // 处理数据
    std::string cppName = data_converter::convertJavaStringToCpp(env, name);
    std::string processed = "User: " + cppName + ", Age: " + std::to_string(age);

    // 清理引用
    env->DeleteLocalRef(name);
    env->DeleteLocalRef(userClass);

    return env->NewStringUTF(processed.c_str());
}

} // extern "C"

processUserData方法讲解

  • 参数

    • JNIEnv* env:JNI环境指针,提供所有JNI功能

    • jobject this:Java中调用该native方法的对象实例(本例未使用)

    • jobject user:传入的Java User对象

1.获取Java类信息

jclass userClass = env->GetObjectClass(user);
  • GetObjectClass:获取传入Java对象的类对象

  • 返回的jclass是Java中Class<User>的本地表示

  • 这是后续访问字段和方法的基础

2. 获取字段ID

jfieldID nameField = env->GetFieldID(userClass, "name", "Ljava/lang/String;");
jfieldID ageField = env->GetFieldID(userClass, "age", "I");
  • GetFieldID:获取字段标识符,需要:

    • 类对象(userClass)

    • 字段名("name""age")

    • 字段签名:

      • "Ljava/lang/String;":Java的String类型

      • "I":Java的int类型

  • 字段ID是后续访问字段的"钥匙"

3. 获取字段值

jstring name = (jstring)env->GetObjectField(user, nameField);
jint age = env->GetIntField(user, ageField);
  • GetObjectField:获取对象类型字段的值(如String)

    • 需要对象实例和字段ID

    • 返回jobject,需要转型为具体类型(如jstring

  • GetIntField:获取基本类型int字段的值

    • 直接返回对应的基本类型(jint实际上是int的别名)

4. 数据处理

std::string cppName = data_converter::convertJavaStringToCpp(env, name);
std::string processed = "User: " + cppName + ", Age: " + std::to_string(age);
  • 将Java字符串转换为C++字符串(使用辅助工具类)

  • 构造新的字符串信息,组合name和age

  • std::to_string:将数字转换为字符串

5. 资源清理

env->DeleteLocalRef(name);
env->DeleteLocalRef(userClass);
  • DeleteLocalRef:删除本地引用,防止内存泄漏

  • JNI中的本地引用在函数返回后不会自动释放

  • 虽然现代JVM通常能处理这些引用,但显式释放是良好实践

6. 返回结果

return env->NewStringUTF(processed.c_str());
  • NewStringUTF:将C字符串(UTF-8)转换为Java字符串(jstring)

  • 这个返回的jstring会被自动管理,不需要手动释放

1. 基本类型映射

JNI定义了与Java基本类型对应的C/C++类型:

Java类型 JNI类型 C/C++类型 大小
boolean jboolean unsigned char 8位
byte jbyte signed char 8位
char jchar unsigned short 16位
short jshort short 16位
int jint int 32位
long jlong long long 64位
float jfloat float 32位
double jdouble double 64位

2. 理解类型转换

字符串转换

Java字符串(jstring)与C/C++字符串的转换是常见操作:

// Java String → C++ std::string
std::string jstringToStdString(JNIEnv* env, jstring jStr) {
    if (!jStr) return "";
    
    const char* cStr = env->GetStringUTFChars(jStr, nullptr);
    std::string cppStr(cStr);
    env->ReleaseStringUTFChars(jStr, cStr);
    
    return cppStr;
}

// C++ std::string → Java String
jstring stdStringToJstring(JNIEnv* env, const std::string& cppStr) {
    return env->NewStringUTF(cppStr.c_str());
}

数组转换

处理Java数组更复杂一些:

// Java int[] → C++ std::vector<int>
std::vector<int> jintArrayToVector(JNIEnv* env, jintArray array) {
    std::vector<int> result;
    
    jsize length = env->GetArrayLength(array);
    if (length <= 0) return result;
    
    jint* elements = env->GetIntArrayElements(array, nullptr);
    result.assign(elements, elements + length);
    env->ReleaseIntArrayElements(array, elements, 0);
    
    return result;
}

// C++ std::vector<int> → Java int[]
jintArray vectorToJintArray(JNIEnv* env, const std::vector<int>& vec) {
    jintArray result = env->NewIntArray(vec.size());
    env->SetIntArrayRegion(result, 0, vec.size(), vec.data());
    return result;
}

3. 内存管理和引用释放

引用类型

JNI有三种引用类型:

  1. 局部引用(Local Reference)

    • 默认创建的引用

    • 仅在当前native方法有效

    • 方法返回后自动释放,但应及时手动释放

  2. 全局引用(Global Reference)

    • 需要显式创建:env->NewGlobalRef()

    • 跨多个native调用有效

    • 必须显式释放:env->DeleteGlobalRef()

  3. 弱全局引用(Weak Global Reference)

    • 类似全局引用但不阻止GC

    • 创建:env->NewWeakGlobalRef()

    • 释放:env->DeleteWeakGlobalRef()

最佳实践

JNIEXPORT void JNICALL Java_Example_memoryExample(JNIEnv* env, jobject obj) {
    // 1. 创建局部引用
    jclass localClass = env->FindClass("java/lang/String");
    
    // 2. 提升为全局引用
    jclass globalClass = (jclass)env->NewGlobalRef(localClass);
    
    // 3. 不再需要局部引用
    env->DeleteLocalRef(localClass);
    
    // ...使用globalClass...
    
    // 4. 最后释放全局引用
    env->DeleteGlobalRef(globalClass);
}

4. 高级主题

缓存字段ID和方法ID

// 在全局变量中缓存
struct CachedIDs {
    jclass exampleClass;
    jmethodID callbackMethod;
    jfieldID valueField;
};

bool cacheIds(JNIEnv* env) {
    static CachedIDs cached;
    
    cached.exampleClass = (jclass)env->NewGlobalRef(env->FindClass("com/example/Example"));
    cached.callbackMethod = env->GetMethodID(cached.exampleClass, "callback", "(I)V");
    cached.valueField = env->GetFieldID(cached.exampleClass, "value", "I");
    
    return cached.exampleClass && cached.callbackMethod && cached.valueField;
}

JNIEXPORT void JNICALL Java_Example_useCachedIds(JNIEnv* env, jobject obj) {
    static bool cached = cacheIds(env);
    if (!cached) return;
    
    // 使用缓存的IDs
    jint value = env->GetIntField(obj, cached.valueField);
    env->CallVoidMethod(obj, cached.callbackMethod, value + 1);
}

多线程注意事项

// 获取JVM指针以便在其他线程使用
JavaVM* g_jvm = nullptr;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    g_jvm = vm;
    return JNI_VERSION_1_6;
}

void backgroundThreadFunction() {
    JNIEnv* env;
    int status = g_jvm->AttachCurrentThread(&env, nullptr);
    if (status < 0) {
        // 处理错误
        return;
    }
    
    // 在这里可以安全使用JNI
    // ...
    
    g_jvm->DetachCurrentThread();
}

math_operations.h

#ifndef LEARN1_MATH_OPERATIONS_H
#define LEARN1_MATH_OPERATIONS_H


class math_operations {
public:
    // 声明为静态成员函数(可通过类名直接调用)
    static int factorial(int n);
    static int fibonacci(int n);
    static bool isPrime(int num);
};


#endif //LEARN1_MATH_OPERATIONS_H

1.头文件保护宏

#ifndef LEARN1_MATH_OPERATIONS_H
#define LEARN1_MATH_OPERATIONS_H
// ...
#endif

这是防止头文件被多次包含的标准做法,避免重复定义错误。

2.类定义

class math_operations {
public:
    // ...
};

定义了一个名为math_operations的类,public:表示后续成员都是公开的。

3.静态成员函数

static int factorial(int n);
static int fibonacci(int n);
static bool isPrime(int num);

这些是静态成员函数的声明,特点是:

  1. static关键字:表示这些函数是静态成员函数

    • 属于类而不是类的实例

    • 可以直接通过类名调用,不需要创建对象实例

    • 不能访问类的非静态成员

  2. 函数声明

    • 只提供了函数原型(返回类型、函数名、参数列表)

    • 没有函数体实现(通常在对应的.cpp文件中实现)

为什么使用静态成员函数

  1. 工具类:当函数逻辑不依赖于对象状态时

  2. 命名空间替代:在C++中可以用来组织相关函数(替代命名空间)

  3. 无需实例化:可以直接调用,使用更方便


math_operations.cpp

#include "math_operations.h"

int math_operations::factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

int math_operations::fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n-1) + fibonacci(n-2);
}

bool math_operations::isPrime(int num) {
    if (num <= 1) return false;
    for (int i = 2; i * i <= num; i++) {
        if (num % i == 0) return false;
    }
    return true;
}
#include "math_operations.h"
  • 作用:包含对应的头文件,确保函数声明与定义一致

  • 语法:使用引号""表示从当前目录或项目目录查找头文件

data_converter.h

#ifndef LEARN1_DATA_CONVERTER_H
#define LEARN1_DATA_CONVERTER_H

#include <vector>
#include <string>
#include <jni.h>  // 必须包含JNI头文件

class data_converter {
public:
    static std::vector<int> convertJavaArrayToVector(JNIEnv* env, jintArray array);
    static std::string convertJavaStringToCpp(JNIEnv* env, jstring jStr);
};


#endif //LEARN1_DATA_CONVERTER_H
  • <vector>:提供C++标准向量容器支持

  • <string>:提供C++字符串支持

  • <jni.h>:JNI开发必需的头文件,定义了Java和本地代码交互的所有类型和函数

  • JNIEnv* env:JNI环境指针,提供所有JNI函数访问

data_converter.cpp

#include "data_converter.h"
#include <vector>

std::vector<int> data_converter::convertJavaArrayToVector(JNIEnv* env, jintArray array) {
    std::vector<int> result;
    jsize length = env->GetArrayLength(array);
    jint* elements = env->GetIntArrayElements(array, nullptr);

    result.assign(elements, elements + length);

    env->ReleaseIntArrayElements(array, elements, 0);
    return result;
}

std::string data_converter::convertJavaStringToCpp(JNIEnv* env, jstring jStr) {
    const char* cStr = env->GetStringUTFChars(jStr, nullptr);
    std::string result(cStr);
    env->ReleaseStringUTFChars(jStr, cStr);
    return result;
}

1. convertJavaArrayToVector 方法

功能

将 Java 的 int[] 数组转换为 C++ 的 std::vector<int>

实现步骤:

  1. 创建空 vectorstd::vector<int> result

  2. 获取数组长度

    • env->GetArrayLength(array) 获取 Java 数组的长度

    • jsize 是 JNI 中表示大小的类型

  3. 获取数组元素指针

    • env->GetIntArrayElements(array, nullptr) 获取指向 Java 数组内容的指针

    • 第二个参数 nullptr 表示不关心是否复制了数组

  4. 填充 vector

    • result.assign(elements, elements + length) 将 Java 数组内容复制到 vector

  5. 释放资源

    • env->ReleaseIntArrayElements(array, elements, 0) 释放获取的数组指针

    • 参数 0 表示将内容复制回原数组并释放临时内存

  6. 返回结果:包含 Java 数组数据的 vector

关键点:

  • 必须成对调用 GetIntArrayElements 和 ReleaseIntArrayElements

  • assign 方法高效地将 C 风格数组复制到 vector

  • 最后一个参数 0 可以是:

    • 0:复制回原数组并释放临时内存

    • JNI_ABORT:不复制回原数组但释放内存

    • JNI_COMMIT:复制回原数组但不释放内存

2. convertJavaStringToCpp 方法

功能

将 Java 的 String 对象转换为 C++ 的 std::string

实现步骤:

  1. 获取 UTF-8 字符指针

    • env->GetStringUTFChars(jStr, nullptr) 获取指向 Java 字符串 UTF-8 编码的指针

    • 第二个参数 nullptr 表示不关心是否复制了字符串

  2. 创建 std::string

    • std::string result(cStr) 用获取的字符指针构造 C++ 字符串

  3. 释放资源

    • env->ReleaseStringUTFChars(jStr, cStr) 释放获取的字符指针

  4. 返回结果:包含 Java 字符串内容的 std::string

关键点:

  • 必须成对调用 GetStringUTFChars 和 ReleaseStringUTFChars

  • 使用 UTF-8 编码转换,这是 Java 和 C++ 之间最常用的编码方式

  • 如果 Java 字符串包含非 ASCII 字符,这种方式能正确处理

3. 可能的改进

  1. 添加空指针检查

    if (!jStr) return std::string();
  2. 错误处理增强

    const char* cStr = env->GetStringUTFChars(jStr, nullptr);
    if (!cStr) {
        // 处理错误
    }

string_processor.h

#ifndef LEARN1_STRING_PROCESSOR_H
#define LEARN1_STRING_PROCESSOR_H

#include <string>

class string_processor {
public:
    // 1. 字符串反转
    static std::string reverseString(const std::string& input);

    // 2. 统计元音字母数量
    static int countVowels(const std::string& input);

    // 3. 凯撒加密(字母位移)
    static std::string encryptString(const std::string& input, int shift);
};


#endif //LEARN1_STRING_PROCESSOR_H
  • const std::string& input:要反转的字符串(常量引用,避免拷贝)

const std::string& 讲解

1. 基本组成解析

可以分解为三个部分:

  1. std::string - C++标准库中的字符串类型

  2. & - 表示这是一个引用

  3. const - 表示这是一个常量(不可修改)

2. 为什么要使用这种形式

2.1 避免不必要的拷贝
  • 不使用引用void func(std::string str)
    传递参数时会创建字符串的完整副本(拷贝构造)

  • 使用引用void func(const std::string& str)
    只传递引用(内存地址),不创建副本

2.2 保证原字符串不被修改
  • const限定确保函数内不能修改原字符串

  • 既享受引用的高效,又保证数据安全

2.3 支持临时对象
  • 可以接受临时字符串对象(右值)

  • 例如:func("temporary string")

3. 与替代方案的对比

参数形式 拷贝开销 可修改原值 接受临时对象 备注
std::string 副本可修改 最安全但效率最低
std::string& 可以修改 需要非常量左值
const std::string& 不可修改 最佳平衡方案
std::string_view (C++17) 不可修改 现代替代方案

4. 典型使用场景

4.1 作为输入参数
void printString(const std::string& str) {
    std::cout << str;  // 只能读取,不能修改
}
4.2 与STL算法配合
bool contains(const std::string& str, const std::string& substr) {
    return str.find(substr) != std::string::npos;
}
4.3 类成员函数
class TextProcessor {
public:
    void process(const std::string& input) {
        // 处理输入但不修改它
    }
};

5. 注意事项

  1. 生命周期管理
    • 引用必须确保指向的对象在函数使用期间有效

    • 不要返回局部变量的const引用

  2. C++17后的替代方案
    • 考虑使用std::string_view作为只读参数

    • 更轻量级,支持更多类型的字符串数据

  3. 与移动语义的关系
    • 对于需要"夺取"所有权的情况,应使用std::string&&(右值引用)

    • const&会阻止移动语义的应用

  4. NULL问题
    • 不能直接传递NULL/nullptr

    • 如果需要可空引用,应使用指针或std::optional

string_processor.cpp

#include "string_processor.h"
#include <algorithm> // 提供std::reverse
#include <string>  // 提供std::string

std::string string_processor::reverseString(const std::string& input) {
    std::string reversed = input;
    std::reverse(reversed.begin(), reversed.end());
    return reversed;
}

int string_processor::countVowels(const std::string& input) {
    int count = 0;
    const std::string vowels = "aeiouAEIOU";
    for (char c : input) {
        if (vowels.find(c) != std::string::npos) {
            count++;
        }
    }
    return count;
}

std::string string_processor::encryptString(const std::string& input, int shift) {
    std::string result;
    for (char c : input) {
        if (isalpha(c)) {
            char base = isupper(c) ? 'A' : 'a';
            c = ((c - base + shift) % 26) + base;
        }
        result += c;
    }
    return result;
}

结果


网站公告

今日签到

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