Android圖形系統(tǒng)(一)-Window加載視圖過程

本篇開始進行了新的專題:繪制優(yōu)化,初步打算分兩部分來寫腻异,一部分是原理機制篇,做優(yōu)化,你原理機制都不清楚談何優(yōu)化次乓,所以知識儲備是十分有必要的,另外一部分就是優(yōu)化實踐篇挠进。(先是這么計劃著边锁,之后看是否會再調整結構)

那么本篇文章就開啟原理機制部分的總結,打算先捋一下window的加載視圖過程纱耻。

話不多說芭梯,進入主題。

一弄喘、了解整體視圖關系

Activity: 本身不是窗口玖喘,也不是視圖,它只是窗口的載體蘑志,一方面是key芒涡、touch事件的響應處理,一方面是對界面生命周期做統(tǒng)一調度

Window: 一個頂級窗口查看和行為的一個抽象基類卖漫。它也是裝載View的原始容器费尽, 處理一些應用窗口通用的邏輯。使用時用到的是它的唯一實現(xiàn)類:PhoneWindow羊始。Window受WindowManager統(tǒng)一管理旱幼。

DecorView: 頂級視圖,一般情況下它內部會包含一個豎直方向的LinearLayout突委,上面的標題欄(titleBar)柏卤,下面是內容欄。通常我們在Activity中通過setContentView所設置的布局文件就是被加載到id為android.R.id.content的內容欄里(FrameLayout)匀油。

二缘缚、window的類型

添加窗口是通過WindowManagerGlobal的addView方法操作的,這里有三個必要參數(shù):view敌蚜,params桥滨,display。

display : 表示要輸出的顯示設備。

view : 表示要顯示的View齐媒,一般是對該view的上下文進行操作蒲每。(view.getContext())

params : 類型為WindowManager.LayoutParams,即表示該View要展示在窗口上的布局參數(shù)喻括。有2個比較重要的參數(shù):flags,type邀杏。

-flags:表示W(wǎng)indow的屬性:

flags 意義
FLAG_NOT_FOCUSABLE 表示W(wǎng)indow不需要獲取焦點,也不需要接收各種輸入事件唬血,此標記會同時啟用FLAG_NOT_TOUCH_MODAL望蜡,最終事件會直接傳遞給下層具有焦點的Window。
FLAG_NOT_TOUCH_MODAL 系統(tǒng)會將當前Window區(qū)域以外的單擊事件傳遞給底層的Window拷恨,當前Window區(qū)域以內的單擊事件則自己處理泣特,這個標記很重要,一般來說都需要開啟此標記挑随,否則其他Window將無法接收到單擊事件状您。
FLAG_SHOW_WHEN_LOCKED 開啟此模式可以讓Window顯示在鎖屏的界面上。

-type: 表示窗口的類型(具體值太多了就不一一列舉了兜挨,具體可以去WindowManager的LayoutParams看詳細類型描述):

window類型 特點 層級范圍 典型window代表
system window 獨立存在 2000~2999 Toast 膏孟、警告提示window、鍵盤window等
application window 屬于應用的窗口 1~99 對應的就是activity的window
sub window 需要依附于父window拌汇,不能獨立存在 1000~1999 Dialog 柒桑、 popupWindow

這個type層級到底有什么作用:
Window是分層的,每個Window都有對應的z-ordered噪舀,(z軸魁淳,從1層層疊加到2999,你可以將屏幕想成三維坐標模式)層級大的會覆蓋在層級小的Window上面与倡。如果想要Window位于所有Window的最頂層界逛,那么采用較大的層級即可。另外有些系統(tǒng)層級的使用是需要聲明權限的纺座。

那么照這么說息拜,最底層的應該是應用window,子window在其上净响,系統(tǒng)window在最上面少欺,這也符合視圖展示的預期效果。

另外WindowManager的LayoutParams中還有個token必須要提一嘴:主要作用是為了維護activity和window的對應關系馋贤。

三赞别、window的創(chuàng)建

講window的創(chuàng)建過程,那么肯定得了解activity的啟動流程配乓,但是在這里不詳細說activitiy的啟動流程了仿滔,因為后面會有計劃單獨拎出四大組件開篇章來講解啟動流程惠毁。

