Android:子线程到底能不能更新UI?

问题由来

我们知道,Andoird由于修改UI是线程不安全的,只能在主线程中修改。如果多个线程修改UI肯定会花屏,于是谷歌做了限制,只能在主线程中修改UI。但是有次我在子线程中修改了UI没弹异常。

先来看两段代码

//正常运行
btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { resultTv.setText("更新TextView"); } }).start(); } });

闪退,控制台异常为:Only the original thread that created a view hierarchy can touch its views.

//弹出异常
btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { resultTv.setText("更新TextView\n");//这里不一样,多了个换行符 } }).start(); } });

源码解读

之前的博客有解读ViewRootImpl是负责View的绘制,在requestLayout这个方法中会检查是否是当前线程。所以只要子线程修改UI但不改变UI布局时,不会弹出非主线程的异常。

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

那么问题来了,假设我在onCreate的时候修改UI,layout也变了,为什么没报错呢?

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    new Thread(new Runnable() {
        @Override
        public void run() {
            resultTv.setText("onCreate\n");
        }
    }).start();
}

在ActivityThread中发现,ViewRootImpl是在onResume的时候被初始化的,上面那段代码sleep久一点等ViewRootImpl初始化完毕就会报错

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
         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();//ViewRootImpl在这里被初始化
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            }       
    }
}

总结

  1. 子线程可以在部分情况下修改UI,如不改变布局,在onResume之前
  2. 不推荐在子线程中修改UI
posted @ 2020-07-17 11:07  夜空中最亮的盖子  阅读(462)  评论(0编辑  收藏  举报