UI的繪制流程以及原理

一.View是如何繪制到窗口屏幕上的
1.源碼解析

創(chuàng)建activity的時(shí)候我們都會(huì)在onCreate里看到setContentView方法,并且傳入了我們創(chuàng)建的layout

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

第一步 我們點(diǎn)開setContentView會(huì)看到如下源碼

 /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

我們可以看到 資源文件傳入到了getWindow().setContentView(layoutResID);
那么可以繼續(xù)看看getWindow(),進(jìn)入getWindow()我們可以看到返回的是一個(gè)Window對(duì)象:

 /**
     * Retrieve the current {@link android.view.Window} for the activity.
     * This can be used to directly access parts of the Window API that
     * are not available through Activity/Screen.
     *
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() {
        return mWindow;
    }

進(jìn)入Window中我們發(fā)現(xiàn)這個(gè)Window是一個(gè)抽象類

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
        大概翻譯: 這個(gè)抽象類是唯一現(xiàn)有的實(shí)現(xiàn)android.view.PhoneWindow,需要時(shí)你應(yīng)該實(shí)例化窗口。
 */
public abstract class Window {
    /** Flag for the "options panel" feature.  This is enabled by default. */
    public static final int FEATURE_OPTIONS_PANEL = 0;
    /** Flag for the "no title" feature, turning off the title at the top
     *  of the screen. */
    public static final int FEATURE_NO_TITLE = 1;
    .....此處代碼省略
}

( 這個(gè)抽象類是唯一現(xiàn)有的實(shí)現(xiàn)android.view.PhoneWindow,需要時(shí)你應(yīng)該實(shí)例化窗口擎值。)
根據(jù)這句話我們可以看出PhoneWindow實(shí)現(xiàn)了這個(gè)抽象類,所以我們需要進(jìn)入到PhoneWindow查看setContentView(int layoutResID)方法的具體實(shí)現(xiàn)

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ....此處省略若干代碼
        @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);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
    ....此處省略若干代碼
}

我們進(jìn)入到 setContentView方法中查看后發(fā)現(xiàn)薛窥,這個(gè)方法主要的過程在 installDecor()方法和mLayoutInflater.inflate(layoutResID, mContentParent)袜蚕。接下來我們主要進(jìn)入到installDecor() 方法(這個(gè)方法我們可以理解為加載DecorView的一個(gè)過程)。通過它横殴,我們可以去了解加載Decor的整個(gè)過程。

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            .....此處省略若干代碼
        }
    }

通過以上源碼我們可以發(fā)現(xiàn)兩個(gè)方法

  • mDecor = generateDecor(-1)-->(創(chuàng)建了一個(gè)DecorView直接返回)
  • mContentParent = generateLayout(mDecor)-->(傳入了DecorView,并將layoutResource基礎(chǔ)布局添加(加載)到了DecorView上)

1.generateDecor()源碼:

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();
        }
    //這里是重點(diǎn)  我們可以看到返回了一個(gè)DecorView對(duì)象 然后傳入到了generateLayout(mDecor)方法中
        return new DecorView(context, featureId, this, getAttributes());
    }

這里我們可以簡(jiǎn)單看下DecorView的源碼,到底是用來干什么的

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    ...省略代碼
}

我們可以看到DecorView繼承自FrameLayout,說明DecorView是一個(gè)容器,所以 generateDecor(int featureId)方法就是獲取一個(gè)DecorView容器

2.generateLayout(mDecor)源碼

   protected ViewGroup generateLayout(DecorView decor) {
         // Apply data from current theme.設(shè)置系統(tǒng)當(dāng)前主題

        TypedArray a = getWindowStyle();

        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }

        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }

        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }

        if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
            requestFeature(FEATURE_ACTION_BAR_OVERLAY);
        }

        if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
            requestFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
          ...省略
                // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
         ...省略部分
              else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
        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");
        }
       
       ...省略部分
            return contentParent;
       
   }

