1. 程式人生 > 實用技巧 >Android:子執行緒到底能不能更新UI?

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