Android JNI C++中调用runOnUiThread解决Toast后台线程崩溃问题
解决JNI后台线程显示Toast崩溃的纯C++方案
问题根源
Android规定所有UI操作(包括显示Toast)必须在**主线程(UI线程)**执行,后台线程直接调用Toast相关API会触发崩溃。我们需要把Toast的显示逻辑切换到主线程执行,纯C++中可通过JNI调用Android的线程调度API实现。
实现步骤
- 利用
ActivityThread.getMainLooper()获取主线程的Looper(主线程消息队列核心)。 - 创建绑定主线程Looper的
Handler对象,负责将任务提交到主线程消息队列。 - 通过动态代理创建
Runnable实例,将Toast显示逻辑封装在run方法中。 - 调用
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(); }
关键细节说明
- 全局变量
g_globalContext:保存传入的thiz(Activity/Context实例)的全局引用,供主线程的Toast逻辑获取ApplicationContext使用,使用后必须释放避免内存泄漏。 - 动态代理
Runnable:通过Java的Proxy.newProxyInstance创建Runnable的动态代理对象,将run方法的调用转发到C++的invokeRunnable函数,从而在主线程执行Toast逻辑。 - Handler与Looper:绑定主线程Looper的Handler可以确保
post的Runnable在主线程消息队列中执行,符合Android的UI线程要求。 - 引用管理:所有JNI创建的局部/全局引用必须及时清理,避免内存泄漏和JNI引用表溢出。
内容的提问来源于stack exchange,提问作者Maaz Arfi
相关产品推荐
相关产品推荐

