You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android JNI C++中调用runOnUiThread解决Toast后台线程崩溃问题

解决JNI后台线程显示Toast崩溃的纯C++方案

问题根源

Android规定所有UI操作(包括显示Toast)必须在**主线程(UI线程)**执行,后台线程直接调用Toast相关API会触发崩溃。我们需要把Toast的显示逻辑切换到主线程执行,纯C++中可通过JNI调用Android的线程调度API实现。

实现步骤

  1. 利用ActivityThread.getMainLooper()获取主线程的Looper(主线程消息队列核心)。
  2. 创建绑定主线程Looper的Handler对象,负责将任务提交到主线程消息队列。
  3. 通过动态代理创建Runnable实例,将Toast显示逻辑封装在run方法中。
  4. 调用Handler.post()将Runnable提交到主线程执行。

修改后的完整代码

// 静态全局变量:保存全局Context引用,供主线程Toast逻辑使用
jobject g_globalContext = nullptr;

// InvocationHandler的invoke方法实现:主线程中执行Toast逻辑
jobject invokeRunnable(JNIEnv* env, jobject thiz, jobject proxy, jobject method, jobjectArray args) {
    jclass methodCls = env->FindClass("java/lang/reflect/Method");
    jmethodID getNameMethod = env->GetMethodID(methodCls, "getName", "()Ljava/lang/String;");
    jstring methodNameStr = static_cast<jstring>(env->CallObjectMethod(method, getNameMethod));
    const char* methodName = env->GetStringUTFChars(methodNameStr, nullptr);

    // 只处理Runnable的run方法
    if (strcmp(methodName, "run") == 0 && g_globalContext != nullptr) {
        // 获取ApplicationContext
        jclass contextCls = env->FindClass("android/content/Context");
        jmethodID getAppContextMethod = env->GetMethodID(contextCls, "getApplicationContext", "()Landroid/content/Context;");
        jobject appContext = env->CallObjectMethod(g_globalContext, getAppContextMethod);

        // 创建并显示Toast
        jclass toastCls = env->FindClass("android/widget/Toast");
        jmethodID makeTextMethod = env->GetStaticMethodID(toastCls, "makeText", "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;");
        jobject toast = env->CallStaticObjectMethod(toastCls, makeTextMethod, appContext, env->NewStringUTF("请求成功!"), 1); // 1 = Toast.LENGTH_SHORT
        jmethodID showMethod = env->GetMethodID(toastCls, "show", "()V");
        env->CallVoidMethod(toast, showMethod);

        // 清理局部引用
        env->DeleteLocalRef(contextCls);
        env->DeleteLocalRef(appContext);
        env->DeleteLocalRef(toastCls);
        env->DeleteLocalRef(toast);
    }

    // 释放资源
    env->ReleaseStringUTFChars(methodNameStr, methodName);
    env->DeleteLocalRef(methodCls);
    env->DeleteLocalRef(methodNameStr);
    return nullptr;
}

