View的繪制源碼分析

平時(shí)我們加載xml文件都是直接在onCreate方法中調(diào)用setContentView,在加載Activity的onCreate方法時(shí)布局就被加載出來(lái)了,但是深入一點(diǎn)看,發(fā)現(xiàn)內(nèi)容還是很多的,看了很多大神相關(guān)的博客,也寫(xiě)了個(gè)總結(jié),內(nèi)容可能不全,不足之處,還請(qǐng)多指教。

1.先從setContentView開(kāi)始說(shuō)起

Activity中有3個(gè)setContentView()方法,可以看到, 這三個(gè)方法都是先getWindow,獲得一個(gè)Window對(duì)象,然后調(diào)用它的setContentView,那么這個(gè)Window又是什么呢?

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();
}

1.1 創(chuàng)建Window對(duì)象

getWindow返回的是mWindow,而這個(gè)mWindow是由PolicyManager創(chuàng)建的,PolicyManager提供了靜態(tài)類方法(這里用到了工廠模式,PolicyManager提供工廠方法),創(chuàng)建了一個(gè)PhoneWindow 對(duì)象。

//創(chuàng)建一個(gè)Window對(duì)象
mWindow = PolicyManager.makeNewWindow(this);

最終創(chuàng)建Window對(duì)象的方法

//創(chuàng)建具體對(duì)象的接口 
public PhoneWindow makeNewWindow(Context context) { 
    return new PhoneWindow(context); 
}

Window:是一個(gè)抽象類,提供繪制窗口的通用API。
PhoneWindow :是Window的唯一的實(shí)現(xiàn)類,每個(gè)Activity都會(huì)有一個(gè)PhoneWindow,它是Activity和整個(gè)View交互的接口。該類內(nèi)部包含了一個(gè)DecorView對(duì)象,該DectorView對(duì)象是所有應(yīng)用窗口(Activity界面)的根View。
DectorView:是PhoneWindow的內(nèi)部類,繼承自FrameLayout。

1.2 調(diào)用PhoneWindow對(duì)象的setContentView方法

一層層下來(lái),發(fā)現(xiàn)Activity的setContentView()實(shí)際上是執(zhí)行的是PhoneWindow的方法,現(xiàn)在來(lái)看一下PhoneWindow的setContentView()。

@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)) {        
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,                getContext());        
        transitionTo(newScene);    
    } else {        
        mLayoutInflater.inflate(layoutResID, mContentParent);    
    }    
    final Callback cb = getCallback();    
    if (cb != null && !isDestroyed()) {        
        cb.onContentChanged();    
    }
}

以上代碼分析:
1 . 判斷mContentParent是否為空。從名字可以看出,這是父容器,它是一個(gè)ViewGroup類型的對(duì)象,是真正的content的Parent,如果是第一次調(diào)用,會(huì)調(diào)用installDecor(),在這個(gè)方法中會(huì)先判斷DectorView是否為空,為空就先創(chuàng)建DectorView對(duì)象mDecor,然后調(diào)用generateLayout(mDecor)得到mContentParent;如果不是第一次調(diào)用,會(huì)先清除mContentParent中的子view。

2 . mLayoutInflater.inflate(layoutResID, mContentParent);將傳入的資源文件轉(zhuǎn)換成View樹(shù),再添加到mContentParent中(mLayoutInflater 在PhoneWindow的構(gòu)造函數(shù)中通過(guò)mLayoutInflater = LayoutInflater.from(context)得到)。

再來(lái)看一下其他的兩個(gè)setContentView()

@Override
public void setContentView(View view) {
    setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    // 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 {
        mContentParent.addView(view, params);
    }
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

可以看到,其實(shí)做的事情都差不多,只不過(guò)多設(shè)置了LayoutParams,因?yàn)閭魅氲腣iew,就不需要像第一個(gè)那樣還從xml文件中解析出來(lái),可以直接將view添加到mContentParent中。

小結(jié):比較3個(gè)setContentView

從上面的代碼中,我們可以看到,第一個(gè)setContentView是通過(guò)反射解析傳入的布局文件,然后添加到mContentParent,而后兩個(gè)setContentView是直接將傳入的View添加到mContentParent,需要注意的是,每次反射拿到的View都是重新創(chuàng)建的,就算兩次setContentView加載的是同一個(gè)布局文件,控件的實(shí)例也是不一樣的,如傳入的是View/ViewGroup就能保證傳入的是同一組控件。

1.3 installDecor()實(shí)例化DectorView對(duì)象

現(xiàn)在來(lái)看一下剛剛提到的installDecor(),初始化mDecor ,創(chuàng)建mContentParent,根據(jù)窗口的風(fēng)格修飾,選擇對(duì)應(yīng)的修飾布局文件,這里內(nèi)容太多,省略了。

