重溫View繪制原理(一)

好記性不如爛筆頭盘榨。生活中多做筆記,不僅可以方便自己蟆融,還可以方便他人草巡。

(下面的源碼大部分是來自API 28)

1. View的知識前提

View的繪制是從上往下一層層迭代下來的:DecorView-->ViewGroup(--->ViewGroup)-->View,所以型酥,在學(xué)習(xí)view的繪制原理前山憨,我們來先看看DecorView。

1.1 DecorView的視圖結(jié)構(gòu)

DecorView的視圖結(jié)構(gòu).jpg

Android 中 Activity 是作為應(yīng)用程序的載體存在弥喉,代表著一個完整的用戶界面郁竟,提供了一個窗口來繪制各種視圖。每個activity都對應(yīng)一個窗口window由境,這個窗口是PhoneWindow的實(shí)例枪孩,PhoneWindow對應(yīng)的布局是DecirView,是一個FrameLayout,DecorView內(nèi)部又分為兩部分蔑舞,一部分是ActionBar,另一部分是ContentParent嘹屯,即activity在setContentView對應(yīng)的布局攻询。

1.2 從源碼看DecorView

activity在啟動的時候都會在onCreate中執(zhí)行setContentView方法:

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

以setContentView為切入點(diǎn),分析Activity州弟、PhoneWindow钧栖、DecorView、ActionBar和ContentParent的關(guān)系婆翔。

進(jìn)入setContentView

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

進(jìn)入activity的setContentView拯杠,發(fā)現(xiàn)里面調(diào)用的是getWindow()的setContentView(layoutResID)。

繼續(xù)看getWindow()

    /**
     * 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;
    }
      final void attach(...這里省略代碼) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        ...這里省略代碼
    }

從上面的源碼可以看出setContentView里面的是getWindow()其實(shí)是PhoneWindow啃奴,這也正如前面所說的潭陪,每個activity都對應(yīng)一個窗口window,這個窗口是PhoneWindow的實(shí)例最蕾。

Activity-PhoneWindow.jpg

繼續(xù)依溯,進(jìn)入PhoneWindow里面看看:

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

PhoneWindow里面有一個DecorView對象mDecor,再看看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);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

首次進(jìn)來mContentParent應(yīng)該是null瘟则,進(jìn)入installDecor()看看:

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

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

在installDecor里黎炉,mDecor=generateDecor(-1),再看看這個方法里是怎么生成DecorView的:

    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醋拧。

看到這里慷嗜,PhoneWindow和DecorView的關(guān)系就一目了然了

PhoneWindow-DecorView.jpg

那問題來了,DecorView是如何跟ActionBar和ContentParent關(guān)聯(lián)起來的呢丹壕?

繼續(xù)回頭看源碼的installDecor()方法:

    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)mContentParent是通過generateLayout(mDecor)生成的庆械,那看看generateLayout方法:

    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        TypedArray a = getWindowStyle();

        ...此處省略代碼

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            ...此處省略代碼
        } 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);
        ...此處省略代碼

        return 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;

generateLayout方法里面根據(jù)不同的配置初始化的代碼特別多,我省略了一些其他代碼

generateLayout方法里面雀费,mDecor加載了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"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

可以看到干奢,R.layout.screen_simple是一個垂直的線性布局,上面的ViewStub就是APP的appBar盏袄,下面的FrameLayout的id為content忿峻!,activity所加載的xml頁面就是加載到這個布局里面

再看看mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)這個方法

    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ...此處省略代碼

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

從上面的方法來看辕羽,root這個View所代表的的就是 R.layout.screen_simple逛尚,然后DecorView調(diào)用addView將root加載到DecorView里面

奇怪了,只看到ContentParent初始化刁愿,沒看到ActionBar初始化按履?

再回頭看看Activity最開始的源碼:

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

原來ActionBar是在這里初始化的,看看initWindowDecorActionBar():

      /**
     * Creates a new ActionBar, locates the inflated ActionBarView,
     * initializes the ActionBar with the view, and sets mActionBar.
     */
    private void initWindowDecorActionBar() {
        Window window = getWindow();

        // Initializing the window decor can change window feature flags.
        // Make sure that we have the correct set before performing the test below.
        window.getDecorView();

        if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
            return;
        }

        mActionBar = new WindowDecorActionBar(this);
        mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

        mWindow.setDefaultIcon(mActivityInfo.getIconResource());
        mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
    }
    @RestrictTo({Scope.LIBRARY_GROUP})
    public WindowDecorActionBar(View layout) {
        assert layout.isInEditMode();

        this.init(layout);
    }
      private void init(View decor) {
        this.mOverlayLayout = (ActionBarOverlayLayout)decor.findViewById(id.decor_content_parent);
        if (this.mOverlayLayout != null) {
            this.mOverlayLayout.setActionBarVisibilityCallback(this);
        }

        this.mDecorToolbar = this.getDecorToolbar(decor.findViewById(id.action_bar));
        this.mContextView = (ActionBarContextView)decor.findViewById(id.action_context_bar);
        this.mContainerView = (ActionBarContainer)decor.findViewById(id.action_bar_container);
        if (this.mDecorToolbar != null && this.mContextView != null && this.mContainerView != null) {
            this.mContext = this.mDecorToolbar.getContext();
            int current = this.mDecorToolbar.getDisplayOptions();
            boolean homeAsUp = (current & 4) != 0;
            if (homeAsUp) {
                this.mDisplayHomeAsUpSet = true;
            }

            ActionBarPolicy abp = ActionBarPolicy.get(this.mContext);
            this.setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);
            this.setHasEmbeddedTabs(abp.hasEmbeddedTabs());
            TypedArray a = this.mContext.obtainStyledAttributes((AttributeSet)null, styleable.ActionBar, attr.actionBarStyle, 0);
            if (a.getBoolean(styleable.ActionBar_hideOnContentScroll, false)) {
                this.setHideOnContentScrollEnabled(true);
            }

            int elevation = a.getDimensionPixelSize(styleable.ActionBar_elevation, 0);
            if (elevation != 0) {
                this.setElevation((float)elevation);
            }

            a.recycle();
        } else {
            throw new IllegalStateException(this.getClass().getSimpleName() + " can only be used " + "with a compatible window decor layout");
        }
    }

