Android Window 啟動過程

不管是 Activity 還是 Dialog 都有一個 window 對象颖御,對應(yīng)的 view 屬于 window汤求,用戶對于界面最直接的感知就是 window议慰,理解和掌握 window 的啟動過程對于開發(fā)者而言尤為重要速址,以下主要分析 Activity 的 window 啟動過程龄糊。

注逆粹,以下代碼都是基于 api=26。

我們從 Activity.attach 方法開始講炫惩,在這個方法里僻弹,完成 window 實(shí)例的創(chuàng)建。(該方法在什么時(shí)候調(diào)用的他嚷,在這里不分析蹋绽,有興趣的朋友可以看另一篇文章: http://www.reibang.com/p/7a904184afc6

final void attach(Context context, ActivityThread aThread,... {
...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
...
    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());
    }
    mWindowManager = mWindow.getWindowManager();
...
}

我們可以看到 window 的實(shí)例其實(shí)就是 PhoneWindow,還有一點(diǎn)值得注意的是 mWindowManager 的賦值筋蓖,這里的 context 實(shí)際是 ContextImpl卸耘,通過 context.getSystemService(Context.WINDOW_SERVICE) 獲取到的實(shí)際是 WindowManagerImpl,是 app 進(jìn)程的一個實(shí)例扭勉,不屬于系統(tǒng)進(jìn)程鹊奖,我們可以隨便看一下該類的方法

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

WindowManagerImpl 只是一個代理類,實(shí)際操作都是由 WindowManagerGlobal 來完成涂炎,后續(xù)所有 window 相關(guān)操作都與這個類有關(guān)忠聚,我們先繼續(xù)往下走。

Activity.attch 我們直接跳到 Activity.setContentView 方法

    // Activity.java
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

Activity.setContentView 直接調(diào)用的是 window 的方法

// PhoneWindow
public void setContentView(int layoutResID) {
...
installDecor();
...
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}

installDecor() 方法主要完成 DecorView 的創(chuàng)建唱捣、為 DecorView 添加 ContentView两蟀、設(shè)置背景、從style中提取對應(yīng)的屬性...

// PhoneWindow
private void installDecor() {
...
    mDecor = generateDecor(-1);
...
}

protected ViewGroup generateLayout(DecorView decor) {
...
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); // 在這里添加的 layout
...
}

// DecorView
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
    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));
    }
...
}

這里我們看到代碼中多次使用 LayoutInflater.inflate 和 ViewGroup.addView 方法震缭,前者最終也是調(diào)用 ViewGroup.addView赂毯,我們來看一下 ViewGroup.addView 方法主要干了些什么

// ViewGroup
public void addView(View child, int index, LayoutParams params) {
...
    requestLayout(); // 核心方法
    invalidate(true);
    addViewInner(child, index, params, false); // 完成添加 view 到數(shù)據(jù)結(jié)構(gòu)等操作...
...
}

主要看下 ViewGroup.requestLayout

// ViewGroup
public void requestLayout() {
...
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
        // Only trigger request-during-layout logic if this is the view requesting it,
        // not the views in its parent hierarchy
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null && viewRoot.isInLayout()) {
            if (!viewRoot.requestLayoutDuringLayout(this)) {
                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }

    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
...
}

可以看出 requestLayout 操作機(jī)制是逐步往上級請求,直到能受理該操作的實(shí)例拣宰,只有 DecorView 才有 ViewRootImpl党涕,所以實(shí)際上最終是有 ViewRootImpl 來完成 requestLayout操作的,但是這里需要注意此時(shí)的 DecorView mAttachInfo 和 mParent 都等于 null巡社,所以實(shí)際上沒有進(jìn)行任何操作膛堤。

下一個與 Window 相關(guān)的邏輯在 ActivityThread.handleResumeActivity,注意此時(shí)已經(jīng)跑過了 Activity.onCreate

// ActivityThread
final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
r = performResumeActivity(token, clearHide, reason);  // 這句話會回調(diào) Activity.onResume
...
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
...
}

wm 即是 WindowManagerImpl晌该,上文已經(jīng)說過了 WindowManagerImpl 操作都交由 WindowManagerGlobal肥荔,所以我們直接看 WindowManagerGlobal.addView

// WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
...
            root = new ViewRootImpl(view.getContext(), display); // 創(chuàng)建 ViewRootImpl 實(shí)例,每個 window 只有一個 ViewRootImpl
            view.setLayoutParams(wparams);

            // 將 view 添加到對應(yīng)的集合中朝群,記錄
            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);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
...
}

ViewRootImpl 構(gòu)造方法也值得一看

public ViewRootImpl(Context context, Display display) {
...
mWindowSession = WindowManagerGlobal.getWindowSession(); // 獲取的是系統(tǒng)進(jìn)程的遠(yuǎn)程對象燕耿,通過它與 WindowManagerService 連接
...
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);
...
}

WindowManagerGlobal.addView 主要完成了 ViewRootImpl 創(chuàng)建,DecorView 與 ViewRootImpl 建立關(guān)系是在 ViewRootImpl.setView 方法中完成

// ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
    requestLayout();
...
            // 通過 mWindowSession 對象最終會調(diào)用 WindowManagerService.addView
            res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(),
                    mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                    mAttachInfo.mOutsets, mInputChannel);
...
    view.assignParent(this); // 將 DecorView mParent = this
}

我們先看下 ViewRootImpl.requestLayout()

// ViewRootImpl
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            // 最終會啟動 TraversalRunnable -> run -> doTraversal() - >performTraversals()
            scheduleTraversals(); 
        }
    }

private void performTraversals() {
...
    if (mFirst) {
        ...
        // 通過 ViewGroup.dispatchAttachedToWindow 最終響應(yīng)每個子 View 的 dispatchAttachedToWindow 方法姜胖,響應(yīng)每個監(jiān)聽 listener.onViewAttachedToWindow
        host.dispatchAttachedToWindow(mAttachInfo, 0);
        ...
    }
...
}

我們可以看到在 performTraversals 方法中觸發(fā) dispatchAttachedToWindow 事件誉帅,到這一步已經(jīng)完成了 window 的添加。

總結(jié)

一個 window 對應(yīng)一個 ViewRootImpl右莱,一個 ViewRootImpl 對應(yīng) 一個 DecorView堵第,與 wms 發(fā)生關(guān)聯(lián)操作是由 ViewRootImpl 完成的,requestLayout 是由 ViewRootImpl 完成隧出,dispatchAttachedToWindow 也是由 ViewRootImpl 觸發(fā)踏志,依次往下走,用 Context 獲取的 WindowManager 調(diào)用 addView 并不是直接調(diào)用 wms.addView 方法胀瞪,而是存在于 app 進(jìn)程的 WindowMnagerGlobal.addView()方法针余。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市凄诞,隨后出現(xiàn)的幾起案子圆雁,更是在濱河造成了極大的恐慌,老刑警劉巖帆谍,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伪朽,死亡現(xiàn)場離奇詭異,居然都是意外死亡汛蝙,警方通過查閱死者的電腦和手機(jī)烈涮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門朴肺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人坚洽,你說我怎么就攤上這事戈稿。” “怎么了讶舰?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵鞍盗,是天一觀的道長。 經(jīng)常有香客問我跳昼,道長般甲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任鹅颊,我火速辦了婚禮敷存,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘挪略。我一直安慰自己历帚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布杠娱。 她就那樣靜靜地躺著挽牢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪摊求。 梳的紋絲不亂的頭發(fā)上禽拔,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機(jī)與錄音室叉,去河邊找鬼睹栖。 笑死,一個胖子當(dāng)著我的面吹牛茧痕,可吹牛的內(nèi)容都是我干的野来。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼踪旷,長吁一口氣:“原來是場噩夢啊……” “哼曼氛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起令野,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤舀患,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后气破,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聊浅,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了低匙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旷痕。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖努咐,靈堂內(nèi)的尸體忽然破棺而出苦蒿,到底是詐尸還是另有隱情殴胧,我是刑警寧澤渗稍,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站团滥,受9級特大地震影響竿屹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜灸姊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一拱燃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧力惯,春花似錦碗誉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至甲喝,卻和暖如春尝苇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背埠胖。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工糠溜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人直撤。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓非竿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谋竖。 傳聞我的和親對象是個殘疾皇子红柱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

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