private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor();
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    }
    if (mContentParent == null) {
        //根據(jù)窗口的風(fēng)格修飾,選擇對(duì)應(yīng)的修飾布局文件
        mContentParent = generateLayout(mDecor);
        ......
    }
}

1.4 generateLayout()創(chuàng)建mContentParent

接下來(lái)看generateLayout()創(chuàng)建mContentParent 的過(guò)程。

protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    TypedArray a = getWindowStyle();
    //根據(jù)當(dāng)前的主題設(shè)置窗口屬性
    ......
    // Inflate the window decor.

    int layoutResource;
    int features = getLocalFeatures();

    //根據(jù)當(dāng)前的窗口屬性選擇相對(duì)應(yīng)的布局
    WindowManager.LayoutParams params = getAttributes();
    ......
    //將相應(yīng)的布局文件轉(zhuǎn)成view添加到窗口視圖對(duì)象decor中
    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;

    ViewGroup contentParent = (ViewGroup)findViewById(ID_Android_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    ......
    return contentParent;
}

這段代碼所做的事情:

  1. 根據(jù)當(dāng)前的主題設(shè)置窗口屬性;
  2. 根據(jù)當(dāng)前的窗口屬性選擇相對(duì)應(yīng)的布局;
  3. 將相應(yīng)的布局文件轉(zhuǎn)成view添加到根視圖對(duì)象decor/mDecor中
    View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));,需要注意的是,這里的layoutResource并不是我們傳入的資源文件,而是系統(tǒng)定義的。
  4. 從根布局中找到id為ID_Android_CONTENT的ViewGroup賦值給contentParent,也就是上文的mContentParent

總結(jié):Activity,PhoneWindow,DectorView,mContentParent之間的關(guān)系

通過(guò)上面的分析,我們來(lái)看一下彼此之間的關(guān)系,有助于理解。

DecorView繼承于FrameLayout,然后它有一個(gè)子view即LinearLayout,方向?yàn)樨Q直方向,其內(nèi)有兩個(gè)FrameLayout,上面的FrameLayout即為T(mén)itleBar之類的,下面的FrameLayout即為我們的ContentView,所謂的setContentView就是往這個(gè)FrameLayout里面添加我們的布局View的!

DectorView及其下層view的結(jié)構(gòu)
層級(jí)關(guān)系

2.PhoneWindow的setContentView最后的回調(diào)

上面分析了加載視圖到父容器mContentParent中,現(xiàn)在我們看一下setContentView()中的最后一步。

@Override
public void setContentView(int layoutResID) {
    ......
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

至此,已經(jīng)分析完了將傳入的布局文件添加到View的整個(gè)過(guò)程。接下來(lái)可以看到,它先創(chuàng)建了一個(gè)CallBack回調(diào)接口,在加載完以上的View到根布局之后,就會(huì)調(diào)用這個(gè)回調(diào)接口,順便說(shuō)一下,cb.onContentChanged()方法在Activity中是一個(gè)空方法,我們可以在自定義的Activity中覆寫(xiě)這個(gè)方法。

現(xiàn)在看getCallback()是由Window提供的,PhoneWindow并沒(méi)有實(shí)現(xiàn),繼續(xù)往下看,發(fā)現(xiàn)Window中有一個(gè)public void setCallback(Callback callback)方法,接收到外部傳入的callback,賦值給內(nèi)部的mCallback 。那么這個(gè)外部方法在哪里調(diào)用呢?這個(gè)就要說(shuō)一下Activity的啟動(dòng)了。

3.Activity的啟動(dòng)

在Activity加載時(shí)會(huì)先創(chuàng)建一個(gè)activity實(shí)例,然后調(diào)用activity的attach方法完成activity的初始化過(guò)程,我們來(lái)看一下attach()方法。

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, IVoiceInteractor voiceInteractor) {
    attachBaseContext(context);
    
    mFragments.attachActivity(this, mContainer, null);

     //1.創(chuàng)建窗口對(duì)象,是一個(gè)PhoneWindow實(shí)例
    mWindow = PolicyManager.makeNewWindow(this);

    //2.設(shè)置回調(diào)
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    
    ......

    mToken = token;
    
    //3.將創(chuàng)建的WindowManager注入窗口對(duì)象以便管理窗口的視圖對(duì)象
    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());
    }
    //4. 獲取窗口管理器
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config;
}
  1. 在這個(gè)方法中,完成了上文提到的Window對(duì)象的創(chuàng)建。

  2. 為Window對(duì)象設(shè)置回調(diào),也就是上面 setContentView()中最后獲取到cb。這里設(shè)置的回調(diào)就是Activity自己,再看Activity,它實(shí)現(xiàn)了Window.Callback, KeyEvent.Callback兩個(gè)回調(diào)接口。其中 KeyEvent.Callback接口中聲明了處理手勢(shì)事件的方法(onKeyDown按下,onKeyUp抬起,onKeyLongPress長(zhǎng)按...),而Window.Callback聲明了一些事件分發(fā)的函數(shù),關(guān)于View的事件分發(fā),可以看這個(gè) View的事件分發(fā)機(jī)制 ,從這里可以看出activity本身不具備處理用戶的事件的能力。

  3. 為mWindow設(shè)置WindowManager,WindowManager主要用來(lái)管理窗口的一些狀態(tài)、屬性、view增加、刪除、更新、窗口順序、消息收集和處理等.

  4. 獲取Window的WindowManager的實(shí)現(xiàn)類WindowManagerImpl保存在activity的mWindowManager中。

