Android從Activity啟動到View顯示中間發(fā)生了什么?

前言

Activity的啟動過程中馋艺,通常我們在onCreate生命周期中調(diào)用setContentView方法設(shè)置布局文件(即xml文件)躲叼,似乎這樣就完成了布局文件對應(yīng)的View的繪制及顯示亲轨,那么背后具體的原理是什么呢醇坝?

?? Activity onCreate生命周期
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
                   ?? 布局文件
    setContentView(R.layout.activity_main);
}

很明顯布局文件對應(yīng)的View的繪制和顯示是伴隨著Activity的生命周期而進行的扰柠。在Activity的啟動過程中,ActivityThread類中的handleLaunchActivity藐吮,performLaunchActivity溺拱,handleResumeActivity這3個主要的方法完成了Activity創(chuàng)建到啟動工作,完成了ActivityonCreate谣辞、onStart迫摔、onResume這三個生命周期的執(zhí)行。

具體的一個Activity啟動過程可以參考Android Activity生命周期泥从,啟動模式句占,啟動過程詳解

讓我們伴隨著Activity的生命周期執(zhí)行過程,來具體進行分析和相關(guān)源碼解讀躯嫉。先來張總結(jié)本文的圖:

Activity Window DecorView.png

Activity啟動過程

我們按照Activity的啟動過程中涉及到的ActivityThread類三個方法來逐步說明纱烘,首先來看handleLaunchActivity方法。

1. ActivityThread#handleLaunchActivity()方法:調(diào)用performLaunchActivity()和handleResumeActivity()方法

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...
    
    // ?? 此方法中完成 Activity 生命周期的 onCreate 和 onStart 方法
    Activity a = performLaunchActivity(r, customIntent);
    
    if (a != null) {
        
        ...
        
        // ?? 此方法中完成 Activity 生命周期的 onResume 方法
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
                
        ...
    }
    
    ...
}

handleLaunchActivity()方法中主要調(diào)用了performLaunchActivity()方法和handleResumeActivity()方法祈餐,performLaunchActivity()中完成onCreateonStart回調(diào)擂啥;handleResumeActivity()中完成onResume回調(diào)。

2. ActivityThread#performLaunchActivity()方法:執(zhí)行onCreate昼弟、onStart生命周期

performLaunchActivity()方法中主要做的工作包括:

  • 1?? 創(chuàng)建Activity實例:調(diào)用mInstrumentation.newActivity方法來創(chuàng)建Activity對象;
  • 2?? 創(chuàng)建Application對象奕筐,如果已經(jīng)創(chuàng)建就不再創(chuàng)建舱痘,一個進程只有一個Application對象;
  • 3?? 調(diào)用Activityattach方法离赫,在其中創(chuàng)建了PhoneWindow對象(有關(guān)PhoneWindow對象下文再慢慢解釋)芭逝;
  • 4?? 調(diào)用了InstrumentationcallActivityOnCreate方法,從而間接執(zhí)行了onCreate生命周期渊胸;
  • 5?? 調(diào)用了ActivityperformStart方法旬盯,從而間接執(zhí)行了onStart生命周期。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    ...
    
    // ?? 通過反射創(chuàng)建 Activity 實例
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);

    // ?? 通過反射構(gòu)建 Application翎猛,如果已經(jīng)構(gòu)建則不會重復(fù)構(gòu)建胖翰,一個進程只能有一個 Application
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);

    if (activity != null) {
        Context appContext = createBaseContextForActivity(r, activity);
        
        ...
        
        // ?? 在這里實例化了 PhoneWindow,并將該 Activity 設(shè)置為 PhoneWindow 的 Callback 回調(diào)切厘;
        // 初始化 WindowManager
        activity.attach(appContext, this, getInstrumentation(), r.token,
                r.ident, app, r.intent, r.activityInfo, title, r.parent,
                r.embeddedID, r.lastNonConfigurationInstances, config);
        
        ...
        
        // ?? 調(diào)用了 Activity 的 performCreate 方法萨咳,間接調(diào)用了 Activity 的 onCreate 方法
        mInstrumentation.callActivityOnCreate(activity, r.state);
        
        // ?? 調(diào)用 Activity 的 onStart 方法
        if (!r.activity.mFinished) {
            activity.performStart();
            r.stopped = false;
        }
        
        ...
    }
}