generateLayout方法通過源碼我們可以看出他做了如下工作:
1.通過 setFlags(0, flagsToUpdate);和 requestFeature();一系列方法設(shè)置了系統(tǒng)主題等配置

2.解析窗口View
根據(jù)不同的features獲取不同的layoutResource ,然后通過DecorView的onResourcesLoaded方法將layoutResource加載到DecorView中

3.根據(jù)layoutResource我們可以通過 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);獲取到對(duì)應(yīng)的contentParent,并且返回

   /**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

ID_ANDROID_CONTENT就是XML中容器的ID,我們可以進(jìn)入到 如:R.layout.screen_simple 中查看

<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
         android:id="@android:id/content"http://這里就是我們需要的ID
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

當(dāng)我們返回contentParent后,generateLayout方法也就執(zhí)行完了,installDecor執(zhí)行的整個(gè)過程其實(shí)就是在獲取DecorView,然后再將layoutResource加載到DecorView中,最終我們又通過com.android.internal.R.id.content這個(gè)id獲取到contentParent,根據(jù)layout代碼我們可以看出contentParent實(shí)際上就是一個(gè)FrameLayout容器

在instalDecor中執(zhí)行完generateDecor和generateLayout方法后我們獲取到contentParent我們回到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();//執(zhí)行完 獲取到contentParent
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
        //通過此方法將我們的layoutResID綁定到mContentParent上
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

總結(jié):

1.在PhoneWindow中我們會(huì)創(chuàng)建一個(gè)頂層布局容器DecorView(是繼承自FrameLayout的)
2.創(chuàng)建完DecorView后,我們會(huì)將系統(tǒng)布局加載到DecorView中
3.獲取到基礎(chǔ)布局中基礎(chǔ)容器(R.id.content),這個(gè)容器實(shí)際上是一個(gè)FrameLayout
4.最終將我們通過setContentView傳遞過來的資源ID綁定到基礎(chǔ)容器中

圖解:


_20190805180030.png

暫時(shí)寫到這里后續(xù)補(bǔ)充..........

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市庙睡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌技俐,老刑警劉巖乘陪,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異雕擂,居然都是意外死亡啡邑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門井赌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谤逼,“玉大人,你說我怎么就攤上這事仇穗×鞑浚” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵纹坐,是天一觀的道長(zhǎng)枝冀。 經(jīng)常有香客問我,道長(zhǎng)恰画,這世上最難降的妖魔是什么宾茂? 我笑而不...
    開封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮拴还,結(jié)果婚禮上跨晴,老公的妹妹穿的比我還像新娘。我一直安慰自己片林,他們只是感情好端盆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開白布怀骤。 她就那樣靜靜地躺著,像睡著了一般焕妙。 火紅的嫁衣襯著肌膚如雪蒋伦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天焚鹊,我揣著相機(jī)與錄音痕届,去河邊找鬼。 笑死末患,一個(gè)胖子當(dāng)著我的面吹牛研叫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播璧针,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼嚷炉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了探橱?” 一聲冷哼從身側(cè)響起申屹,我...
    開封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎隧膏,沒想到半個(gè)月后哗讥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡私植,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年忌栅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片曲稼。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖湖员,靈堂內(nèi)的尸體忽然破棺而出贫悄,到底是詐尸還是另有隱情,我是刑警寧澤娘摔,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布窄坦,位于F島的核電站,受9級(jí)特大地震影響凳寺,放射性物質(zhì)發(fā)生泄漏鸭津。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一肠缨、第九天 我趴在偏房一處隱蔽的房頂上張望逆趋。 院中可真熱鬧,春花似錦晒奕、人聲如沸闻书。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魄眉。三九已至砰盐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坑律,已是汗流浹背岩梳。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晃择,地道東北人蒋腮。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像藕各,于是被迫代替她去往敵國(guó)和親池摧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容