Android 9 Activity的載入和顯示

賈亦真亦賈發表於2019-04-15

起因

昨天被人問起Activity的啟動過程,我一陣心虛,說實話,N年前看過一回別人寫的文章,但是自己從來沒有跟著原始碼去研究過Activity的啟動過程,所以別人問到後,我只能把從PhoneWindow到DecorView到SetContentView到ViewRootImpl呼叫performTraversals()方法,再呼叫其內部的performMeasure()、performLayout()、performDraw(),從而將佈局檔案繪製並載入到父類為FrameLayout的DecorView上,這個過程雖然沒什麼打錯,但是這個其實只是View的繪製流程分支,與Activity的介面載入有部分重合,真正的Activity啟動後,介面載入的流程是要比這個複雜的,懷著慚愧的心情,今天趕緊開啟AndroidStudio,從原始碼開始,一點一點的扣整個啟動過程。

不過這裡先說一下,我無法從Activity的StartActivity()方法開始講,因為這部分內容特別多,而且相當複雜,我還沒有完全吃透,所以我的原始碼分析過程是從ActivityThread的StartActivityNow開始講解並分析的,如果這個過程很熟悉,只是為了看前半部分的朋友,可以轉戰到這篇文章下:

(Android 9.0)Activity啟動流程原始碼分析

過程

現在開始分析,首先開啟android.app.ActivityThread類,找到startActivityNow()

package android.app.ActivityThread

public final Activity startActivityNow(Activity parent, String id,
        Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
        Activity.NonConfigurationInstances lastNonConfigurationInstances) {
    ...
    return performLaunchActivity(r, null /* customIntent */);
}
複製程式碼

這個方法主要的作用就是初始化一些引數後,並呼叫同類的performLaunchActivity()

package android.app.ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
 ...
  Activity activity = null;
  try{
  ...
  activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
  ...
  }
  activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
           
 ...
 if (r.isPersistable()) {
   mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
 } else {
   mInstrumentation.callActivityOnCreate(activity, r.state);
 }
 ...
 }
複製程式碼

首先第7行,先建立一個activity的例項;

然後第10行呼叫這個activiy例項的attach方法;

然後第16行開始,通過判斷是否啟用了PersistableBundle,來判斷Instrumentation物件mInstrumentation呼叫哪個Activity的onCreate()方法,不瞭解PersistableBundle的可以看這篇文章:

android-1.0-四大元件-PersistableBundle

這裡主要看一下attach方法:

package android.app.Activity;
final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
    ...
     mWindow = new PhoneWindow(this, window, activityConfigCallback);
     mWindow.setWindowControllerCallback(this);
     mWindow.setCallback(this);
     mWindow.setOnWindowDismissedCallback(this);
    ...
      mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
    ...
}
複製程式碼

要了解這裡,首先要了解一下Activity的組成結構:

Android 9 Activity的載入和顯示

一個Activity的內部包含一個PhoneWindow,PhoneWindow有包含一個DecorView,DecorView其實就是一個FrameLayout的子類,它的內部又包含了TitleActionBar和ContentView,而attach這個方法,其中一個重要目的就是初始化PhoneWindow物件。

現在回到原始碼部分,上面這個方法,我羅列出的程式碼主要做了三件事:

1.將Activity.mWindow物件初始化

2.給mWindow設定WindowManager

3.給mWindowManager賦值。

好了,attach方法看完後,我們回到performLaunchActivity方法裡,現在該mInstrumentation呼叫callActivityOnCreate方法了:

package com.app.Instrumentation;
public void callActivityOnCreate(Activity activity, Bundle icicle) {
        prePerformCreate(activity);
        activity.performCreate(icicle);
        postPerformCreate(activity);
    }
複製程式碼

這裡我們主要看activity.performCreate(icicle);這行程式碼,進入performCreate方法:

package com.app.Activity; 
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
    if (persistentState != null) {
            onCreate(icicle, persistentState);
        } else {
            onCreate(icicle);
        }
}
複製程式碼