void contactMSFServers(JNIEnv* env, jobject thiz) {
    JavaVM* jvm;
    env->GetJavaVM(&jvm);
    // 创建全局Context引用,供主线程使用
    g_globalContext = env->NewGlobalRef(thiz);

    std::thread([jvm]() {
        JNIEnv* threadEnv;
        JavaVMAttachArgs args = {JNI_VERSION_1_6, nullptr, nullptr};
        if (jvm->AttachCurrentThread(&threadEnv, &args) != JNI_OK) {
            return;
        }

        // --- 原有的API请求逻辑(假设此处success已正确获取)---
        int success = threadEnv->CallIntMethod(obj_JSONObject, method_getInt, keySuccess);
        jstring keyEcho = threadEnv->NewStringUTF("echo");
        auto echoObj = static_cast<jstring>(threadEnv->CallObjectMethod(obj_JSONObject, method_getString, keyEcho));
        // --- 原API请求逻辑结束 ---

        if (success == 200) {
            // 1. 获取主线程Looper
            jclass activityThreadCls = threadEnv->FindClass("android/app/ActivityThread");
            jmethodID getMainLooperMethod = threadEnv->GetStaticMethodID(activityThreadCls, "getMainLooper", "()Landroid/os/Looper;");
            jobject mainLooper = threadEnv->CallStaticObjectMethod(activityThreadCls, getMainLooperMethod);

            // 2. 创建主线程Handler
            jclass handlerCls = threadEnv->FindClass("android/os/Handler");
            jmethodID handlerCtor = threadEnv->GetMethodID(handlerCls, "<init>", "(Landroid/os/Looper;)V");
            jobject mainHandler = threadEnv->NewObject(handlerCls, handlerCtor, mainLooper);

            // 3. 创建动态代理实现Runnable接口
            // 3.1 获取系统类加载器
            jclass classLoaderCls = threadEnv->FindClass("java/lang/ClassLoader");
            jmethodID getSystemClassLoaderMethod = threadEnv->GetStaticMethodID(classLoaderCls, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
            jobject systemClassLoader = threadEnv->CallStaticObjectMethod(classLoaderCls, getSystemClassLoaderMethod);

            // 3.2 准备Runnable接口数组
            jclass runnableCls = threadEnv->FindClass("java/lang/Runnable");
            jobjectArray interfaces = threadEnv->NewObjectArray(1, threadEnv->FindClass("java/lang/Class"), threadEnv->GetObjectClass(runnableCls));
            threadEnv->SetObjectArrayElement(interfaces, 0, runnableCls);

            // 3.3 创建InvocationHandler实例并绑定C++的invoke方法
            jclass invocationHandlerCls = threadEnv->FindClass("java/lang/reflect/InvocationHandler");
            jobject invocationHandler = threadEnv->NewGlobalRef(threadEnv->AllocObject(invocationHandlerCls));
            
            JNINativeMethod invokeMethod = {
                "invoke", 
                "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", 
                reinterpret_cast<void*>(invokeRunnable)
            };
            threadEnv->RegisterNatives(invocationHandlerCls, &invokeMethod, 1);

            // 3.4 创建动态代理的Runnable对象
            jclass proxyCls = threadEnv->FindClass("java/lang/reflect/Proxy");
            jmethodID newProxyInstanceMethod = threadEnv->GetStaticMethodID(proxyCls, "newProxyInstance", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;");
            jobject runnableObj = threadEnv->CallStaticObjectMethod(proxyCls, newProxyInstanceMethod, systemClassLoader, interfaces, invocationHandler);

            // 4. 提交Runnable到主线程执行
            jmethodID postMethod = threadEnv->GetMethodID(handlerCls, "post", "(Ljava/lang/Runnable;)Z");
            threadEnv->CallBooleanMethod(mainHandler, postMethod, runnableObj);

            // 清理局部引用
            threadEnv->DeleteLocalRef(activityThreadCls);
            threadEnv->DeleteLocalRef(mainLooper);
            threadEnv->DeleteLocalRef(handlerCls);
            threadEnv->DeleteLocalRef(mainHandler);
            threadEnv->DeleteLocalRef(classLoaderCls);
            threadEnv->DeleteLocalRef(systemClassLoader);
            threadEnv->DeleteLocalRef(runnableCls);
            threadEnv->DeleteLocalRef(interfaces);
            threadEnv->DeleteLocalRef(invocationHandlerCls);
            threadEnv->DeleteLocalRef(proxyCls);
            threadEnv->DeleteLocalRef(runnableObj);
            threadEnv->DeleteGlobalRef(invocationHandler);
        }

        // 清理原有逻辑的局部引用
        threadEnv->DeleteLocalRef(obj_URL);
        threadEnv->DeleteLocalRef(obj_outputStream);
        threadEnv->DeleteLocalRef(obj_inputStream);
        threadEnv->DeleteLocalRef(byteArray);
        threadEnv->DeleteLocalRef(keySuccess);
        threadEnv->DeleteLocalRef(keyEcho);
        
        // 解绑线程并释放全局引用
        jvm->DetachCurrentThread();
        if (g_globalContext != nullptr) {
            threadEnv->DeleteGlobalRef(g_globalContext);
            g_globalContext = nullptr;
        }
    }).detach();
}

关键细节说明

  1. 全局变量g_globalContext:保存传入的thiz(Activity/Context实例)的全局引用,供主线程的Toast逻辑获取ApplicationContext使用,使用后必须释放避免内存泄漏。
  2. 动态代理Runnable:通过Java的Proxy.newProxyInstance创建Runnable的动态代理对象,将run方法的调用转发到C++的invokeRunnable函数,从而在主线程执行Toast逻辑。
  3. Handler与Looper:绑定主线程Looper的Handler可以确保post的Runnable在主线程消息队列中执行,符合Android的UI线程要求。
  4. 引用管理:所有JNI创建的局部/全局引用必须及时清理,避免内存泄漏和JNI引用表溢出。

内容的提问来源于stack exchange,提问作者Maaz Arfi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 08:40:54