Activity的生命周期 和 View的繪制流程 的關(guān)聯(lián)

問(wèn)題

我們都知道Android在子線程中更新UI會(huì)報(bào)錯(cuò):

Only the original thread that created a view hierarchy can touch its views。

但是,現(xiàn)在有如下代碼,卻可以正常運(yùn)行。

private ImageView mImageView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mImageView = (ImageView)findViewById(R.id.iv);
    new Thread(new Runnable() {
        @Override
        public void run() {
            mImageView.setImageResource(R.drawable.ic_book);//更新 ui
        }
    }).start();
}

所以就從這個(gè)問(wèn)題入手,回顧下Android中的繪制流程。

聲明周期和繪制流程

我們知道Activity的聲明周期是onCreate->onStart->onResume,View的繪制流程是 onMeasure->onLayout ->onDraw,而且,我們?cè)赼ctivity的聲明周期的這三個(gè)階段是獲取不到View的寬高信息的,也就是說(shuō)View的繪制肯定是發(fā)生在activity聲明周期onResume之后的,但具體是在哪個(gè)階段就要從源碼中找了。

ActivityThread

onCreateonStart、onResume既然是個(gè)方法,肯定是從某個(gè)管理Activity聲明周期執(zhí)行的類中被調(diào)用的,這個(gè)類就是ActivityThread。

handleLaunchActivity

  • #performLaunchActivity

    • 通過(guò)反射創(chuàng)建Activity

    • activity.attach

      • mWindow = new PhoneWindow(this, window, activityConfigCallback);

      • mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        mWindowManager = mWindow.getWindowManager();
        
    • mInstrumentation.callActivityOnCreate

      • activity.performCreate進(jìn)而調(diào)用到onCreate
        • 然后setContentView會(huì)初始化decorView ,以及我們的視圖放到decorView下構(gòu)成View樹(shù)

handleStartActivity

  • activity.performStart("handleStartActivity")
    • mInstrumentation.callActivityOnStart(this)

handleResumeActivity

  • #performResumeActivity

    • activity.performResume
  • decor.setVisibility(View.INVISIBLE);

  • wm = a.getWindowManager() 是一個(gè)WindowManagerImpl

  • wm.addView(decor, l)

    • WindowManagerImpl#addView()

      • WindowManagerGlobal#addView

        • root = new ViewRootImpl(view.getContext(), display);
          view.setLayoutParams(wparams);
          root.setView(view, wparams, panelParentView, userId);
          
          • requestLayout()//view的第一次requestLayout

            • scheduleTraversals

            • mChoreographer.postCallback(
                      Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
              //將在下一次Looper輪循的時(shí)候調(diào)用mTraversalRunnable#run()
              
          • view.assignParent(this);

          • 這里的view是decor,this是ViewRootImpl,也就是將decor的parent設(shè)置成了ViewRootImpl。

          • 到這里為止,上至ViewRootImpl,到decor,再到咱們自己寫的View,這一顆view樹(shù)就算構(gòu)建完成了。

ViewRootImpl下的TraversalRunnable

TraversalRunnableViewRootImpl下的子類,所以以下這些方法都是寫在ViewRootImpl下的。

所以就像Activity的生命周期相關(guān)方法是在ActivityThread中被調(diào)用一樣,View的繪制流程相關(guān)方法被調(diào)用的源頭就是ViewRootImpl。

  • run() -> doTraversal()->performTraversals()
    • performMeasure
    • performLayout
    • performDraw

這三個(gè)方法就對(duì)應(yīng)了View下的onMeasure,onLayout ,onDraw,所以會(huì)從ViewRootImpl開(kāi)始,至上而下的開(kāi)始繪制流程。

解答

再回到最開(kāi)始的那個(gè)問(wèn)題,再onCreate中開(kāi)辟線程,竟然可以成功更新UI。

會(huì)一層一層往上執(zhí)行requestLayout。

if (mParent != null && !mParent.isLayoutRequested()) {
    mParent.requestLayout();
}

假設(shè)這時(shí)更新到decorView了,再去往上找ViewRootImpl,因?yàn)?code>ViewRootImpl是在onResume之后root.setView()的時(shí)候才被設(shè)置為decor的parent,所以這個(gè)時(shí)候decor是沒(méi)有parent的,所以自然會(huì)略過(guò)這行代碼,直接開(kāi)始更新了。

如果為這個(gè)線程加上延時(shí),使其在view.assignParent(this)執(zhí)行完之后,再調(diào)用requestLayout,就會(huì)執(zhí)行到ViewRootImpl#requestLayout方法中來(lái):

@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.");
    }
}

這個(gè)時(shí)候,如果線程不對(duì)的話,就會(huì)報(bào)“子線程不能更新UI”的異常了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容