最後發現,執行的就是onCreate方法,而我們寫Activity方法的時候,一般都會寫一個setContentView(layoutId)來設定介面佈局,這時我們再看看setContentView方法:

package com.app.Activity; 
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
複製程式碼

這個方法其實做兩件事,第一件事是呼叫getWindow()的setContentView方法,而getWindow()返回的是一個android.app.Window物件,這個物件就是剛剛在attach()中賦值的mWindow成員變數。

後面的initWindowDecorActionBar方法看名字就是初始化DecorView的ActionBar,這也印證了前面我們講的Activity的框架結構,這個分支就走到這裡,我們還是繼續看getWindow().setContentView,由於Window是一個抽象類,而Window的setContentView方法其實是一個抽象方法,並沒有具體的實現,所以我們要看的是window的子類:PhoneWindowsetContentView方法:

package com.android.internal.policy.PhoneWindow;

@Override
    public void setContentView(int layoutResID) {
 // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
             mLayoutInflater.inflate(layoutResID, mContentParent);
            //如果呼叫的是同類構造方法:setContentView(View view, ViewGroup.LayoutParams params)的話
            //則這裡的程式碼是:mContentParent.addView(view, params);
        }
         mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
     ...
    }
複製程式碼

知識點:FEATURE_CONTENT_TRANSITIONS是標記當前內容載入有沒有使用過渡動畫,也就是轉場動畫。

首先我們注意一個變數,mContentParent是一個ViewGroup,而學過自定義View的同學,肯定知道ViewGroup就是FrameLayout,LinearLayout,RelativeLayout等佈局檔案的父類,所以這裡有一個執行判斷就是,如果mContentParent不為空,並且沒有過度動畫就執行mContentParent.removeAllViews();來清理介面,之後通過判斷,沒有過渡動畫後,給mContentParent這個ViewGroup中新增view和佈局檔案。

現在我們來看看installDecor方法:

package com.android.internal.policy.PhoneWindow; 
private void installDecor() {
    ...
          if (mDecor == null) {
            mDecor = generateDecor(-1);
            ...
        } else {
            mDecor.setWindow(this);
        }
       if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
           ...
       }
}
複製程式碼

上面的方法主要做的其實就是初始化DecorView,並將DecorView和PhoneWindow進行關聯,並初始化mContentParent.

看看generateDecor方法:

package com.android.internal.policy.PhoneWindow;
protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

複製程式碼

這裡注意一下最後new DecorView的時候傳入了this,這就說明DecorView與PhoneWindow確實關聯了,並返回了一個DecorView的例項。

我們再看看generateLayout方法:

package com.android.internal.policy.PhoneWindow;
protected ViewGroup generateLayout(DecorView decor) {
    ...
    layoutResource = R.layout.screen_title;
    ...
        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
    ...
        mDecor.finishChanging();
        return contentParent;
}
複製程式碼

這個方法很長,注意看onResourcesLoaded方法:

 if (mBackdropFrameRenderer != null) {
            loadBackgroundDrawablesIfNeeded();
            mBackdropFrameRenderer.onResourcesLoaded(
                    this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                    getCurrentColor(mNavigationColorViewState));
        }

        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
複製程式碼

細節太多就不細說了,這個方法主要是建立mDecorCaptionView,然後將傳遞進來的佈局檔案inflate到這個DecorView中去。

再回到generateLayout方法,ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); 而ID_ANDROID_CONTENT的常量值就是:com.android.internal.R.id.content;

然後根據:layoutResource = R.layout.screen_title;我們開啟此佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
複製程式碼

發現最下面的FrameLayout的id屬性就是android:id="@android:id/content",也就是說這個FrameLayout其實就是我們的變數contentParent

最後放一個前輩對於DecorView的總結:

綜合以上的探究,加上自己的一些思考和猜測。對PhoneWindow做一下小小的總結:

1.一個Activity對應著一個PhoneWindow物件,是一對一的關係,如果從Activity A啟動到Activity B,那麼Activity B會建立一個自己的PhoneWindow物件。