ActionBar和ContentParent并非是添加到DecorView上去的滤钱,而是本身就存在于DecorView觉壶,

對于有ActionBar的activity,DecorView的默認(rèn)布局是screen_action_bar.xml件缸,里面就會包含ActionBar和ContentParent
對于沒有ActionBar的Activity铜靶,會根據(jù)Activity所帶的參數(shù)選擇decorView的默認(rèn)布局,例如screen_simple.xml

選擇DecorView的默認(rèn)布局的相關(guān)的判斷邏輯是installDecor方法中調(diào)用generateLayout完成的.

看到這里他炊,DecorView和ContentParent争剿、ActionBar的關(guān)系就一目了然了

DecorView-ContentParent-ActionBar.jpg

1.3 DecorView建立與viewRootImpl的聯(lián)系

在ActivityThread的handleLaunchActivity中啟動Activity,當(dāng)onCreate()方法執(zhí)行完畢痊末,上面所述的DecorView創(chuàng)建動作也完畢了蚕苇,在handleLaunchActivity方法里會繼續(xù)調(diào)用到ActivityThread的handleResumeActivity方法,看看這個方法的源碼:

  @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ...

        // TODO Push resumeArgs into the activity for consideration
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        ...

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

            ...
    }

在handleResumeActivity方法中凿叠,獲取該activity所關(guān)聯(lián)的window對象涩笤,DecorView對象,以及windowManager對象幔嫂,而WindowManager是抽象類辆它,它的實(shí)現(xiàn)類是WindowManagerImpl,所以后面調(diào)用的是WindowManagerImpl的addView方法履恩,看看源碼:

  public final class WindowManagerImpl implements WindowManager {    
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
}

mGlobal則是WindowManagerGlobal的一個實(shí)例锰茉,那么我們接著看WindowManagerGlobal的addView方法:

  public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...

            root = new ViewRootImpl(view.getContext(), display); // 1

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView); // 2
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

在上面的方法中,實(shí)例化了ViewRootImpl類切心,然后調(diào)用ViewRootImpl#setView方法飒筑,并把DecorView作為參數(shù)傳遞進(jìn)去,在這個方法內(nèi)部绽昏,會通過跨進(jìn)程的方式向WMS(WindowManagerService)發(fā)起一個調(diào)用协屡,從而將DecorView最終添加到Window上,在這個過程中全谤,ViewRootImpl肤晓、DecorView和WMS會彼此關(guān)聯(lián),最后通過WMS調(diào)用ViewRootImpl#performTraverals方法開始View的測量认然、布局补憾、繪制流程。(參考:Android View源碼解讀:淺談DecorView與ViewRootImpl

DecorView的相關(guān)知識就記錄到這卷员,下面開始view的繪制流程盈匾。

2. View的繪制流程

view繪制流程放在下一篇文章(http://www.reibang.com/p/68afe6c8501b

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市毕骡,隨后出現(xiàn)的幾起案子削饵,更是在濱河造成了極大的恐慌岩瘦,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窿撬,死亡現(xiàn)場離奇詭異启昧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)劈伴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門箫津,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宰啦,你說我怎么就攤上這事”模” “怎么了赡模?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長师抄。 經(jīng)常有香客問我漓柑,道長,這世上最難降的妖魔是什么叨吮? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任辆布,我火速辦了婚禮,結(jié)果婚禮上茶鉴,老公的妹妹穿的比我還像新娘锋玲。我一直安慰自己,他們只是感情好涵叮,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布惭蹂。 她就那樣靜靜地躺著,像睡著了一般割粮。 火紅的嫁衣襯著肌膚如雪盾碗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天舀瓢,我揣著相機(jī)與錄音廷雅,去河邊找鬼。 笑死京髓,一個胖子當(dāng)著我的面吹牛航缀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播朵锣,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼谬盐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了诚些?” 一聲冷哼從身側(cè)響起飞傀,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤皇型,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后砸烦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弃鸦,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年幢痘,在試婚紗的時候發(fā)現(xiàn)自己被綠了唬格。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡颜说,死狀恐怖购岗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情门粪,我是刑警寧澤喊积,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站玄妈,受9級特大地震影響乾吻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拟蜻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一绎签、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酝锅,春花似錦诡必、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至阁谆,卻和暖如春碳抄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背场绿。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工剖效, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人焰盗。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓璧尸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親熬拒。 傳聞我的和親對象是個殘疾皇子爷光,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

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