那么簡單的先上個圖了解下activity的啟動流程(借用輝輝的圖):

對于window的創(chuàng)建,我們就從handleLaunchActivity開始堤撵,開始看源碼吧:

//ActivityThread
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ``````
    //獲取WindowManagerService的Binder引用(proxy端)仁讨。
    WindowManagerGlobal.initialize();
    //創(chuàng)建activity,調用attach方法羽莺,然后調用Activity的onCreate,onStart,onResotreInstanceState方法
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        ``````
        //會調用Activity的onResume方法.
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
        ``````
    }
}

主要看 Activity a = performLaunchActivity(r, customIntent);方法实昨,關注Activity的attach方法:

inal 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) {
        //綁定上下文
        attachBaseContext(context);
        //創(chuàng)建Window, PhoneWindow是Window的唯一具體實現(xiàn)類
        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        ``````
        //設置WindowManager
        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());
        }
        //創(chuàng)建完后通過getWindowManager就可以得到WindowManager實例
        mWindowManager = mWindow.getWindowManager();//其實它是WindowManagerImpl
    }

這里創(chuàng)建了一個PhoneWindow對象,并且實現(xiàn)了Window的Callback接口盐固,這樣activity就和window關聯(lián)在了一起荒给,并且通過callback能夠接受key和touch事件。

此外刁卜,初始化且設置windowManager志电。每個 Activity 會有一個 WindowManager 對象,這個 mWindowManager 就是和 WindowManagerService 進行通信蛔趴,也是 WindowManagerService 識別 View 具體屬于那個 Activity 的關鍵挑辆,創(chuàng)建時傳入 IBinder 類型的 mToken。

mWindow.setWindowManager(..., mToken, ..., ...)

我們從window的setWindowManager方法出發(fā)孝情,很容易找到WindowManager這個接口的具體的實現(xiàn)是WindowManagerImpl鱼蝉。

四、window添加view過程

我們前面知道PhoneWindow對View來說更多是扮演容器的角色箫荡,而真正完成把一個 View魁亦,作為窗口添加到 WMS 的過程是由 WindowManager 來完成的。而且從上面創(chuàng)建過程我們知道了WindowManager 的具體實現(xiàn)是 WindowManagerImpl羔挡。

那么我們繼續(xù)來跟代碼:

從上面handleLaunchActivity的代碼中performLaunchActivity后面洁奈,有個handleResumeActivity,從名字也能看出绞灼,跟activity onResume相關利术。進去看看:

//ActivityThread
final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        //把activity數(shù)據(jù)記錄更新到ActivityClientRecord
        ActivityClientRecord r = mActivities.get(token);
        r = performResumeActivity(token, clearHide, reason);
        if (r != null) {
            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;
             ``````
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);//把decor添加到窗口上(劃重點)
                }
            }
                //屏幕參數(shù)發(fā)生了改變
                performConfigurationChanged(r.activity, r.tmpConfig);
                WindowManager.LayoutParams l = r.window.getAttributes();
                    if (r.activity.mVisibleFromClient) {
                        ViewManager wm = a.getWindowManager();
                        View decor = r.window.getDecorView();
                        wm.updateViewLayout(decor, l);//更新窗口狀態(tài)
                    }
                ``````
                if (r.activity.mVisibleFromClient) {
                    //已經成功添加到窗口上了(繪制和事件接收),設置為可見
                    r.activity.makeVisible();
                }
            //通知ActivityManagerService低矮,Activity完成Resumed
             ActivityManagerNative.getDefault().activityResumed(token);
        }
    }

我們注意到這么幾行代碼:

            if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);/
                }

wm是activity getWindowManager()獲取的氯哮,那不就是WindowManagerImpl的addView方法嗎,追商佛!

//WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

我們看到了又代理了一層:mGlobal喉钢, 它是誰? 如果有印象的會記得講window類型的時候帶了一嘴的WindowManagerGlobal 良姆,那這個WindowManagerImpl原來也是一個吃空餉的家伙肠虽!對于Window(或者可以說是View)的操作都是交由WindowManagerGlobal來處理,WindowManagerGlobal以工廠的形式向外提供自己的實例玛追。這種工作模式是橋接模式税课,將所有的操作全部委托給WindowManagerGlobal來實現(xiàn)闲延。