2.PhoneWindow管理著整個螢幕的內容,不包括螢幕最頂部的系統狀態條。所以,PhoneWindow或者Window是與應用的一個頁面所相關聯。

3.PhoneWindow同時管理著ActionBar和下面的內容主題,setContentView()方法是用來設定內容主體的,而setTitle()等其他方法就是操作ActionBar的,Window中定義的requestFeature()等方法,有很多與ActionBar屬性相關的設定。另外這些方法都是公有方法,顯然是為了給客戶端程式設計師呼叫的,也進一步佐證了這些操作的意義與作用。

4.PhoneWindow自己並不是一個檢視(View),它的成員變數mDecor才是整個介面的檢視,mDecor是在generateLayout()的時候被填充出來的,而actionBar和contentParent兩個檢視都是通過findViewById()直接從mDecor中獲取出來的。

講到這裡,算是把方法installDecor講完了,現在繼續回到程式碼塊:com.android.internal.policy.PhoneWindow的setContentView中去繼續從installDecor方法往下看,mContentParent.removeAllViews();簡單的說過了,這裡就不復述了,之後PhoneWindow類的setContentView方法最後通過呼叫mLayoutInflater.inflate(layoutResID, mContentParent);或者mContentParent.addView(view, params);將我們的xml或者java View插入到了mContentParent(id為content的FrameLayout物件)ViewGroup中,最後setContentView還會呼叫一個Callback介面的成員函式onContentChanged來通知對應的Activity元件檢視內容發生了變化。

現在重新放一下setContentView這個程式碼段:

package com.android.internal.policy.PhoneWindow;

@Override
    public void setContentView(int layoutResID) {
 // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
             mLayoutInflater.inflate(layoutResID, mContentParent);
            //如果呼叫的是同類構造方法:setContentView(View view, ViewGroup.LayoutParams params)的話
            //則這裡的程式碼是:mContentParent.addView(view, params);
        }
         mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
     ...
    }
複製程式碼

注意最後幾行程式碼,是由Callback的例項物件呼叫的onContentChanged方法,進入Callback的原始碼我們得知,Callback就是定義在抽象類Window中的一個介面,而**getCallback()**也僅僅是獲取Callback介面的例項,但是這個Callback具體在哪裡實現的,我們還得繼續查,這裡分享一下我的查詢方式,我是通過在對應介面上按Ctrl+T的方式羅列出該介面的實現類,如下:

Android 9 Activity的載入和顯示

這時我們就注意到了,一個親切的傢伙就出現在我們面前了, Activity呀!對呀,如果PhoneWindow沒有實現這個介面,那麼作為組合類的Activity應該就會實現呀,而且我們回憶一下Activity的attach方法,呃,不用回憶了,直接貼再貼一次原始碼:

 package android.app.Activity;
final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
    attachBaseContext(context);
    mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
    ....
    
}
複製程式碼

注意,mWindow.setCallback(this);這行程式碼,這個this不就代表的是Activity本身嗎?那麼cb.onContentChanged();方法不就是Activity的onContentChanged()方法嗎?我們看一下:

package android.app.Activity;
public void onContentChanged() {
}
複製程式碼

Activity的onContentChanged()是一個空方法,這就是說,etContentView()或者addContentView()方法執行完畢時就會呼叫該方法,那麼我們知道這個邏輯後,以後有什麼佈局二次變化的需求後,就可以將元件初始化的程式碼,如:findViewById()或者一些引數的初始化等業務程式碼放在我們App對應的Activity重寫的onContentChanged()方法中去,讓系統幫忙回撥。

現在來總結一下setContentView做的事:

建立一個DecorView的物件mDecor,該mDecor物件將作為整個應用視窗的根檢視。

依據Feature等style theme建立不同的視窗修飾佈局檔案,並且通過findViewById獲取Activity佈局檔案該存放的地方(視窗修飾佈局檔案中id為content的FrameLayout)。

