【发布时间】:2020-04-20 12:47:22
【问题描述】:
我正在尝试为我的活动动态设置屏幕亮度。起初,在我进入ALooper 循环之前,我很容易通过JNIEnv 调用CallVoidMethod 和公司来做到这一点。但经过 65 次循环迭代后,我开始不断收到如下异常:
android.view.ViewRootImpl$CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能接触其视图。
让我感到奇怪的是,我从同一个本机线程执行 JNIEnv 调用,但调用只是在多次尝试后才开始失败。
这里发生了什么?如何确保从正确的 Java 线程调用 Java 方法?
这是简化的示例代码:
#include <cmath>
#include <stdexcept>
#include <jni.h>
#include <android/log.h>
#include <android_native_app_glue.h>
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG,"color-picker", __VA_ARGS__))
class JNIEnvGetter
{
JavaVM* javaVM = nullptr;
JNIEnv* jniEnv = nullptr;
bool threadAttached = false;
public:
JNIEnvGetter(ANativeActivity* activity)
: javaVM(activity->vm)
{
// Get JNIEnv from javaVM using GetEnv to test whether
// thread is attached or not to the VM. If not, attach it
// (and note that it will need to be detached at the end
// of the function).
switch (javaVM->GetEnv((void**)&jniEnv, JNI_VERSION_1_6))
{
case JNI_OK:
LOGD("No need to attach thread");
break;
case JNI_EDETACHED:
{
const auto result = javaVM->AttachCurrentThread(&jniEnv, nullptr);
if(result == JNI_ERR)
throw std::runtime_error("Could not attach current thread");
LOGD("Thread attached");
threadAttached = true;
break;
}
case JNI_EVERSION:
throw std::runtime_error("Invalid java version");
}
}
JNIEnv* env() { return jniEnv; }
~JNIEnvGetter()
{
if(threadAttached)
javaVM->DetachCurrentThread();
}
};
void setBrightness(JNIEnv* env, ANativeActivity* activity, const float screenBrightness)
{
LOGD("setBrightness()");
const jclass NativeActivity = env->FindClass("android/app/NativeActivity");
const jclass Window = env->FindClass("android/view/Window");
const jmethodID getWindow = env->GetMethodID(NativeActivity, "getWindow",
"()Landroid/view/Window;");
const jmethodID getAttributes = env->GetMethodID(Window, "getAttributes",
"()Landroid/view/WindowManager$LayoutParams;");
const jmethodID setAttributes = env->GetMethodID(Window, "setAttributes",
"(Landroid/view/WindowManager$LayoutParams;)V");
const jobject window = env->CallObjectMethod(activity->clazz, getWindow);
const jobject attrs = env->CallObjectMethod(window, getAttributes);
const jclass LayoutParams = env->GetObjectClass(attrs);
const jfieldID screenBrightnessID = env->GetFieldID(LayoutParams, "screenBrightness", "F");
env->SetFloatField(attrs, screenBrightnessID, screenBrightness);
env->CallVoidMethod(window, setAttributes, attrs);
if(env->ExceptionCheck())
{
LOGD("Exception detected");
env->ExceptionDescribe();
env->ExceptionClear();
}
else
{
static int count=0;
LOGD("Brightness set successfully %d times", ++count);
}
env->DeleteLocalRef(attrs);
env->DeleteLocalRef(window);
}
void android_main(struct android_app* state)
{
JNIEnvGetter jeg(state->activity);
const auto env=jeg.env();
setBrightness(env, state->activity, 1); // works fine
for(float x=0;;x+=0.001)
{
int events;
struct android_poll_source* source;
while (ALooper_pollAll(0, nullptr, &events, (void**)&source) >= 0)
{
if (source)
source->process(state, source);
if (state->destroyRequested != 0)
return;
}
setBrightness(env, state->activity, std::cos(x)); // gets exception
}
}
来自adb logcat的相关输出:
04-20 15:34:45.778 12468 12487 D color-picker: Thread attached
04-20 15:34:45.778 12468 12487 D color-picker: setBrightness()
04-20 15:34:45.779 12468 12487 D color-picker: Brightness set successfully 1 times
04-20 15:34:45.779 12468 12487 D color-picker: setBrightness()
04-20 15:34:45.779 12468 12487 D color-picker: Brightness set successfully 2 times
<...>
04-20 15:34:45.834 12468 12487 D color-picker: setBrightness()
04-20 15:34:45.835 12468 12487 D color-picker: Brightness set successfully 64 times
04-20 15:34:45.835 12468 12487 D color-picker: setBrightness()
04-20 15:34:45.837 12468 12487 D color-picker: Brightness set successfully 65 times
04-20 15:34:45.837 12468 12487 D color-picker: setBrightness()
04-20 15:34:45.837 3176 5876 V WindowManager: Relayout Window{37a74d5 u0 zozzozzz.color_picker/android.app.NativeActivity}: viewVisibility=0 req=720x1232 WM.LayoutParams{(0,0)(fillxfill) sim=#110 ty=1 fl=#81810100 pfl=0x20000 wanim=0x10302fc sbrt=0.9980162 vsysui=0x600 needsMenuKey=2 colorMode=0 naviIconColor=0}
04-20 15:34:45.838 12468 12487 D color-picker: Exception detected
04-20 15:34:45.838 12468 12487 W System.err: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
04-20 15:34:45.838 12468 12487 W System.err: at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8483)
04-20 15:34:45.839 12468 12487 W System.err: at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1428)
04-20 15:34:45.839 12468 12487 W System.err: at android.view.View.requestLayout(View.java:23221)
04-20 15:34:45.839 12468 12487 W System.err: at android.view.View.setLayoutParams(View.java:16318)
04-20 15:34:45.840 12468 12487 W System.err: at android.view.WindowManagerGlobal.updateViewLayout(WindowManagerGlobal.java:402)
04-20 15:34:45.840 12468 12487 W System.err: at android.view.WindowManagerImpl.updateViewLayout(WindowManagerImpl.java:106)
04-20 15:34:45.840 12468 12487 W System.err: at android.app.Activity.onWindowAttributesChanged(Activity.java:3201)
04-20 15:34:45.840 12468 12487 W System.err: at android.view.Window.dispatchWindowAttributesChanged(Window.java:1138)
04-20 15:34:45.841 12468 12487 W System.err: at com.android.internal.policy.PhoneWindow.dispatchWindowAttributesChanged(PhoneWindow.java:3207)
04-20 15:34:45.841 12468 12487 W System.err: at android.view.Window.setAttributes(Window.java:1191)
04-20 15:34:45.841 2680 2680 I SurfaceFlinger: id=12125 createSurf (720x1280),1 flag=404, zozzozzz.color_picker/android.app.NativeActivity#0
04-20 15:34:45.841 12468 12487 W System.err: at com.android.internal.policy.PhoneWindow.setAttributes(PhoneWindow.java:4197)
04-20 15:34:45.841 12468 12487 D color-picker: setBrightness()
04-20 15:34:45.842 12468 12487 D color-picker: Exception detected
04-20 15:34:45.842 12468 12487 W System.err: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
【问题讨论】:
-
在我看来,您的所有呼叫都发生在同一个线程 (12487) 上。 AFAIK
ViewRootImpl忽略对requestLayout的调用,如果它已经在处理布局请求,那么这可能是您的前 65 个setBrightness调用发生的情况。android_main不在 UI 线程上运行,因此您必须找到一些解决方法。参见例如stackoverflow.com/questions/44808206/…
标签: android c++ multithreading java-native-interface jnienv