天天看點

DecorView與window的建立前言window的建立建立DecorView将DecorView添加至Window

前言

對于Android開發者來說,View無疑是開發中經常接觸的,包括它的事件分發機制、測量、布局、繪制流程等,如果要自定義一個View,那麼應該對以上流程有所了解、研究。本系列文章将會為大家帶來View的工作流程詳細解析。在深入接觸View的測量、布局、繪制這三個流程之前,我們從Activity入手,看看從Activity建立後到View的正式工作之前,所要經曆的步驟。以下源碼均取自Android API 21。

window的建立

一般地,我們在Activity中,會在onCreate()方法中寫下這樣一句:

顯然,這是為activity設定一個我們定義好的main.xml布局,我們跟蹤一下源碼,看看這個方法是怎樣做的,Activity#setContentView:

public void setContentView(@LayoutRes int layoutResID) {
     getWindow().setContentView(layoutResID);  //調用getWindow方法,傳回mWindow
     initWindowDecorActionBar();
}
...
public Window getWindow() {   
     return mWindow;
}                

從上面看出,裡面調用了mWindow的setContentView方法,那麼這個“mWindow”是何方神聖呢?嘗試追蹤一下源碼,發現mWindow是Window類型的,但是它是一個抽象類,setContentView也是抽象方法,是以我們要找到Window類的實作類才行。我們在Activity中查找一下mWindow在哪裡被指派了,可以發現它在Activity#attach方法中有如下實作:

final 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) {
        ...
        mWindow = new PhoneWindow(this);
        ...
    }                

我們隻看關鍵部分,這裡執行個體化了PhoneWindow類,由此得知,PhoneWindow是Window的實作類,那麼我們在PhoneWindow類裡面找到它的setContentView方法,看看它又實作了什麼,PhoneWindow#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) { // 1
        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); // 2
    }
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}                

首先判斷了mContentParent是否為null,如果為空則執行installDecor()方法,那麼這個mContentParent又是什麼呢?我們看一下它的注釋:

// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;                

它是一個ViewGroup類型,結合②号代碼處,可以得知,這個mContentParent是我們設定的布局(即main.xml)的父布局。注釋還提到了,這個mContentParent是mDecor本身或者是mDecor的一個子元素,這句話什麼意思呢?這裡先留一個疑問,下面會解釋。

這裡先梳理一下以上的内容:Activity通過PhoneWindow的setContentView方法來設定布局,而設定布局之前,會先判斷是否存在mContentParent,而我們設定的布局檔案則是mContentParent的子元素。

建立DecorView

接着上面提到的installDecor()方法,我們看看它的源碼,PhoneWindow#installDecor:

private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor(); // 1
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != ) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor); // 2
        ...
        } 
    }
}                

首先,會執行①号代碼,調用PhoneWindow#generateDecor方法:

protected DecorView generateDecor() {
    return new DecorView(getContext(), -);
}                

可以看出,這裡執行個體化了DecorView,而DecorView則是PhoneWindow類的一個内部類,繼承于FrameLayout,由此可知它也是一個ViewGroup。

那麼,DecroView到底充當了什麼樣的角色呢?

其實,DecorView是整個ViewTree的最頂層View,它是一個FrameLayout布局,代表了整個應用的界面。在該布局下面,有标題view和内容view這兩個子元素,而内容view則是上面提到的mContentParent。我們接着看②号代碼,PhoneWindow#generateLayout方法

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        // 從主題檔案中擷取樣式資訊
        TypedArray a = getWindowStyle();

        ...

        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }

        if(...){
            ...
        }

        // Inflate the window decor.
        // 加載視窗布局
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & ( << FEATURE_SWIPE_TO_DISMISS)) != ) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if(...){
            ...
        }

        View in = mLayoutInflater.inflate(layoutResource, null);    //加載layoutResource
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); //往DecorView中添加子View,即mContentParent
        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); // 這裡擷取的就是mContentParent
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & ( << FEATURE_INDETERMINATE_PROGRESS)) != ) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }

        if ((features & ( << FEATURE_SWIPE_TO_DISMISS)) != ) {
            registerSwipeCallbacks();
        }

        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        ...

        return contentParent;
    }                

