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

Android JNI 回调示例,展示了本地 C++ 代码如何通过 JNI 回调 Java 层接口。

MainActivity.kt

Kotlin 复制代码
package com.demo.learn2

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

class MainActivity : ComponentActivity() {

    // 用于存储本地对象的指针
    private var nativeWorkerPtr: Long = 0  // 初始化为 0

    // 加载本地库
    init {
        System.loadLibrary("native_code")
    }

    // 本地方法声明
    external fun startNativeThread(callback: NativeCallback)
    external fun stopNativeThread()

    // 回调接口
    interface NativeCallback {
        fun onProgressUpdate(progress: Int)
        fun onMessageReceived(message: String)
        fun onError(errorCode: Int, errorMessage: String)
    }

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

        // 创建回调实现
        val callback = object : NativeCallback {
            override fun onProgressUpdate(progress: Int) {
                Log.d("JNICallback", "Progress updated: $progress%")
            }

            override fun onMessageReceived(message: String) {
                Log.d("JNICallback", "Message received: $message")
            }

            override fun onError(errorCode: Int, errorMessage: String) {
                Log.e("JNICallback", "Error $errorCode: $errorMessage")
            }
        }

        // 启动本地线程
        startNativeThread(callback)

        // 10秒后停止线程(示例)
         Handler(Looper.getMainLooper()).postDelayed({
             stopNativeThread()
         }, 10000)
    }
}

CMakeLists.txt

cpp 复制代码
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_CXX_STANDARD 11)  # 启用C++11支持

project("native_code") #定义项目名称为 "native_code"
add_library(
        native_code
        SHARED
        native_code.cpp
        native_code.h
)

#设置头文件搜索路径
target_include_directories(
        native_code # 目标库名称
        PRIVATE # 表示这些路径仅用于构建该库
        ${CMAKE_SOURCE_DIR} # CMake 变量,表示项目根目录
)

find_library(
        log-lib
        log
)

target_link_libraries(
        native_code
        android # 链接 Android NDK 平台库
        ${log-lib}
)

native_code.h

cpp 复制代码
#ifndef LEARN2_NATIVE_CODE_H
#define LEARN2_NATIVE_CODE_H


#include <jni.h>
#include <string>
#include <thread>
#include <atomic>

class NativeWorker {
public:
    NativeWorker(JNIEnv* env, jobject callback);
    ~NativeWorker();

    void start();
    void stop();
    bool isRunning() const;

private:
    void run();

    JNIEnv* getJNIEnv(bool* attached);
    void detachJNIEnv(bool attached);

    JavaVM* jvm;
    jobject javaCallbackRef;
    jmethodID onProgressUpdateMethod;
    jmethodID onMessageReceivedMethod;
    jmethodID onErrorMethod;

    std::thread workerThread;
    std::atomic<bool> running;
};

#endif //LEARN2_NATIVE_CODE_H

NativeWorker 类定义

1. 公共接口

  • 构造函数:接收 JNI 环境和 Java 回调对象

  • 析构函数:负责资源清理

  • start():启动工作线程

  • stop():停止工作线程

  • isRunning():检查线程是否在运行

2. 私有成员

2.1 核心方法
  • run():工作线程的主逻辑

  • getJNIEnv():获取当前线程的 JNI 环境

  • detachJNIEnv():从当前线程分离 JVM

2.2 JNI 相关成员
  • jvm:保存 JavaVM 引用,用于后续获取 JNIEnv

  • javaCallbackRef:Java 回调对象的全局引用

  • onXXXMethod:三个 Java 回调方法的 ID

2.3 线程控制成员
  • workerThread:工作线程对象

  • running:原子布尔标志,用于线程安全地控制线程运行状态

3.关键设计要点

3.1 JNI 环境管理
  1. JavaVM 保存:在构造函数中保存 JavaVM,用于后续在任何线程获取 JNIEnv

  2. 全局引用:将 Java 回调对象转换为全局引用,防止被垃圾回收

  3. 方法 ID 缓存:提前获取方法 ID 提升性能

3.2 线程安全设计
  1. 原子标志 :使用 std::atomic<bool> 确保 running 标志的线程安全访问

  2. 线程生命周期 :通过 start()/stop() 明确控制线程生命周期

  3. 资源清理:析构函数确保线程停止和资源释放

3.3 跨线程回调机制
  1. 全局引用:允许在不同线程回调 Java 方法

  2. JNIEnv 获取getJNIEnv() 处理线程附加/分离

4. 典型使用场景

cpp 复制代码
// JNI 函数中创建 worker
NativeWorker* worker = new NativeWorker(env, callback);
worker->start();

// ...

worker->stop();
delete worker;

native_code.cpp

cpp 复制代码
#include "native_code.h"
#include <android/log.h>

#define LOG_TAG "NativeCode"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

