为什么非UI线程可以修改UI?

gar*_*daf 1 debugging multithreading android

是的,它不应该,但我在我的mainActivityin中制作的线程onCreate可以修改UI,如下所示:

Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                someTextview.setText("Hello");
            }
        });
        thread.start();
Run Code Online (Sandbox Code Playgroud)

我正在使用android studio 2.2.2.

Gar*_*wzh 6

简短的回答:只要你在mainthread输入onResume()之前就可以做到这一点

详情:

通常,在从非UI线程修改UI时会出现类似这样的错误

android.view.ViewRootImpl$CalledFromWrongThreadException:
    Only the original thread that created a view hierarchy can touch its views.
Run Code Online (Sandbox Code Playgroud)

这个异常在ViewRootImpl课堂上抛出来了

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}
Run Code Online (Sandbox Code Playgroud)

mThread是主线程.ViewRootImpl创建实例后,UI修改操作将需要检查线程.

那么什么时候ViewRootImpl创建了实例?

android.view.WindowManagerImpl.java

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
Run Code Online (Sandbox Code Playgroud)

android.view.WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    ...
    root = new ViewRootImpl(view.getContext(), display);
    ...
}
Run Code Online (Sandbox Code Playgroud)

那么这个addView方法何时被调用?

android.app.ActivityThread.java

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient && !a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            }

}
Run Code Online (Sandbox Code Playgroud)

.

 public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                ...
                case RESUME_ACTIVITY:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
                    SomeArgs args = (SomeArgs) msg.obj;
                    handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
                            args.argi3, "RESUME_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                ...
            }
            Object obj = msg.obj;
            if (obj instanceof SomeArgs) {
                ((SomeArgs) obj).recycle();
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }
Run Code Online (Sandbox Code Playgroud)

在这里你可以看到ViewRootImpl实例是围绕RESUME_ACTIVITY事件创建的,所以在主线程处理resume事件之前,你可以从其他线程修改UI.你只需要快点.

但不建议这样做.