源碼解析:dialog, popupwindow, 和activity 的第一個view是怎麼來的?

來源:互聯網
上載者:User

標籤:源碼   hierarchy   控制項   介面   framework   

問題

在慢慢熟悉android 的過程中,發現一個view 或者layout的初始化,或者構造的流程還是比較清楚的,也就是加到父控制項中,然後就開始了對應的生命週期。但是整個介面的父控制項,或者說系統的第一個view, 是怎麼來的,如何初始化和繪製的呢?

概述

概述:帶著困擾我的問題,在前文的基礎上,繼續分析應用介面和framework的關係,通過分析viewrootimpl 的來源,並結合dialog, popupwindow, 和activity 的 根view的建立流程,回答了問題介面的根view 或者第一個view 是如何初始化,並加入到framework 中的。

分析viewrootimpl 的來源

本文分析是接上篇《源碼分析:LayoutParams的wrap_content, match_parent, 和具體值》,在上文中簡要分析了windowmanager中對介面的處理。

使用各種搜尋方法,可以看到,全部android代碼中只有一處引用ViewRootImpl 這個類,那就是

android.view.WindowManagerImpl.addView(View, LayoutParams, CompatibilityInfoHolder, boolean)

通過分析代碼可以看到,windowmanager在addview() 的過程中,為了管理添加進來的view,使用了三個數組

    private View[] mViews;    private ViewRootImpl[] mRoots;    private WindowManager.LayoutParams[] mParams;
在代碼處理中,通過view 的context 來構造一個對應的ViewRootImpl ,然後把view, rootViewImpl, 和layoutParams 三個變數,存到數組中。並在最後,setView。

            root = new ViewRootImpl(view.getContext());            root.mAddNesting = 1;            if (cih == null) {                root.mCompatibilityInfo = new CompatibilityInfoHolder();            } else {                root.mCompatibilityInfo = cih;            }            view.setLayoutParams(wparams);                        if (mViews == null) {                index = 1;                mViews = new View[1];                mRoots = new ViewRootImpl[1];                mParams = new WindowManager.LayoutParams[1];            } else {                index = mViews.length + 1;                Object[] old = mViews;                mViews = new View[index];                System.arraycopy(old, 0, mViews, 0, index-1);                old = mRoots;                mRoots = new ViewRootImpl[index];                System.arraycopy(old, 0, mRoots, 0, index-1);                old = mParams;                mParams = new WindowManager.LayoutParams[index];                System.arraycopy(old, 0, mParams, 0, index-1);            }            index--;            mViews[index] = view;            mRoots[index] = root;            mParams[index] = wparams;
        // do this last because it fires off messages to start doing things        root.setView(view, wparams, panelParentView);
在setview中,就是前文measure 過程中提出的問題, view是哪裡來的-參見-《盡量理解xml 中LayoutParams的wrap_content, match_parent, 和具體值》
好,現在知道了,rootview ,最終處理的view就是就是從windowmanager 中add進來的。那麼順藤摸瓜,看看到底哪裡調用了WindowManagerImpl.addView()