NativeWorker::NativeWorker(JNIEnv* env, jobject callback) : running(false) {
    // 保存JavaVM引用
    env->GetJavaVM(&jvm);

    // 创建全局引用,防止被垃圾回收
    javaCallbackRef = env->NewGlobalRef(callback);

    // 获取回调方法ID
    jclass callbackClass = env->GetObjectClass(callback);
    onProgressUpdateMethod = env->GetMethodID(callbackClass, "onProgressUpdate", "(I)V");
    onMessageReceivedMethod = env->GetMethodID(callbackClass, "onMessageReceived", "(Ljava/lang/String;)V");
    onErrorMethod = env->GetMethodID(callbackClass, "onError", "(ILjava/lang/String;)V");

    if (!onProgressUpdateMethod || !onMessageReceivedMethod || !onErrorMethod) {
        LOGE("Failed to get method IDs");
    }
}

NativeWorker::~NativeWorker() {
    stop();

    bool attached = false;
    JNIEnv* env = getJNIEnv(&attached);
    if (env) {
        env->DeleteGlobalRef(javaCallbackRef);
    }
    detachJNIEnv(attached);
}

void NativeWorker::start() {
    if (running) return;

    running = true;
    workerThread = std::thread(&NativeWorker::run, this);
}

void NativeWorker::stop() {
    if (!running) return;

    running = false;
    if (workerThread.joinable()) {
        workerThread.join();
    }
}

bool NativeWorker::isRunning() const {
    return running;
}

//获取当前线程的 JNI 环境,必要时附加到 JVM
JNIEnv* NativeWorker::getJNIEnv(bool* attached) {
    *attached = false;
    JNIEnv* env = nullptr;

    // 获取当前线程的JNIEnv
    jint status = jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
    if (status == JNI_EDETACHED) {
        // 如果当前线程未附加到JVM,附加它
        if (jvm->AttachCurrentThread(&env, nullptr) == JNI_OK) {
            *attached = true;
        } else {
            LOGE("Failed to attach thread to JVM");
            return nullptr;
        }
    } else if (status != JNI_OK) {
        LOGE("Failed to get JNIEnv, status: %d", status);
        return nullptr;
    }

    return env;
}

//如果需要,从当前线程分离 JVM
void NativeWorker::detachJNIEnv(bool attached) {
    if (attached) {
        jvm->DetachCurrentThread();
    }
}

void NativeWorker::run() {
    int progress = 0;

    while (running && progress < 100) {
        // 模拟工作
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        progress += 10;

        bool attached = false;
        JNIEnv* env = getJNIEnv(&attached);
        if (!env) {
            LOGE("Failed to get JNIEnv in worker thread");
            continue;
        }

        // 回调进度更新
        env->CallVoidMethod(javaCallbackRef, onProgressUpdateMethod, progress);

        // 检查是否有异常
        if (env->ExceptionCheck()) {
            env->ExceptionDescribe();
            env->ExceptionClear();
            LOGE("Exception occurred during callback");
        }

        // 每隔几次发送消息
        if (progress % 20 == 0) {
            jstring message = env->NewStringUTF("Progress milestone reached");
            env->CallVoidMethod(javaCallbackRef, onMessageReceivedMethod, message);
            env->DeleteLocalRef(message);

            if (env->ExceptionCheck()) {
                env->ExceptionDescribe();
                env->ExceptionClear();
                LOGE("Exception occurred during message callback");
            }
        }

        detachJNIEnv(attached);
    }

    // 工作完成或停止
    bool attached = false;
    JNIEnv* env = getJNIEnv(&attached);
    if (env) {
        if (progress >= 100) {
            jstring message = env->NewStringUTF("Work completed");
            env->CallVoidMethod(javaCallbackRef, onMessageReceivedMethod, message);
            env->DeleteLocalRef(message);
        } else {
            jstring message = env->NewStringUTF("Work stopped");
            env->CallVoidMethod(javaCallbackRef, onMessageReceivedMethod, message);
            env->DeleteLocalRef(message);
        }

        if (env->ExceptionCheck()) {
            env->ExceptionDescribe();
            env->ExceptionClear();
        }

        detachJNIEnv(attached);
    }
}

// JNI函数实现
extern "C" {
    JNIEXPORT void JNICALL
    Java_com_demo_learn2_MainActivity_startNativeThread(JNIEnv* env, jobject thiz, jobject callback) {
        // 创建并启动worker
        NativeWorker* worker = new NativeWorker(env, callback);
        worker->start();

        // 存储worker指针到Java对象(简化示例,实际应更安全地处理)
        jclass clazz = env->GetObjectClass(thiz);
        jfieldID fieldId = env->GetFieldID(clazz, "nativeWorkerPtr", "J");
        if (fieldId) {
            env->SetLongField(thiz, fieldId, reinterpret_cast<jlong>(worker));
        } else {
            LOGE("Failed to find nativeWorkerPtr field");
        }
    }

    JNIEXPORT void JNICALL
    Java_com_demo_learn2_MainActivity_stopNativeThread(JNIEnv* env, jobject thiz) {
        // 获取worker指针
        jclass clazz = env->GetObjectClass(thiz);
        jfieldID fieldId = env->GetFieldID(clazz, "nativeWorkerPtr", "J");
        if (!fieldId) {
            LOGE("Failed to find nativeWorkerPtr field");
            return;
        }

        //停止并删除对象,清除指针
        jlong ptr = env->GetLongField(thiz, fieldId);
        NativeWorker* worker = reinterpret_cast<NativeWorker*>(ptr);
        if (worker) {
            worker->stop();
            delete worker;
            env->SetLongField(thiz, fieldId, 0L);
        }
    }
}