由以上代碼可以看出,該方法還是做了相當多的工作的,首先根據設定的主題樣式來設定DecorView的風格,比如說有沒有titlebar之類的,接着為DecorView添加子View,而這裡的子View則是上面提到的mContentParent,如果上面設定了FEATURE_NO_ACTIONBAR,那麼DecorView就隻有mContentParent一個子View,這也解釋了上面的疑問:mContentParent是DecorView本身或者是DecorView的一個子元素。

用一幅圖來表示DecorView的結構如下:

DecorView與window的建立前言window的建立建立DecorView将DecorView添加至Window

DecorView結構

小結:DecorView是頂級View,内部有titlebar和contentParent兩個子元素,contentParent的id是content,而我們設定的main.xml布局則是contentParent裡面的一個子元素。

在DecorView建立完畢後,讓我們回到PhoneWindow#setContentView方法,直接看②号代碼: mLayoutInflater.inflate(layoutResID, mContentParent);這裡加載了我們設定的main.xml布局檔案,并且設定mContentParent為main.xml的父布局,至于它怎麼加載的,這裡就不展開來說了。

到目前為止,通過setContentView方法,建立了DecorView和加載了我們提供的布局,但是這時,我們的View還是不可見的,因為我們僅僅是加載了布局,并沒有對View進行任何的測量、布局、繪制工作。在View進行測量流程之前,還要進行一個步驟,那就是把DecorView添加至window中,然後經過一系列過程觸發ViewRootImpl#performTraversals方法,在該方法内部會正式開始測量、布局、繪制這三大流程。至于該一系列過程是怎樣的,因為涉及到了很多機制,這裡簡單說明一下:

将DecorView添加至Window

每一個Activity元件都有一個關聯的Window對象,用來描述一個應用程式視窗。每一個應用程式視窗内部又包含有一個View對象,用來描述應用程式視窗的視圖。上文分析了建立DecorView的過程,現在則要把DecorView添加到Window對象中。而要了解這個過程,我們首先要簡單先了解一下Activity的建立過程:

首先,在ActivityThread#handleLaunchActivity中啟動Activity,在這裡面會調用到Activity#onCreate方法,進而完成上面所述的DecorView建立動作,當onCreate()方法執行完畢,在handleLaunchActivity方法會繼續調用到ActivityThread#handleResumeActivity方法,我們看看這個方法的源碼:

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { 
    //...
    ActivityClientRecord r = performResumeActivity(token, clearHide); // 這裡會調用到onResume()方法

    if (r != null) {
        final Activity a = r.activity;

        //...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow(); // 獲得window對象
            View decor = r.window.getDecorView(); // 獲得DecorView對象
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager(); // 獲得windowManager對象
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                wm.addView(decor, l); // 調用addView方法
            }
            //...
        }
    }
}                

在該方法内部,擷取該activity所關聯的window對象,DecorView對象,以及windowManager對象,而WindowManager是抽象類,它的實作類是WindowManagerImpl,是以後面調用的是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的成員函數,而mGlobal則是WindowManagerGlobal的一個執行個體,那麼我們接着看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 >= ) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }                

先看①号代碼處,執行個體化了ViewRootImpl類,接着,在②号代碼處,調用ViewRootImpl#setView方法,并把DecorView作為參數傳遞進去,在這個方法内部,會通過跨程序的方式向WMS(WindowManagerService)發起一個調用,進而将DecorView最終添加到Window上,在這個過程中,ViewRootImpl、DecorView和WMS會彼此關聯,至于詳細過程這裡不展開來說了。

最後通過WMS調用ViewRootImpl#performTraverals方法開始View的測量、布局、繪制流程,這三大流程在下文會詳細講述,謝謝大家的閱讀。

讀後小結:

(1)Window是在Activity的attach的方法中建立的,window即PhoneWindow,因為PhoneWindow是Window的實作類;

(2)DecorView是在PhoneWindow中進行建立的,DecorView是ViewTree最頂層的View,而Decor View本質就是FramLayout布局;

(3)WindowMangerImpl(windowManger的實作類)通過調用WindowMangerGlobal的addVIew方法添加DecorView,然後交給ViewRootImpl去處理(其實最終是交給WindowMangerService處理的),至于怎麼處理的以後更新;

原連結

繼續閱讀