將Activity的佈局檔案新增至id為content的FrameLayout內。

題目叫做Activity的載入和顯示,前面講的都是Activity的載入,現在講講Activity的顯示吧,至於為什麼會是這個呼叫順序或執行過程,那個需要單開一篇文章細說,這裡只分析具體的載入和顯示的原始碼過程,現在我們來看Activity中的handleResumeActivity方法:

package android.app.ActivityThread;
 @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        if (r == null) {
            // We didn't actually resume the activity, so skipping any follow-up actions.
            return;
        }
    ...
    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) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }
        ...

複製程式碼

performResumeActivity()這個方法追到底,其主要就是Instrumentation呼叫了Acitivy的onResume()方法,我們瞭解就好,然後主要要看的是wm.addView(decor, l);,這裡就是要動真格的了,我們繼續往下追:

package android.view;
public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
複製程式碼

addView是介面ViewManager的一個方法,但是我們很詫異的是wm沒記錯的話應該是windowManager的例項啊,怎麼成了ViewManager了?我們看一下handleResumeActivity的這行程式碼:

ViewManager wm = a.getWindowManager();
複製程式碼

通過追蹤這個方法的呼叫,發現其實這裡的設計是這樣的:

package android.view;
@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager {
    ...
}
複製程式碼

WindowManager介面繼承了ViewManager介面,從而在載入View時就使用了ViewManager中的addView方法,現在可以知道的是addView只是一個抽象方法,我們需要找到WindowManager的實現類,檢視addView的原始碼而WindowManagerImpl就是WindowManager的實現類,我們檢視這個類的addView方法:

package android.view;
public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
    ...
}
複製程式碼

其實本質上呼叫的是WindowManagerGlobal的addView方法,我們進去看一下:

package android.view;
public final class WindowManagerGlobal {
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    ...
    root = new ViewRootImpl(view.getContext(), display);
    ...
    root.setView(view, wparams, panelParentView);
    ...
}
}
複製程式碼

這段程式碼主要的一個作用是呼叫了ViewRootImpl的setView方法,我們繼續追蹤:

package android.view.ViewRootImpl;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
 synchronized (this) {
            if (mView == null) {
                mView = view; 
                ...
               requestLayout();
                ...
              view.assignParent(this);    
            }
}
複製程式碼

首先,此方法會把之前傳進來的引數view賦值給mView,mView其實就是handleResumeActivity中的wm.addView時傳進來的DecorView,而DecorView又是一個FrameLayout,這裡其實就是將setContentView所做的一切初始化操作的DecorView設定成這個Activity的最基礎的檢視框架,具體見程式碼:

view.assignParent(this);

然後呼叫了**requestLayout()**方法來顯示介面內容:

package android.view.ViewRootImpl;
@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
複製程式碼

先判斷當前執行緒是不是主執行緒,然後就呼叫了**scheduleTraversals()**方法,繼續跟進:

package android.view.ViewRootImpl;
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
複製程式碼

mChoreographer.postCallback( ​ Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);通過跟進原始碼得知其本質就是通過handler傳送訊息,那麼我們關注的重點就應該是mTraversalRunnable這個Runnable介面:

package android.view.ViewRootImpl;
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
複製程式碼

doTraversal方法看看:

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
複製程式碼

繼續跟進performTraversals方法:

private void performTraversals() {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
 // Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
}
複製程式碼

這個方法的邏輯非常長,反正我是沒看完,但是我們可以注意到,這三個方法和兩個屬性的初始化,其主要作用其實就是對基礎的根節點View進行View的初始化操作,也就是我們常說的onMeasure(測量)、onLayout(佈局)和onDraw(繪製),而childWidthMeasureSpec和childHeightMeasureSpec主要的作用就是為測量提供測量規格,這裡具體的內容可以看我的另一篇文章:Android自定義View的測量過程詳解

總結

千言萬語的總結不如最後繪成一張圖來的清晰明瞭:

Android 9 Activity的載入和顯示

相關文章