performLaunchActivity()方法的前兩步工作:Activity對象和Application對象的創(chuàng)建過程,這里不再具體描述疫稿,可以參考Android Activity生命周期培他,啟動模式鹃两,啟動過程詳解

下面重點看下attach()方法舀凛,其中創(chuàng)建了PhoneWindow對象俊扳,而PhoneWindowWindow的一個子類(其實也是唯一子類)。
要想理解PhoneWindow的作用猛遍,肯定要先知道Window這個概念馋记。

Window:視圖容器

Window代表一個窗口,是視圖的容器螃壤。Android中的視圖是以View樹的形式組織的抗果,而View樹必須依附在Window上才能工作,一個Window對應(yīng)著一個View樹奸晴。Activity并不負責(zé)視圖控制冤馏,它只是控制生命周期和處理事件,真正控制視圖的是Window寄啼。

啟動Activity時會創(chuàng)建一個Window逮光,顯示Dialog時也會創(chuàng)建一個Window,而顯示Toast時也會創(chuàng)建Window墩划,因此Activity內(nèi)部可以有多個Window涕刚。由于View的測量、布局乙帮、繪制只是在View樹內(nèi)進行的杜漠,因此一個Window內(nèi)View的改動不會影響到另一個Window。Window是一個抽象類察净,它只有一個實現(xiàn)類PhoneWindow驾茴,也就是說在PhoneWindow中完成了Window的真正工作。

Activity#attach()方法:實例化PhoneWindow

介紹完WindowPhoneWindow后氢卡,再回到attach方法锈至。其中主要做了以下兩步工作:

  • 1?? 實例化PhoneWindow:直接new一個PhoneWindow對象;
  • 2?? 為Activity的Window設(shè)置WindowManager:Android 中對 Window 的管理都是通過 WindowManager來完成的译秦,創(chuàng)建 PhoneWindow 之后還會為該 Window 對象設(shè)置 WindowManager 峡捡,WindowManager 是一個接口,繼承 ViewManager 接口筑悴,從這里也能看出對 Window 的操作其實就是對 View 的操作们拙。WindowManager 的實現(xiàn)類是 WindowMangerImpl 。具體關(guān)于 Window和WindowManager 的關(guān)系下文再說阁吝。
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) {
    ...

    // ?? 實例化 PhoneWindow
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    
    ...
    
    // ?? 為 Activity 的 Window 設(shè)置 WindowManager
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

    mWindowManager = mWindow.getWindowManager();
    ...
}

Activity#onCreate生命周期:調(diào)用setContentView方法(設(shè)置Activity對應(yīng)的布局)

performLaunchActivity()方法的第4??步中睛竣,調(diào)用了InstrumentationcallActivityOnCreate方法,源碼較為簡單求摇,如下:

public void callActivityOnCreate(Activity activity, Bundle icicle) {
    prePerformCreate(activity);
    activity.performCreate(icicle);
    postPerformCreate(activity);
}

// ?? 然后調(diào)用 Activity 的 performCreate 方法
final void performCreate(Bundle icicle) {
    performCreate(icicle, null);
}

// ?? 執(zhí)行 onCreate 生命周期
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
    ....
    if (persistentState != null) {
        onCreate(icicle, persistentState);
    } else {
        onCreate(icicle);
    }
    ......
}

Activity#setContentView方法

onCreate方法中射沟,我們通過setContentView來加載我們定義的布局文件殊者。Activity的源碼中提供了三個重載的setContentView方法,如下:

// ?? 最常用的 setContentView 方法
public void setContentView(int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

public void setContentView(View view) {
    getWindow().setContentView(view);
    initWindowDecorActionBar();
}

public void setContentView(View view, ViewGroup.LayoutParams params) {
    getWindow().setContentView(view, params);
    initWindowDecorActionBar();
}

這三種方法都先調(diào)用了getWindow()setContentView方法验夯,而getWindow()方法返回的正是之前創(chuàng)建的PhoneWindow對象猖吴;然后調(diào)用Activity的initWindowDecorActionBar方法。


// ?? Activity 中 Window 成員變量
private Window mWindow;

public Window getWindow() {
    // ?? 這個 mWindow 是在 attach 方法中設(shè)置的挥转;
    // ?? mWindow 是一個 PhoneWindow 對象
    return mWindow;
}

所以最終調(diào)用了PhoneWindow中的setContentView方法海蔽。

PhoneWindow#setContentView方法

PhoneWindow中也有三個對應(yīng)的重載setContentView方法,下面以參數(shù)為布局文件
id
(layoutResID)的方法進行說明绑谣,其中大概做了以下幾步工作:

  • 1?? 初始化:首先判斷 mContentParent 是否為null党窜,如果為 null,則調(diào)用 installDecor()方法初始化mContentParent借宵,此方法后續(xù)會說明幌衣;

mContentParent 用來裝xml布局文件解析出來的view樹,是一個FrameLayout壤玫,后文還會繼續(xù)說明其作用豁护。

  • 2?? 填充布局:默認情況下會將設(shè)置的布局文件解析為View樹,并添加到 mContentParent 中欲间;
  • 3?? 通知Activity布局改變:通過相關(guān)回調(diào)通知Activity布局已發(fā)改變楚里。
public void setContentView(int layoutResID) {
    // ?? 首先判斷 mContentParent 是否為 null,如果是第一次調(diào)用猎贴,則調(diào)用 installDecor() 方法
    if (mContentParent == null) {
            ??
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        // 否則判斷是否設(shè)置 FEATURE_CONTENT_TRANSITIONS Window屬性(默認false)班缎,
        // 如果沒有就移除該 mContentParent 內(nèi)所有的所有子View;
        mContentParent.removeAllViews();
    }

    // ?? 填充布局
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext()););
        transitionTo(newScene);
    } else {
        // 將xml資源文件通過 LayoutInflater 轉(zhuǎn)換為 View 樹
        // 并且添加至 mContentParent 視圖中
                          ??
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    
    // ?? 通知 Activity 布局改變
    // 獲取 Callback她渴,在 Activity attach 方法中通過 setCallback 設(shè)置
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        // onContentChanged 是個空方法达址,當(dāng) Activity 的布局改動時,
        // 即 setContentView() 或者 addContentView() 方法執(zhí)行完畢時就會調(diào)用該方法
               ??
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

PhoneWindowsetContentView方法將我們想要顯示的View添加到mContentParent中惹骂,而mContentParent又是由installDecor方法得來苏携,下面我們關(guān)注此方法做瞪。

DecorView:窗口頂層視圖

再介紹installDecor()方法之前对粪,我們需要介紹一個重要的概念:DecorView

installDecor()方法中重點完成了兩個對象的初始化装蓬,一個是mDecor對象著拭,另一個就是之前說的mContentParent對象,這兩個對象的定義如下:

// ?? 是一個 DecorView 對象牍帚,而 DecorView 是窗口頂層視圖
DecorView mDecor;

// ?? 是一個 ViewGroup 儡遮,本質(zhì)上是一個 FrameLayout
ViewGroup mContentParent;

這里讓我們先回顧下已經(jīng)提到的概念,Activity(調(diào)用setContentView設(shè)置布局)暗赶,PhoneWindowsetContentView中完成 mContentParent 初始化鄙币,填充布局等)肃叶,這兩個與視圖有關(guān)的概念并未真正直接承載視圖,而承載視圖的便是 DecorView 對象十嘿,即此處的 mDecor因惭,DecorView 繼承自 FrameLayout,如下所示:

public class DecorView extends FrameLayout

DecorView既然是一個FrameLayout绩衷,那么肯定也有其對應(yīng)的布局文件蹦魔,只不過是系統(tǒng)默認設(shè)置的布局文件,由于在不同的Activity主題情況下咳燕,系統(tǒng)默認的 DecorView 對應(yīng)的布局不一樣勿决,我們這里以其中一種舉例說明:

// ?? screen_simple.xml 為例
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <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
                       ?? 對應(yīng) mContentParent
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

那么mDecormContentParent 之間是什么關(guān)系呢? DecorView 分為兩部分招盲,一部分是 ActionBar有的Activity Theme情況下沒有ActionBar)低缩,另一部分就是 mContentParent即上圖中id為content的FrameLayout)。

DecorView.jpg

那么Activity宪肖、PhoneWindow表制、DecorView三者之間有什么關(guān)系呢?見下圖所示:

Activity PhoneWindow DecorView.png

PhoneWindow#installDecor方法

介紹完DecorView后控乾,我們再回頭介紹installDecor方法的具體工作么介,其中主要做了以下幾步工作:

  • 1?? 初始化mDecor:調(diào)用generateDecor方法來創(chuàng)建mDecor對象;
  • 2?? 初始化mContentParent:調(diào)用generateLayout方法創(chuàng)建mContentParent對象蜕衡;
private void installDecor() {
    ...
    
    // ?? 初始化 mDecor
    if (mDecor == null) {
        // 如果 mDecor 為空壤短,則生成一個 DecorView 對象
        mDecor = generateDecor(-1);
        
        ...
        
    } else {
        // 將 PhoneWindow 設(shè)置給 mDecor
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        // ?? 初始化 mContentParent
        mContentParent = generateLayout(mDecor);
        
        ...
        
    }
}

// ?? generateDecor:初始化 DecorView
protected DecorView generateDecor(int featureId) {

    ...
    
    return new DecorView(context, featureId, this, getAttributes());
}

// ?? generateLayout:根據(jù)窗口的風(fēng)格,為 DecorView 選擇對應(yīng)的布局文件
protected ViewGroup generateLayout(DecorView decor) {

    // 獲取窗口屬性
    TypedArray a = getWindowStyle();

    ...
    
    
    int layoutResource;
    int features = getLocalFeatures();
    
    // ?? 根據(jù)設(shè)定好的 features 值選擇不同的窗口布局文件慨仿,得到 layoutResource 值久脯,前文中曾以 screen_simple 舉例
    // 注意:此處還有很多分支判斷代碼省略了
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
    } 
    
    ...

    // ?? 把選中的窗口布局文件解析成 View 樹,并添加到 DecorView 中
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    // ?? 指定 contentParent 值镰吆,對應(yīng)的是布局文件中 id 為 content 的 View
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    ...

    // ?? 返回 contentParent帘撰,賦值給 mContentParent,所以 mContentParent 對應(yīng)的是 R.id.content
    return contentParent;
}

介紹完installDecor方法万皿,我們再來看看PhoneWindow.setContentView方法中的第2??步工作:填充布局摧找,即默認情況下會將我們設(shè)置的布局文件解析為 View 樹,并添加到 mContentParent 中牢硅,現(xiàn)在我們應(yīng)該理解的更加清楚了蹬耘。

3. ActivityThread#handleResumeActivity()方法:執(zhí)行onResume生命周期

執(zhí)行完onCreate、onStart生命周期后减余,來到了handleResumeActivity()方法中執(zhí)行onResume生命周期综苔,有一點需要注意的是,到目前為止,我們也僅是生成了一個Activity如筛,一個PhoneWindow堡牡,一個DecorView,而并未真正的將我們需要顯示的內(nèi)容和Android系統(tǒng)進行交互杨刨,以進行View繪制悴侵,而onResume是我們啟動Activity過程中的生命周期的最后一步,那我們有理由猜想有關(guān)繪制的執(zhí)行應(yīng)該在此生命周期中執(zhí)行拭嫁。此方法主要工作包括以下幾步:

  • 1?? 執(zhí)onResume生命周期:調(diào)用 performResumeActivity 來間接執(zhí)行 Activity 的 onResume 生命周期可免;
  • 2?? 獲取Activity的Window和DecorView:獲取這兩個變量賦值給相關(guān)變量,同時暫時使 DecorView 不可見做粤;
  • 3?? 獲取 WindowManager:在Activity.attach()方法中浇借,我們?yōu)锳ctivity設(shè)置了WindowManager
  • 4?? WindowManager添加DecorView:調(diào)用WindowManager.addView方法為Window添加DecorView怕品;
  • 5?? 使得 DecorView 可見:調(diào)用Activity.makeVisible方法使得DecorView重新可見妇垢。
