Android自定義View開篇:View繪制時(shí)機(jī)

前言

Android 中 Activity 是作為應(yīng)用程序的載體存在,代表著一個(gè)完整的用戶界面泼各,提供了一個(gè)窗口來繪制各種視圖阶牍,當(dāng) Activity 啟動(dòng)時(shí),我們會(huì)通過 setContentView 方法來設(shè)置一個(gè)內(nèi)容視圖偏灿,這個(gè)內(nèi)容視圖就是用戶看到的界面丹诀。那么 View 和 activity 是如何關(guān)聯(lián)在一起的呢 ?

上圖是 View 和 Activity 之間的關(guān)系翁垂。先解釋圖中一些類的作用以及相關(guān)關(guān)系:

  • Window:每個(gè)Activity都會(huì)創(chuàng)建一個(gè)Window用于承載View視圖的顯示铆遭,Window是一個(gè)抽象類,存在一個(gè)唯一實(shí)現(xiàn)類PhoneWindow沿猜。
  • PhoneWindow:該類繼承于Window類枚荣,是Window類的具體實(shí)現(xiàn),我們可以通過該類去繪制窗口啼肩。并且橄妆,該類內(nèi)部包含了一個(gè) DecorView 對象,該 DectorView 對象是所有應(yīng)用窗口的根 View祈坠。
  • DecorView:最頂層的View呼畸,是一個(gè)FrameLayout子類,最終會(huì)被加載到Window當(dāng)中颁虐,它內(nèi)部只有一個(gè)垂直方向的LinearLayout分為兩部分:一個(gè)是TitleBar(ActionBar 的容器)蛮原,另一個(gè)是ContentView(Activity對應(yīng)的XML布局,通過setContentView設(shè)置到DecorView中)另绩。
  • WindowManager : 是一個(gè)接口儒陨,里面常用的方法有:添加View花嘶,更新View和刪除View。主要是用來管理 Window 的蹦漠。WindowManager 具體的實(shí)現(xiàn)類是WindowManagerImpl椭员。最終,WindowManagerImpl 會(huì)將業(yè)務(wù)交給 WindowManagerGlobal 來處理笛园。
  • WindowManagerService (WMS) : 負(fù)責(zé)管理各 app 窗口的創(chuàng)建隘击,更新,刪除研铆, 顯示順序埋同。運(yùn)行在 system_server 進(jìn)程。
  • ViewRootImpl :擁有 DecorView 的實(shí)例棵红,通過該實(shí)例來控制 DecorView 凶赁,最終通過執(zhí)行ViewRootImpl的performTraversals()開啟整個(gè)View樹的繪制。ViewRootImpl 的一個(gè)內(nèi)部類 W逆甜,實(shí)現(xiàn)了 IWindow 接口虱肄,IWindow 接口是供 WMS 使用的,WSM 通過調(diào)用 IWindow 一些方法交煞,通過 Binder 通信的方式咏窿,最后執(zhí)行到了 W 中對應(yīng)的方法中。同樣的素征,ViewRootImpl 通過 IWindowSession 來調(diào)用 WMS 的 Session 一些方法翰灾。Session 類繼承自 IWindowSession.Stub,每一個(gè)應(yīng)用進(jìn)程都有一個(gè)唯一的 Session 對象與 WMS 通信稚茅。

DecorView 的創(chuàng)建

首先來看一下Activity中setContentView源碼:

    public void setContentView(@LayoutRes int layoutResID) {
        //將xml布局傳遞到Window當(dāng)中
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

從代碼可以看出纸淮,Activity的setContentView實(shí)質(zhì)是將View傳遞到Window的setContentView()方法中,Window的setContenView會(huì)在內(nèi)部調(diào)用installDecor()方法創(chuàng)建DecorView亚享,看一下它的部分源碼:

 public void setContentView(int layoutResID) { 
        if (mContentParent == null) {
            //初始化DecorView以及其內(nèi)部的content
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ...............
        } else {
            //將contentView加載到DecorVoew當(dāng)中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...............
    }

  private void installDecor() {
        ...............
        if (mDecor == null) {
            //實(shí)例化DecorView
            mDecor = generateDecor(-1);
            ...............
            }
        } else {
            mDecor.setWindow(this);
        }
       if (mContentParent == null) {
            //獲取Content
            mContentParent = generateLayout(mDecor);
       }  
        ...............
 }

 protected DecorView generateDecor(int featureId) {
        ...............
        return new DecorView(context, featureId, this, getAttributes());
 }

通過generateDecor()new一個(gè)DecorView咽块,然后調(diào)用generateLayout()獲取DecorView中content,最終通過inflate將Activity視圖添加到DecorView中的content中欺税,到此侈沪,DecorView 的創(chuàng)建就講完了⊥碓洌可是我們似乎并沒有看到 DecorView 是被添加的亭罪,什么時(shí)候?qū)τ脩艨梢姷摹?/p>

WindowManager

View 創(chuàng)建完以后,那 Decorview 是怎么添加到屏幕中去的呢歼秽?當(dāng)然是 WindowManager 呢应役,那么是如何將 View 傳到 WindowManager 中呢。