ViewRootImpl(Context) - android.view.ViewRootImpladdView(View, LayoutParams, CompatibilityInfoHolder, boolean) : void - android.view.WindowManagerImpladdView(View, LayoutParams, CompatibilityInfoHolder) : void - android.view.WindowManagerImpladdView(View, LayoutParams) : void - android.view.WindowManagerImpladdIntruderView() : void - com.android.systemui.statusbar.phone.PhoneStatusBaraddNavigationBar() : void - com.android.systemui.statusbar.phone.PhoneStatusBaraddPanelWindows() : void - com.android.systemui.statusbar.tablet.TabletStatusBar (5 matches)addStartingWindow(IBinder, String, int, CompatibilityInfo, CharSequence, int, int, int) : View - com.android.internal.policy.impl.PhoneWindowManageraddView(View) : void - android.view.WindowManagerImpladvance() : void - com.android.systemui.statusbar.tablet.TabletTickerhandleResumeActivity(IBinder, boolean, boolean) : void - android.app.ActivityThreadhandleShow() : void - android.widget.Toast.TNinvokePopup(LayoutParams) : void - android.widget.PopupWindowmakeVisible() : void - android.app.ActivityonBarViewAttached() : void - com.android.systemui.statusbar.phone.PhoneStatusBaronCreate() : void - com.android.systemui.LoadAverageServiceopenPanel(PanelFeatureState, KeyEvent) : void - com.android.internal.policy.impl.PhoneWindowsetVisible(boolean) : void - android.widget.ZoomButtonsControllershow() : void - android.app.Dialogshow() : void - com.android.internal.policy.impl.KeyguardViewManagershow(int) : void - android.widget.MediaControllershowCompatibilityHelp() : void - com.android.systemui.statusbar.tablet.TabletStatusBarshowSafeModeOverlay() : void - com.android.server.am.ActivityManagerServicestart() : void - com.android.systemui.statusbar.StatusBarstartAnimation(Runnable, int, int, boolean, boolean) : void - com.android.systemui.screenshot.GlobalScreenshotupdateRecentsPanel() : void - com.android.systemui.statusbar.phone.PhoneStatusBarupdateSettings() : void - com.android.internal.policy.impl.PhoneWindowManager
通過查看調用,可以看到很多地方都有調用。大概過一下,就能發現很多熟悉的東西,比如PhoneStatusBar,ActivityThread,PhoneWindow,PopupWindow,Activity,Toast,Dialog 等等。那這裡感覺就比較明顯了,這些熟悉的控制項和類,就是通過windowmanager ,來把自己的view和介面加到系統中了。

分析Dialog 是如何加入到windowmanager 的

柿子先撿軟的捏, 挑個簡單的先。看下show() : void - android.app.Dialog

這不就是經常調用的大名鼎鼎的show() 嘛。 dialog寫完最後調用的show() 方法,沒錯就是它。從這個控制項來看確實就是這樣通過windowmanager, show 一個dialog,就是windowmanager.addview一下啦。 那麼addview的時候,自然就應該是dialog的根view,父控制項嘍。查看一下代碼驗證一下我們的猜測。

    /**     * Start the dialog and display it on screen.  The window is placed in the     * application layer and opaque.  Note that you should not override this     * method to do initialization when the dialog is shown, instead implement     * that in {@link #onStart}.     */    public void show() {        if (mShowing) {            if (mDecor != null) {                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);                }                mDecor.setVisibility(View.VISIBLE);            }            return;        }        mCanceled = false;                if (!mCreated) {            dispatchOnCreate(null);        }        onStart();        mDecor = mWindow.getDecorView();        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {            mActionBar = new ActionBarImpl(this);        }        WindowManager.LayoutParams l = mWindow.getAttributes();        if ((l.softInputMode                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();            nl.copyFrom(l);            nl.softInputMode |=                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;            l = nl;        }        try {            mWindowManager.addView(mDecor, l);            mShowing = true;                sendShowMessage();        } finally {        }    }
被add進 windowmanager中的是這個mDecor, 來自mWindow.getDecorView();

    /**     * Retrieve the top-level window decor view (containing the standard     * window frame/decorations and the client's content inside of that), which     * can be added as a window to the window manager.     *      * <p><em>Note that calling this function for the first time "locks in"     * various window characteristics as described in     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p>     *      * @return Returns the top-level window decor view.     */    public abstract View getDecorView();
從注釋中的描述,確實是 top-level window decor view。 印證了前面的猜測。

接下來看下這個mDecor到底是什麼。

找到具體的實現com.android.internal.policy.impl.PhoneWindow.getDecorView()

    @Override    public final View getDecorView() {        if (mDecor == null) {            installDecor();        }        return mDecor;    }
調用了installDecor()

    private void installDecor() {        if (mDecor == null) {            mDecor = generateDecor();            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);            mDecor.setIsRootNamespace(true);        }<span style="white-space:pre"></span>//......    }