3.1 關(guān)于mWindow.setWindowManager()方法

從setWindowManager方法中可以發(fā)現(xiàn),這里返回的是WindowManagerImpl對(duì)象,WindowManager是一個(gè)接口,而WindowManagerImpl是它的實(shí)現(xiàn)類,這里調(diào)用createLocalWindowManager()

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {
    mAppToken = appToken;
    mAppName = appName;
    mHardwareAccelerated = hardwareAccelerated
            || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
    if (wm == null) {
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
    }
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

WindowManagerImpl中的createLocalWindowManager()方法

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mDisplay, parentWindow);
}

3.2 將生成的窗口視圖對(duì)象是添加到手機(jī)屏幕

我們知道,Activity的視圖是在Activity的生命周期onResume()方法執(zhí)行之后才會(huì)顯示的,這是因?yàn)樵贏ctivityThread的handleResumeActivity方法中調(diào)用了Activity的onResume()方法,關(guān)于Activity的啟動(dòng)這一部分內(nèi)容我還沒(méi)有看過(guò),“老羅的Android之旅”中有這一部分內(nèi)容的詳細(xì)分析,有興趣的可以看一下。在這個(gè)函數(shù)中,會(huì)調(diào)用activity的makeVisible方法經(jīng)WindowManagerImpl將DecorView展示出來(lái)。這里的getWindowManager()就是之前設(shè)置的WindowManager。最后就Activity就可以請(qǐng)求WindowManagerService將視圖繪制到屏幕上了。

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

總結(jié)

總結(jié)一下整個(gè)View的加載過(guò)程:

  1. 首先在Activity啟動(dòng)時(shí),在attach方法中先創(chuàng)建Activity的窗口對(duì)象,它是PhoneWindow類型,每個(gè)Activity都有一個(gè)窗口對(duì)象,然后為這個(gè)窗口設(shè)置各種事件的回調(diào),還要注冊(cè)其對(duì)應(yīng)的窗口管理器,用來(lái)管理窗口的一些狀態(tài),屬性,view的更新等。

  2. 當(dāng)調(diào)用Activity的onCreat方法時(shí),會(huì)調(diào)用設(shè)置布局文件,Activity的setContentView其實(shí)調(diào)用的是Activity的窗口對(duì)象PhoneWindow的setContentView,PhoneWindow有一個(gè)內(nèi)部類DectorView(FrameLayout的子類),它是整個(gè)窗口下的根View,內(nèi)部包含兩個(gè)FrameLayout,一個(gè)根據(jù)主題樣式來(lái)進(jìn)行TitleBar之類設(shè)置,一個(gè)就是用來(lái)裝我們傳入的布局文件中的view,這個(gè)就是mContetntParent。第一次調(diào)用PhoneWindowsetcontentView方法會(huì)先創(chuàng)建DectorView,進(jìn)行一些初始化的設(shè)置,然后解析系統(tǒng)的資源文件到DectorView中,接著會(huì)找到id為ID_Android_CONTENTFrameLayout,將其賦值給mContetntParent,用來(lái)放我們傳入的資源文件解析出來(lái)的view.

  3. 這些初始化的設(shè)置完成之后,就是處理我們調(diào)用setContentView時(shí)傳入的布局文件了,如果傳入的資源文件id,會(huì)調(diào)用反射機(jī)制解析xml文件,再把解析出來(lái)的各個(gè)view加到mContetntParent,如果傳入的是View,那么直接加到mContetntParent就可以,最后就是系統(tǒng)在調(diào)用onResume之后,經(jīng)之前設(shè)置的WindowManager將整個(gè)DecorView展示出來(lái)。

最后編輯于
?著作權(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)容