public interface WindowManager extends ViewManager {
    public static class BadTokenException extends RuntimeException{...}
    public static class InvalidDisplayException extends RuntimeException{...}
    public Display getDefaultDisplay();
    public void removeViewImmediate(View view);
    public static class LayoutParams extends ViewGroup.LayoutParams
        implements Parcelable

ViewManager接口定義了一組規(guī)則箩祥,也就是add院崇、update、remove的操作View接口袍祖。也就是說ViewManager是用來添加和移除activity中View的接口底瓣,可以通過Context.getSystemService()獲取實(shí)例。

WindowManager是一個(gè)接口蕉陋,而且它繼承與ViewManager捐凭。WindowManager字面理解就是窗口管理器,每一個(gè)窗口管理器都與一個(gè)的窗口顯示綁定凳鬓。獲取實(shí)例可以通過
Context.getSystemService(Context.WINDOW_SERVICE)獲取茁肠。既然繼承了ViewManager,那么它也就可以進(jìn)行添加刪除View的操作了村视,不過它的操作放在它的實(shí)現(xiàn)類WindowManagerImpl里面。

  • BadTokenException:則是addView時(shí)它的LayoutParams無效則會(huì)被拋出酒奶,或是添加第二個(gè)View的時(shí)候沒有移除第一個(gè)View則會(huì)被拋出
  • InvalidDisplayException:如果一個(gè)窗口是在一個(gè)二級的顯示上而指定的顯示找不到則會(huì)被拋出
  • getDefaultDisplay:返回當(dāng)前WindowManager管理的顯示Display
  • removeViewImmediate:表示從窗口上移除View蚁孔,一般是當(dāng)View調(diào)用了onDetachedFromWindow也就是從Window上分開后,把它移除惋嚎。
  • LayoutParams:靜態(tài)內(nèi)部類杠氢。顯然是Window的布局參數(shù),里面定義了一系列的窗口屬性另伍。
WindowManagerImpl
public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;
    private final Window mParentWindow;
     @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    ...
     @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
}

可以看到WindowManagerImpl里面有一個(gè)成員變量WindowManagerGlobal鼻百,而真正的實(shí)現(xiàn)則是在WindowManagerGlobal了,類似代理摆尝,只不過WindowManagerGlobal是個(gè)沒有實(shí)現(xiàn)WindowManager的類的温艇,自己定義了套實(shí)現(xiàn)。

public final class WindowManagerGlobal {
    private static final String TAG = "WindowManager";
     public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
            ...
     }
}

ViewRootImpl

實(shí)際上堕汞,View 的繪制是由 ViewRootImpl 來負(fù)責(zé)的勺爱。每個(gè)應(yīng)用程序窗口的 DecorView 都有一個(gè)與之關(guān)聯(lián)的 ViewRootImpl 對象,這種關(guān)聯(lián)關(guān)系是由 WindowManager 來維護(hù)的讯检。

先看 ViewRootImpl 的 setView 方法琐鲁,該方法很長,我們將一些不重要的點(diǎn)注釋掉:

/**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ......
               
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.

                requestLayout();
                ......
            }
        }
    }

這里先將 mView 保存了 DecorView 的實(shí)例人灼,然后調(diào)用 requestLayout() 方法围段,以完成應(yīng)用程序用戶界面的初次布局。

public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

因?yàn)槭?UI 繪制投放,所以一定要確保是在主線程進(jìn)行的奈泪,checkThread 主要是做一個(gè)校驗(yàn)。接著調(diào)用 scheduleTraversals 開始計(jì)劃繪制了。

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

最終調(diào)用其 run 方法:

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

該方法內(nèi)部最終會(huì)調(diào)用 performTraversals 進(jìn)行繪制段磨。

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

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

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

到此取逾,DecorView 與 activity 之間的綁定關(guān)系就講完了。后面代碼比較多苹支,總結(jié)一下就是:
ViewRootImpl的作用是用來銜接WindowManager和DecorView砾隅,在Activity被創(chuàng)建后會(huì)通過WindowManager將DecorView添加到PhoneWindow中并且創(chuàng)建ViewRootImpl實(shí)例,隨后將DecorView與ViewRootImpl進(jìn)行關(guān)聯(lián)债蜜,最終通過執(zhí)行ViewRootImpl的performTraversals()開啟整個(gè)View樹的繪制晴埂。

下一章,將會(huì)介紹 performTraversals 所做的事情寻定,也就是 View 繪制流程儒洛。
Android自定義View中篇:View繪制流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市狼速,隨后出現(xiàn)的幾起案子琅锻,更是在濱河造成了極大的恐慌,老刑警劉巖向胡,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恼蓬,死亡現(xiàn)場離奇詭異,居然都是意外死亡僵芹,警方通過查閱死者的電腦和手機(jī)处硬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拇派,“玉大人荷辕,你說我怎么就攤上這事〖悖” “怎么了疮方?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長茧彤。 經(jīng)常有香客問我案站,道長,這世上最難降的妖魔是什么棘街? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任蟆盐,我火速辦了婚禮,結(jié)果婚禮上遭殉,老公的妹妹穿的比我還像新娘石挂。我一直安慰自己,他們只是感情好险污,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布痹愚。 她就那樣靜靜地躺著富岳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拯腮。 梳的紋絲不亂的頭發(fā)上窖式,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音动壤,去河邊找鬼萝喘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛琼懊,可吹牛的內(nèi)容都是我干的阁簸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼哼丈,長吁一口氣:“原來是場噩夢啊……” “哼启妹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起醉旦,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤饶米,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后车胡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體檬输,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年吨拍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了褪猛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片网杆。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡羹饰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碳却,到底是詐尸還是另有隱情队秩,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布昼浦,位于F島的核電站馍资,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏关噪。R本人自食惡果不足惜鸟蟹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望使兔。 院中可真熱鬧建钥,春花似錦、人聲如沸虐沥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至镐依,卻和暖如春匹涮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背槐壳。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工然低, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宏粤。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓脚翘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绍哎。 傳聞我的和親對象是個(gè)殘疾皇子来农,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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