NativeWorker 类构造函数

cpp 复制代码
NativeWorker::NativeWorker(JNIEnv* env, jobject callback) : running(false) {
    // 保存JavaVM引用
    env->GetJavaVM(&jvm);

    // 创建全局引用,防止被垃圾回收
    javaCallbackRef = env->NewGlobalRef(callback);

    // 获取回调方法ID
    jclass callbackClass = env->GetObjectClass(callback);
    onProgressUpdateMethod = env->GetMethodID(callbackClass, "onProgressUpdate", "(I)V");
    onMessageReceivedMethod = env->GetMethodID(callbackClass, "onMessageReceived", "(Ljava/lang/String;)V");
    onErrorMethod = env->GetMethodID(callbackClass, "onError", "(ILjava/lang/String;)V");
}
  • 构造函数接收 JNI 环境和 Java 回调对象

  • 保存 JavaVM 引用(用于后续获取 JNIEnv)

  • 创建回调对象的全局引用(防止被垃圾回收)

  • 获取回调方法的 ID(用于后续调用)

NativeWorker 类析构函数

cpp 复制代码
NativeWorker::~NativeWorker() {
    stop();

    bool attached = false;
    JNIEnv* env = getJNIEnv(&attached);
    if (env) {
        env->DeleteGlobalRef(javaCallbackRef);
    }
    detachJNIEnv(attached);
}
  • 停止工作线程

  • 获取 JNI 环境

  • 删除全局引用

  • 如果需要,从当前线程分离 JVM

工作线程主逻辑

cpp 复制代码
void NativeWorker::run() {
    int progress = 0;

    while (running && progress < 100) {
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        progress += 10;

        bool attached = false;
        JNIEnv* env = getJNIEnv(&attached);
        if (!env) continue;

        // 回调进度更新
        env->CallVoidMethod(javaCallbackRef, onProgressUpdateMethod, progress);

        // 检查异常
        if (env->ExceptionCheck()) {
            env->ExceptionDescribe();
            env->ExceptionClear();
            LOGE("Exception occurred during callback");
        }

        // 每隔20%发送消息
        if (progress % 20 == 0) {
            jstring message = env->NewStringUTF("Progress milestone reached");
            env->CallVoidMethod(javaCallbackRef, onMessageReceivedMethod, message);
            env->DeleteLocalRef(message);

            if (env->ExceptionCheck()) {
                env->ExceptionDescribe();
                env->ExceptionClear();
                LOGE("Exception occurred during message callback");
            }
        }

        detachJNIEnv(attached);
    }

    // 工作完成或停止后的处理
    bool attached = false;
    JNIEnv* env = getJNIEnv(&attached);
    if (env) {
        jstring message = env->NewStringUTF(progress >= 100 ? "Work completed" : "Work stopped");
        env->CallVoidMethod(javaCallbackRef, onMessageReceivedMethod, message);
        env->DeleteLocalRef(message);

        if (env->ExceptionCheck()) {
            env->ExceptionDescribe();
            env->ExceptionClear();
        }

        detachJNIEnv(attached);
    }
}
  • 模拟工作进度(每200毫秒增加10%)

  • 定期回调 Java 方法更新进度

  • 每20%发送一个里程碑消息

  • 处理完成后发送最终消息

关键点总结

  1. 线程管理 :使用 C++11 的 std::thread 创建本地线程

  2. JNI 环境处理:正确处理线程附加/分离 JVM

  3. Java 回调:通过全局引用和方法 ID 安全回调 Java 方法

  4. 异常处理:检查并清除 JNI 调用可能产生的异常

  5. 内存管理:正确管理全局引用和本地对象

  6. 线程安全 :使用 running 标志控制线程生命周期

相关推荐
还债大湿兄25 分钟前
《C++内存泄漏8大战场:Qt/MFC实战详解 + 面试高频陷阱破解》
c++·qt·mfc
alexhilton4 小时前
SnapshotFlow还是collectAsState?对于Jetpack Compose来说哪个更香?
android·kotlin·android jetpack
珊瑚里的鱼4 小时前
LeetCode 692题解 | 前K个高频单词
开发语言·c++·算法·leetcode·职场和发展·学习方法
AI+程序员在路上4 小时前
QTextCodec的功能及其在Qt5及Qt6中的演变
开发语言·c++·qt
Risehuxyc4 小时前
C++卸载了会影响电脑正常使用吗?解析C++运行库的作用与卸载后果
开发语言·c++
张可5 小时前
一个KMP/CMP项目的组织结构和集成方式
android·前端·kotlin
林林要一直努力5 小时前
AOSP Settings模块问题初窥
android·学习·bug·android studio
景彡先生7 小时前
C++编译期计算:常量表达式(constexpr)全解析
服务器·c++
tan77º8 小时前
【Linux网络编程】应用层自定义协议与序列化
linux·运维·服务器·网络·c++·tcp/ip