繼續generateDecor();

    protected DecorView generateDecor() {        return new DecorView(getContext(), -1);    }
        public DecorView(Context context, int featureId) {            super(context);            mFeatureId = featureId;        }
        /** The feature ID of the panel, or -1 if this is the application's DecorView */        private final int mFeatureId;
    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
最後,從類定義中看到,DecorView 是一個 FrameLayout 並實現了RootViewSurfaceTaker

所以,回顧一下,這個 top-level window decor view,被add 進windowmanager 中的就是一個framelayout。

這點通過 hierarchyviewer 分析 UI 時得到印證。

如何使用,請移步官方文檔《Using Hierarchy Viewer》,已經有很多人寫了中文文檔,不再贅述。


好了,到這裡知道了系統dialog 中對應的 頂層 view 是一個framelayout, 對應到framework中 viewrootimpl 中的rootview。

同理,PopupWindow 是怎麼乾的

然後舉一反三,看下PopupWindow 是怎麼搞的。

invokePopup(LayoutParams) : void - android.widget.PopupWindowshowAsDropDown(View, int, int) : void - android.widget.PopupWindowshowAtLocation(IBinder, int, int, int) : void - android.widget.PopupWindow
這個介面showAsDropDown就是PopupWindow 的api嘛, 
這裡簡單的列出關鍵代碼,就不一一分析了。

        mWindowManager.addView(mPopupView, p);
        if (mBackground != null) {            final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();            int height = ViewGroup.LayoutParams.MATCH_PARENT;            if (layoutParams != null &&                    layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {                height = ViewGroup.LayoutParams.WRAP_CONTENT;            }            // when a background is available, we embed the content view            // within another view that owns the background drawable            PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);            PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(                    ViewGroup.LayoutParams.MATCH_PARENT, height            );            popupViewContainer.setBackgroundDrawable(mBackground);            popupViewContainer.addView(mContentView, listParams);            mPopupView = popupViewContainer;        } else {            mPopupView = mContentView;
    public void setContentView(View contentView) {        if (isShowing()) {            return;        }        mContentView = contentView;        if (mContext == null && mContentView != null) {            mContext = mContentView.getContext();        }        if (mWindowManager == null && mContentView != null) {            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);        }    }
所以PopupWindow  是把setContentView 的view  加入到了windowmanager 中。

接下來是activity

那最常用的activity 中又是怎樣的呢?

    void makeVisible() {        if (!mWindowAdded) {            ViewManager wm = getWindowManager();            wm.addView(mDecor, getWindow().getAttributes());            mWindowAdded = true;        }        mDecor.setVisibility(View.VISIBLE);    }
這裡的mDecor 就不像前面的dialog 和popupWindow 中那麼容易分析了。

首先掃了一下activity 這個類,發現沒有賦值語句, 查看一下基類,也沒有。最後查看所有mDecor 的調用。

mDecor - android.app.ActivitydispatchKeyEvent(KeyEvent) : boolean - android.app.ActivityhandleDestroyActivity(IBinder, boolean, int, boolean) : void - android.app.ActivityThread (2 matches)handleResumeActivity(IBinder, boolean, boolean) : void - android.app.ActivityThread (2 matches)handleSendResult(ResultData) : void - android.app.ActivityThreadhandleWindowVisibility(IBinder, boolean) : void - android.app.ActivityThreadmakeVisible() : void - android.app.Activity (2 matches)onWindowAttributesChanged(LayoutParams) : void - android.app.ActivitysetVisible(boolean) : void - android.app.ActivityupdateVisibility(ActivityClientRecord, boolean) : void - android.app.ActivityThread

逐個查看,最後僅在handleResumeActivity() : void - android.app.ActivityThread 中 找到相關賦值。

    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {        // If we are getting ready to gc after going to the background, well        // we are back active so skip it.        unscheduleGcIdler();        ActivityClientRecord r = performResumeActivity(token, clearHide);        if (r != null) {            final Activity a = r.activity;            if (localLOGV) Slog.v(                TAG, "Resume " + r + " started activity: " +                a.mStartedActivity + ", hideForNow: " + r.hideForNow                + ", finished: " + a.mFinished);            final int forwardBit = isForward ?                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;            // If the window hasn't yet been added to the window manager,            // and this guy didn't finish itself or start another activity,            // then go ahead and add the window.            boolean willBeVisible = !a.mStartedActivity;            if (!willBeVisible) {                try {                    willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(                            a.getActivityToken());                } catch (RemoteException e) {                }            }            if (r.window == null && !a.mFinished && willBeVisible) {                r.window = r.activity.getWindow();                <span style="color:#ff0000;">View decor = r.window.getDecorView();</span>                decor.setVisibility(View.INVISIBLE);                ViewManager wm = a.getWindowManager();                WindowManager.LayoutParams l = r.window.getAttributes();                <span style="color:#ff0000;">a.mDecor = decor;</span>                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;                l.softInputMode |= forwardBit;                if (a.mVisibleFromClient) {                    a.mWindowAdded = true;                    wm.addView(decor, l);                }
<span style="white-space:pre"></span>//......    }
這裡代碼涉及較多內容,集中注意力,看decorView ,注意到decor = r.window.getDecorView();。

而ActivityClientRecord r = performResumeActivity(token, clearHide);

也就是說是具體resume 的activity 的 window 的decorView()。

通過前面dialog 部分的分析,我們知道使用phoneWindow 的話,那就是framelayout 的那個 decorView。而目前還沒有其他類型的window,這從window的繼承關係可以看出。

在這裡我們看到,activity 在resume的時候,通過判斷是否要visible ,來把activity 的window 的decorView 加到windowmanager中,那麼在activity的生命週期就應該在對應的狀態中從windowmanager中移除該decorView。

在前面mDecor 的調用中,確實發現了在android.app.ActivityThread.handleDestroyActivity(IBinder, boolean, int, boolean),中。

    private void handleDestroyActivity(IBinder token, boolean finishing,            int configChanges, boolean getNonConfigInstance) {        ActivityClientRecord r = performDestroyActivity(token, finishing,                configChanges, getNonConfigInstance);        if (r != null) {            cleanUpPendingRemoveWindows(r);            WindowManager wm = r.activity.getWindowManager();            <span style="color:#ff0000;">View v = r.activity.mDecor;</span>            if (v != null) {                if (r.activity.mVisibleFromServer) {                    mNumVisibleActivities--;                }                IBinder wtoken = v.getWindowToken();                if (r.activity.mWindowAdded) {                    if (r.onlyLocalRequest) {                        // Hold off on removing this until the new activity's                        // window is being added.                        r.mPendingRemoveWindow = v;                        r.mPendingRemoveWindowManager = wm;                    } else {                       <span style="color:#ff0000;"> wm.removeViewImmediate(v);</span>                    }                }
<span style="white-space:pre"></span>}
<span style="white-space:pre"></span>//......    }
至此,通過看了dialog, popupwindow, 和activity 中的部分源碼, 知道了普通介面(dialog, popupwindow, 和activity )的第一個view, 是怎麼來的,是如何加入到系統中的,也就是windowManager。

一句話結論應用程式層面的介面都是通過windowmanager 加入到framework 中的,ViewRootImpl 是framework對view 的抽象, 介面管理的根節點。

留的尾巴
弄清楚第一個的問題後,接下來爭取比較完整地回顧一下view 的建立,繪製,layout過程。to be continued...

在分析過程中,簡單的接觸到了framework中處理activity的部分流程,handleResumeActivity(),handleDestroyActivity()。另一個問題就是系統framework 是如何管理activity的生命週期的。//TODO 



弄清楚第一個的問題後,接下來爭取比較完整地回顧一下view 的建立,繪製,layout過程。to be continued...

在分析過程中,簡單的接觸到了framework中處理activity的部分流程,handleResumeActivity(),handleDestroyActivity()。另一個問題就是系統framework 是如何管理activity的生命週期的。//TODO 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.