final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        
    ...
    
    // ?? 在其內(nèi)部間接執(zhí)行 Activity 的 onResume 方法,此時界面還不可見
    ActivityClientRecord r = performResumeActivity(token, clearHide, reason);

    ...
    
    final Activity a = r.activity;
    
    ...
    
    // ?? 獲取 Window肉康,DecorView 等對象
    r.window = r.activity.getWindow();
    View decor = r.window.getDecorView();
    // ?? 使 DecorView 不可見
    decor.setVisibility(View.INVISIBLE);
    
    // ?? 獲取 WindowManager
    ViewManager wm = a.getWindowManager();
    WindowManager.LayoutParams l = r.window.getAttributes();
    a.mDecor = decor;

    if (a.mVisibleFromClient && !a.mWindowAdded) {
        ...
        // ?? WindowManager 添加 DecorView闯估,此時依然不可見
        wm.addView(decor, l);
        ...
    }
    
    ...
    
    if (r.activity.mVisibleFromClient) {
        // ?? 使得 DecorView 可見
        r.activity.makeVisible();
    }
            
    ...

}

// ?? Activity.makeVisible方法:使得 DecorView 可見
void makeVisible() {
    
    ...
    
    mDecor.setVisibility(View.VISIBLE);
}

可見到目前為止,還是沒有相關(guān)直接繪制View的操作吼和,但對DecorView的操作有好幾處涨薪,重點是第4??步,其中調(diào)用WindowManageraddView方法添加DecorView炫乓。我們之前解釋過View必須依附于Window才能顯示刚夺,而Android 中對 Window 的管理都是通過 WindowManager 來完成的,那相關(guān)的繪制操作應(yīng)該就是在此方法中了末捣。

在理解addView方法之前侠姑,我們先來看看WindowManager的初始化過程,即Activity.attach方法中調(diào)用的setWindowManager方法:

// ?? Window.setWindowManager 方法
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
    
    ...
    
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

// ?? WindowManagerImpl.createLocalWindowManager 方法
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mContext, parentWindow);
}

可見此處WindowManager它的真正實現(xiàn)是WindowManagerImpl箩做,而WindowManagerImpl也并沒有真正實現(xiàn)ViewManager接口的三大操作(addView莽红,updateViewLayout,removeView)邦邦,而是交給了WindowManagerGlobal安吁。

// ?? WindowManagerImpl.addView 方法
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
      ?? WindowManagerGlobal對象
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

mGlobal 對象的初始化如下,就是 WindowManagerGlobal 的單例模式圃酵。

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

WindowManagerGlobal#addView方法

下面再來看看 WindowManagerGlobal.addView 方法柳畔,主要做了兩步工作:

  • 1?? 初始化ViewRootImplViewRootImpl視圖層次的最頂層馍管,連接WindowManagerService和DecorView的紐帶郭赐, 同時 ViewRootImpl 的內(nèi)容大部分都是 WindowManagerGlobal 類的內(nèi)部實現(xiàn)細節(jié)
  • 2?? 調(diào)用 ViewRootImpl.setView 方法:調(diào)用此方法來完成真正的添加 View 操作。
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ...

    // ?? ViewRootImpl 對象
    ViewRootImpl root;
    
    synchronized (mLock) {
    
        ...
        
        // ?? 初始化 ViewRootImpl 
        root = new ViewRootImpl(view.getContext(), display);

        ...
        
        // ?? 調(diào)用 ViewRootImpl.setView 方法
        root.setView(view, wparams, panelParentView);
    }
}

ViewRootImpl#setView方法

真正完成添加 View 的操作是此方法:

// ?? ViewRootImpl.setView 方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    
        ??
    requestLayout();
    
    ...
                            ??
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
    
    ...
            ?? 將 DecorView 的 parent 設(shè)置為 ViewRootImpl
    view.assignParent(this);
    
    ... 
}