在WindowManagerImpl的全局變量中通過單例模式初始化了WindowManagerGlobal,也就是說一個進程就只有一個WindowManagerGlobal對象韩玩。那看看它:

//WindowManagerGlobal
   public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            //調整布局參數(shù)垒玲,并設置token
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    //如果待刪除的view中有當前view,刪除它
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                }
                // The previous removeView() had not completed executing. Now it has.
               //之前移除View并沒有完成刪除操作找颓,現(xiàn)在正式刪除該view
            }

            //如果這是一個子窗口個(popupWindow)合愈,找到它的父窗口。
            //最本質的作用是使用父窗口的token(viewRootImpl的W類击狮,也就是IWindow)
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                    //在源碼中token一般代表的是Binder對象佛析,作用于IPC進程間數(shù)據(jù)通訊。并且它也包含著此次通訊所需要的信息彪蓬,
                    //在ViewRootImpl里寸莫,token用來表示mWindow(W類,即IWindow)档冬,并且在WmS中只有符合要求的token才能讓
                    //Window正常顯示.
                        panelParentView = mViews.get(i);
                    }
                }
            }
            //創(chuàng)建ViewRootImpl膘茎,并且將view與之綁定
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);//將當前view添加到mViews集合中,mViews存儲所有Window對應的View
            mRoots.add(root);//將當前ViewRootImpl添加到mRoots集合中酷誓,mRoots存儲所有Window對應的ViewRootImpl
            mParams.add(wparams);//將當前window的params添加到mParams集合中披坏,存儲所有Window對應的布局參數(shù)
        }
          ``````
            //通過ViewRootImpl的setView方法,完成view的繪制流程呛牲,并添加到window上刮萌。
            root.setView(view, wparams, panelParentView);
    }

最最重要的是:root.setView(view, wparams, panelParentView); 一方面觸發(fā)繪制流程,一方面把view添加到window上娘扩。

講setView之前先普及下WindowManager與WindowManagerService binder IPC的兩個接口:

IWindowSession: 應用程序向WMS請求功能
實現(xiàn)類:Session
IWindow:WMS向客戶端反饋它想確認的信息
實現(xiàn)類:W

下面看看ViewRootImpl的setView:

//ViewRootImpl
  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                int res;
                 //在 Window add之前調用着茸,確保 UI 布局繪制完成 --> measure , layout , draw
                requestLayout();//View的繪制流程
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    //創(chuàng)建InputChannel
                    mInputChannel = new InputChannel();
                }
                try {
                    //通過WindowSession進行IPC調用,將View添加到Window上
                    //mWindow即W類琐旁,用來接收WmS信息
                    //同時通過InputChannel接收觸摸事件回調
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                }
                ``````
                    //處理觸摸事件回調
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                ``````
    }

在ViewRootImpl的setView()方法里涮阔,

1.執(zhí)行requestLayout()方法完成view的繪制流程(之后會講)
2.通過WindowSession將View和InputChannel添加到WmS中,從而將View添加到Window上并且接收觸摸事件灰殴。這是一次IPC 過程敬特。
那么接下來看看這個IPC過程

//ViewRootImpl的setView方法中:
mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
        getHostVisibility(), mDisplay.getDisplayId(),
        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
        mAttachInfo.mOutsets, mInputChannel);

mWindowSession:類型是interface IWindowSession

//WindowManagerGlobal
public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                IWindowManager windowManager = getWindowManagerService();
                sWindowSession = windowManager.openSession(
                        new IWindowSessionCallback.Stub() {
                            @Override
                            public void onAnimatorScaleChanged(float scale) {
                                ValueAnimator.setDurationScale(scale);
                            }
                        },
                        imm.getClient(), imm.getInputContext());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return sWindowSession;
    }
}

我們看到了getWindowManagerService(); 獲取了WMS , 那么再看下windowManager.openSession返回值就是sWindowSession

@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
        IInputContext inputContext) {
    if (client == null) throw new IllegalArgumentException("null client");
    if (inputContext == null) throw new IllegalArgumentException("null inputContext");
    Session session = new Session(this, callback, client, inputContext);
    return session;
}

IWindowSession的真正實現(xiàn)類是Session,他是一個Binder. 那么Session的addToDisplay:

@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
        Rect outOutsets, InputChannel outInputChannel) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
            outContentInsets, outStableInsets, outOutsets, outInputChannel);
}

從這知道了牺陶,最終是WMS執(zhí)行addWindow操作.

下面一張圖總結下:

圖出處:https://blog.csdn.net/freekiteyu/article/details/79408969

WMS執(zhí)行addWindow部分代碼有點多伟阔,本篇就不鋪開說了,不然篇幅就太長了掰伸,之后再說皱炉,看下如下的流程圖:

圖出處:http://www.reibang.com/p/effaff9ab9f2

總結起來:WMS中 addWindow流程就幾點:
1.通過type和 token對window進行分類和驗證,確保其有效性狮鸭。
2.構造WindowState與Window一一對應合搅。
3.通過token對window進行分組多搀。
4.對window分配層級。

那么到這里灾部,window添加view的過程就結束了康铭。

五、總結
下面用一張圖來總結下Activity赌髓、PhoneWindow从藤、 DecorView 、WindowManagerGlobal 春弥、ViewRootImpl 呛哟、Wms 以及WindowState之間的關系:

圖出處:https://blog.csdn.net/qian520ao/article/details/78555397?locationNum=7&fps=1

Activity在attach的時候叠荠,創(chuàng)建了一個PhoneWindow對象匿沛,并且實現(xiàn)了Window的Callback接口,這樣activity就和window綁定在了一起榛鼎,通過setContentView逃呼,創(chuàng)建DecorView,并解析好視圖樹加載到DecorView的contentView部分者娱,WindowManagerGlobal一個進程只有唯一一個抡笼,對當前進程內所有的視圖進行統(tǒng)一管理,其中包括ViewRootImpl黄鳍,它主要做兩件事情推姻,先觸發(fā)view繪制流程,再通過IPC 把view添加到window上框沟。

另外這是添加視圖的方法執(zhí)行時序圖:

圖出處:https://blog.csdn.net/qian520ao/article/details/78555397?locationNum=7&fps=1

至于Window的刪除和更新過程藏古,舉一反三,也是使用WindowManagerGlobal對ViewRootImpl的操作忍燥,最終也是通過Session的IPC跨進程通信通知到WmS拧晕。整個過程的本質都是同出一轍的。下一節(jié)接著講DecorView布局的加載流程梅垄。

參考:
https://blog.csdn.net/qian520ao/article/details/78555397?locationNum=7&fps=1
http://www.reibang.com/p/effaff9ab9f2
https://blog.csdn.net/freekiteyu/article/details/79408969

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
禁止轉載厂捞,如需轉載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末队丝,一起剝皮案震驚了整個濱河市靡馁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌机久,老刑警劉巖臭墨,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吞加,居然都是意外死亡裙犹,警方通過查閱死者的電腦和手機尽狠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叶圃,“玉大人袄膏,你說我怎么就攤上這事〔艄冢” “怎么了沉馆?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長德崭。 經常有香客問我斥黑,道長,這世上最難降的妖魔是什么眉厨? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任锌奴,我火速辦了婚禮,結果婚禮上憾股,老公的妹妹穿的比我還像新娘鹿蜀。我一直安慰自己,他們只是感情好服球,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布茴恰。 她就那樣靜靜地躺著,像睡著了一般斩熊。 火紅的嫁衣襯著肌膚如雪往枣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天粉渠,我揣著相機與錄音分冈,去河邊找鬼。 笑死渣叛,一個胖子當著我的面吹牛丈秩,可吹牛的內容都是我干的。 我是一名探鬼主播淳衙,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼蘑秽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了箫攀?” 一聲冷哼從身側響起肠牲,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎靴跛,沒想到半個月后缀雳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡梢睛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年肥印,在試婚紗的時候發(fā)現(xiàn)自己被綠了识椰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡深碱,死狀恐怖腹鹉,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情敷硅,我是刑警寧澤功咒,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站绞蹦,受9級特大地震影響力奋,放射性物質發(fā)生泄漏。R本人自食惡果不足惜幽七,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一景殷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锉走,春花似錦滨彻、人聲如沸藕届。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽休偶。三九已至梁厉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間踏兜,已是汗流浹背词顾。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碱妆,地道東北人肉盹。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像疹尾,于是被迫代替她去往敵國和親上忍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345