為什么子線程不能更新UI的完全解析!!


前言

————————————————————————————————————————————————————

為什么Andorid不能更新子線程更新UI?這里我們從幾個方面來探究;

———————————————————————————————————————————————————

1:Android開發人員設計角度


由於Android是典型的GUI(圖形用戶界面)模型,而現代GUI模型都是單線程設計思想(讀者可自行查詢相關資料)。大意就是為了避免獲取數據時候的競爭導致死鎖。多線程問題已經夠麻煩了,如果GUI也設計程多線程,肯定是會更加麻煩。


2:SDK源碼角度


1:如果子線程更新了UI,我們會得到異常提示,跟進異常拋出類查看,此方法是ViewRootImpl類內部的方法:

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

通過mThread 與 Thread.currentThread()相等來判斷是否異常,,

所以重點來了Thread.currentThread()是當前線程,那么mThread是什么線程呢?


     2:我們跟進查看構造方法

public ViewRootImpl(Context context, Display display) {
    ......

    mThread = Thread.currentThread();
    ......
}
知道當ViewRootimpl第一次構造時候就分發給了我們一個currentThread(). 那么創建ViewRootimpl與更新checkThread的線程就必須是一樣的才不會有異常。那么我們創建ViewRootImpl的線程是什么線程呢?如下分析


ViewRootImpl分析
官方對於此類解釋:

The top of a view hierarchy, implementing the needed protocol between View* and the WindowManager.
 This is for the most part an internal implementation* detail of {@link WindowManagerGlobal}.

 
 

ViewRootImpl他是View樹的頂級視圖,但它卻又不是View,只是實現了View與WindowManager之間的通信協議(2.2版本之前使用ViewRoot此后版本更改為ViewRootImpl)。

這里我們要了解就是ViewRootImpl並不是一個View,只是一個將View與WindowManager關聯起來的類;分析得:ViewRootImpl也是依附於View與windowManager存在的,再通過此ViewRootImpl類注釋分析:就是WindowManagerGlobal具體細節上的一個內部實現“This is for the most part an internal implementation detail of @link WindowManagerGlobal” ——————>跟進

WindowManagerGlobal分析:

通過官方注釋得

這個類的關系:是底層類與窗口之間的上下文聯系類,我們並不需要使用它,直接使用WindowManager;

類注釋叫我們 see windowmanager

WindowManager與windowManagerimpl

然而WindowManager是一個接口 實現了ViewManager,ViewManager也是一個接口。

所以我們要看其實現類 windowMangerImpl,

WindowManagerImpl並沒有實現WindowManager的三大操作,而是全部交給WindowManagerGlobal來處理,WindowManagerGlobal以工廠的形式向外提供自己的實例。就是說WindowManagerImpl將所有的操作全部委托給WindowManagerGlobal來實現。這是一種橋接模式。

通過windowManagerImpl發現其內部就是對WindowManagerGlobal 的封裝,各種addview,removeview之類的調用本意就是WindowManagerGlobal 的調用如

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
 @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
@Override
 public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

 
ViewManager分析 

ViewManager就一個簡單的接口,定義了addview,removeview,updateview,三個方法,

小結

通過一系列的跟源碼與分析大致流程:ViewManager  -->> WindowManager(繼承自ViewManager) -->> WindowManagerImpl(繼承自WindowManager) -->>WindowManagerGlobal(暴露實例給WindowManagerImpl調用處理) -->> ViewRootImpl (WindowManagerGlobal的內部具體細節實現)

接下來簡單分析Activity的啟動

Activity的其實很復雜的,本人也不是完全理解但是我們抓住其入口ActivityThread類即可 (android.app.ActivityThread

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { 
    //...
    ActivityClientRecord r = performResumeActivity(token, clearHide); // 這里會調用到onResume()方法

    if (r != null) {
        final Activity a = r.activity;

        //...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow(); // 獲得window對象
            View decor = r.window.getDecorView(); // 獲得DecorView對象
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager(); // 獲得windowManager對象
            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); // 調用addView方法
            }
            //...
        }
    }
}
根據注釋可以看到:回調onResume方法后  內部得到windowmanager與Window並且addView,加入了一個Decorview,

總結

我們開發是基於Activity,activity的setcontentview一執行 ,系統就給與了我們一個基礎的window視圖PhoneWIndow實例,

PhoneWindow內部就管理了一個頂級View,DecorVew,層級如圖


我們之后的操作都是建立在DecorView上來實現UI的更新。因為創建最頂級DecoView是ActivityThread的window.add進來的,同時因為ViewRoolImpl管理DecoView與window窗體之間的聯系,所以DecoView任何子視圖ViewRoolImpl的currentThread()其實就是ActivityThread(UI線程),當在非UI線程更新時候checkThread()就出現錯誤。(有點拗口但是道理就是這么個道理。)

延伸:::

1:子線程不能更新UI是錯誤的,嚴格意義上講是(只有創建了View的頂級父視圖的線程才能更新UI。其他線程不能干預,干預了ViewRootImpl就給你異常錯誤提示,目的是為了避免GUI多線程的其他錯誤)。

2:子線程如果在onResume之前更新UI(onCreate時候),此時的view只是一個對象還沒有與phonewindow之間建立聯系。所以可以更新。 但是onResume之后也就是View被主線程添加進window后,ViewRoolImpl此類就能管理view與window之間的聯系,此時其他線程不能干預了。



注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com