此方法中做的工作主要包括以下兩個方面:

  • 1?? requestLayout()方法調(diào)用View的測量捌锭,布局俘陷,繪制三個流程
  • 2?? mWindowSession.addToDisplay()方法mWindowSession 是一個aidl观谦,ViewRootImpl 利用它來和 WindowManagerService 進行跨進程交互拉盾,這里先不過多介紹有關(guān) WindowManagerService 內(nèi)容,只簡單介紹下其作用豁状,如下捉偏。

WindowManagerService(WMS)的作用有很多:

  • 窗口的管理者:負責(zé)窗口的啟動、添加和刪除泻红,另外窗口的大小和層級也是由WMS進行管理夭禽;
  • 事件的管理和派發(fā)工作:通過對窗口的觸摸從而產(chǎn)生觸摸事件,InputManagerService(IMS)會對觸摸事件進行處理谊路,它會尋找一個最合適的窗口來處理觸摸反饋信息讹躯,WMS是窗口的管理者,因此缠劝,WMS“理所應(yīng)當(dāng)”的成為了事件的中轉(zhuǎn)站潮梯。
  • Surface管理:窗口并不具備有繪制的功能,因此每個窗口都需要有一塊Surface來供自己繪制惨恭。為每個窗口分配Surface是由WMS來完成的秉馏。

摘自Android解析WindowManagerService(一)WMS的誕生

我們重點看看 requestLayout() 方法,源碼如下:

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        // ?? 檢查是不是主線程
        checkThread();
        mLayoutRequested = true;
        // ?? view繪制三大流程入口
        scheduleTraversals();
    }
}

熟悉View繪制流程的同學(xué)應(yīng)該知道脱羡,當(dāng)View的大小沃饶、形狀發(fā)生了變化的時候,可以調(diào)用此方法來進行重新繪制轻黑,此方法會從View樹重新進行一次測量糊肤、布局、繪制這三個流程氓鄙。此方法主要做了兩步工作:

  • 1?? 檢查當(dāng)前線程:如果調(diào)用此方法的現(xiàn)場不是主線程馆揉,那么就在checkThread()方法中拋出異常,這一點與我們"通常意義"上所說的:只能在主線程更新UI相吻合抖拦,具體checkThread()方法稍后寫明升酣;
  • 2?? 調(diào)用 scheduleTraversals 方法:此方法后續(xù)完成了View的三大流程(測量(measure),布局(layout)态罪,繪制(draw))噩茄,具體分析見后續(xù) 。

再看下 checkThread() 方法:

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

mThread 的初始化是在 ViewRootImpl 的構(gòu)造函數(shù)中完成的:

// ViewRootImpl 構(gòu)造函數(shù)
public ViewRootImpl(Context context, Display display) {
    ...
    
      ??
    mThread = Thread.currentThread();
    
    ...
}

回憶下前文复颈, ViewRootImpl 的初始化是在 WindowManagerGlobal.addView 方法中完成的绩聘,因此mThread 肯定對應(yīng)的是主線程,因為 ActivityThread.handleLaunchActivity 方法就是在主線程中執(zhí)行的,而其中并未切換過線程凿菩。因此如果我們在子線程中更新UI机杜,那么最終會走到 requestLayout 方法進行重繪制,但此時會發(fā)現(xiàn) mThread(主線程) 和 Thread.currentThread()(子線程)不是同一個線程衅谷,那么便會拋出異常椒拗。

再來說說 ViewRootImpl ,其作用非常重大获黔。所有View的繪制以及事件分發(fā)等交互都是通過它來執(zhí)行或傳遞的蚀苛。View的三大流程(測量(measure),布局(layout)玷氏,繪制(draw))均通過ViewRootImpl來完成枉阵。Android的所有觸屏事件、按鍵事件预茄、界面刷新等事件都是通過ViewRootImpl進行分發(fā)的兴溜。

從源碼實現(xiàn)上來看,ViewRooImpl 既非View的子類耻陕,也非View的父類拙徽,但是,它實現(xiàn)了ViewParent接口诗宣,這讓它可以作為View的名義上的父視圖膘怕。
ViewRootImpl.setView 方法中調(diào)用了 View.assignParent 方法:

void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } 
    
    ...
}

View是以View樹組織的,每個View都有其Parent召庞,DecorView作為最頂層的View岛心,其Parent被設(shè)置為ViewRootImpl對象這樣它可以作為View的名義上的父視圖篮灼,實質(zhì)上完成了View的更新忘古,繪制等工作

View Tree.png

View類中也有requestLayout方法:用于 View 的位置诅诱,大小髓堪、形狀發(fā)生了變化的時候進行調(diào)用。當(dāng)一個子 View 調(diào)用此方法時娘荡,便會令 View 樹重新進行一次測量干旁、布局、繪制這三個流程炮沐,具體源碼如下:

// ?? View#requestLayout:
public void requestLayout() {
    
    ...
   
    if (mParent != null && !mParent.isLayoutRequested()) {
        // ?? 調(diào)用父容器的 `requestLayout` 方法
        mParent.requestLayout();
    }
    
    ... 
}

此方法中最重要的是調(diào)用mParent.requestLayout方法争群,是向父容器請求布局,即調(diào)用父容器的 requestLayout 方法大年,此方法沿著View樹向上傳遞换薄,最終來到了 DecorView#requestLayout中玉雾,而DecorView是頂層View,其mParent便是ViewRootImpl专控,所以子View的requestLayout方法,經(jīng)過層層傳遞遏餐,最終會被ViewRootImpl 接收并處理伦腐。

那具體的 scheduleTraversals 方法中主要完成了 View 的三大流程,本文暫且不進行分析失都。

總結(jié)

通過以上可以知道柏蘑,Activity就像個控制器,不負責(zé)視圖部分粹庞。Window像個承載器咳焚,裝著內(nèi)部視圖DecorView是個頂層視圖庞溜,是所有View的最外層布局革半。ViewRootImpl像個連接器,負責(zé)溝通流码,通過硬件的感知(分發(fā)事件又官,繪制View內(nèi)容)來通知視圖,進行用戶之間的交互漫试。

其實本文還存在兩個問題沒有說明:

  • 1?? WindowManagerService具體原理ViewRootImpl 和 WindowManagerService 進行跨進程交互的背后原理是什么六敬,怎么做到View的真正顯示?
  • 2?? View繪制的三大流程View 測量驾荣,布局外构,繪制的詳細流程是什么?

這些分析后續(xù)陸續(xù)進行說明播掷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末审编,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子歧匈,更是在濱河造成了極大的恐慌割笙,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眯亦,死亡現(xiàn)場離奇詭異伤溉,居然都是意外死亡,警方通過查閱死者的電腦和手機妻率,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門乱顾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宫静,你說我怎么就攤上這事走净∪保” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵伏伯,是天一觀的道長橘洞。 經(jīng)常有香客問我,道長说搅,這世上最難降的妖魔是什么炸枣? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮弄唧,結(jié)果婚禮上适肠,老公的妹妹穿的比我還像新娘。我一直安慰自己候引,他們只是感情好侯养,可當(dāng)我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著澄干,像睡著了一般逛揩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上麸俘,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天息尺,我揣著相機與錄音,去河邊找鬼疾掰。 笑死搂誉,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的静檬。 我是一名探鬼主播炭懊,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拂檩!你這毒婦竟也來了侮腹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤稻励,失蹤者是張志新(化名)和其女友劉穎父阻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體望抽,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡加矛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了煤篙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斟览。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖辑奈,靈堂內(nèi)的尸體忽然破棺而出苛茂,到底是詐尸還是另有隱情已烤,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布妓羊,位于F島的核電站胯究,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏躁绸。R本人自食惡果不足惜裕循,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涨颜。 院中可真熱鬧费韭,春花似錦茧球、人聲如沸庭瑰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弹灭。三九已至,卻和暖如春揪垄,著一層夾襖步出監(jiān)牢的瞬間穷吮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工饥努, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捡鱼,地道東北人。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓酷愧,卻偏偏與公主長得像驾诈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子溶浴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,562評論 2 349