深入理解Window、WindowManager

本篇主要分爲3個部分:
1、通過官方文檔註釋,來理解Window、WindowManager;
2、通過分析Activity中setContentView()源碼,來理解Window在Activity中的工作流程;
3、通過分析Dialog源碼,來理解Window在Dialog中的工作流程;

源碼角度理解Window、WindowManager

Window相關的主要有以下幾個類、接口:
Window抽象類、Window.Callback接口,WindowManager接口、ViewManager接口、WindowManagerImpl實現類、WindowManagerGlobal類、ViewRootImpl類。

先把這幾個類的作用、特性、工作流程總結一下,捋一捋思路,然後再去分析每個類,這樣思路會更清晰。

1、Window表示一個窗口的概念,只有一個唯一實現類PhoneWindow,所有的能讓用戶看到的組件都是通過Window來展現的,Window規定了UI的展現方式、接收用戶的觸摸等交互,然後傳給各個組件;這些組件通過實現Window.Callback接口,就可以接受到Window的通知了;

2、Window持有一個WindowManager對象,該對象的主要作用就是幫助Window完成部分功能實現,比如添加View、刪除View(ViewManager定義了添加、刪除View,WindowManager繼承自ViewManager);

3、WindowManagerImpl就是WindowManager的具體實現(非唯一實現),大部分功能都在這裏完成。除了完成自己的本職工作外,WindowManagerImpl還要將Window和View的顯示通知給系統,所以這個類持有一個WindowManagerGlobal對象,該對象是單例、全局存在,的主要作用就是對Window和View的全局管理;

4、WindowManagerGlobal做兩件事,一件是添加/刪除/更新View(真正的執行者是ViewRootImpl),另一件就是通過List管理着所有View、ViewRootImpl、LayoutParamas等。而ViewRootImpl則通過performTraversals()發起View的繪製流程。

5、Activity通過new PhoneWindow()得到Window,通過getSystemService()得到WindowManager,通過mWindow.setWindowManager()方法使Window和WindowManager綁定,通過實現Window.Callback接口接收Window的通知。


Window
理解Window是什麼、幹什麼用的,只看Google官方註釋就已經明白了一大半。

/**重點內容
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */

這個類是頂層視圖外觀和行爲策略的抽象基類,這個類的實例應該被添加在窗口管理器的頂層視圖中,它提供了標準的UI策略,規定UI的顯示、交互等準則;

這個抽象類的唯一實現是android.view.PhoneWindow,當你需要一個窗口時你應該實例化它。

Window.Callback
實現這個接口的類有下面這些:
Activity, ActivityGroup, AlertDialog, AliasActivity, CharacterPickerDialog, DatePickerDialog, Dialog, ExpandableListActivity, LauncherActivity, ListActivity, PreferenceActivity, ProgressDialog, TabActivity, TimePickerDialog 。

截取了Window.Callback的部分方法,如下:

public interface Callback {
    public boolean dispatchKeyEvent(KeyEvent event);

    public boolean dispatchTouchEvent(MotionEvent event);

    public boolean onMenuOpened(int featureId, Menu menu);

    public void onContentChanged();

    public void onWindowFocusChanged(boolean hasFocus);

    public void onAttachedToWindow();
}

看這些方法的命名,就已經知道了一切。Window接收到用戶的交互操作,然後通過這個接口通知到Activity、Dialog。

WindowManager
看官方註釋:

 * The interface that apps use to talk to the window manager.
 * <p>
 * Use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code> to get one of these.
 * </p><p>
 * Each window manager instance is bound to a particular {@link Display}.
 * To obtain a {@link WindowManager} for a different display, use
 * {@link Context#createDisplayContext} to obtain a {@link Context} for that
 * display, then use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code>
 * to get the WindowManager.
 * </p><p>
 * The simplest way to show a window on another display is to create a
 * {@link Presentation}.  The presentation will automatically obtain a
 * {@link WindowManager} and {@link Context} for that display.

這個接口是app用來與窗口管理器通信的;

通過Context.getSystemService()獲取實例;

每個窗口管理器實例都綁定一個特定的Display(即Activity、Dialog),要爲不同Display獲取WindowManager,請使用createDisplayContext()獲取該Display對應的Context,然後使用Context.getSystemService獲取WindowManager;

在Display上顯示window最簡單方法是創建一個Presentation, Presentation將自動獲得該Display的WindowManager和Context。

WindowManagerImpl

 * Provides low-level communication with the system window manager for
 * operations that are bound to a particular context, display or parent window.
 * Instances of this object are sensitive to the compatibility info associated
 * with the running application.
 *
 * This object implements the {@link ViewManager} interface,
 * allowing you to add any View subclass as a top-level window on the screen.
 * Additional window manager specific layout parameters are defined for
 * control over how windows are displayed.  It also implements the {@link WindowManager}
 * interface, allowing you to control the displays attached to the device.
 * 
 * <p>Applications will not normally use WindowManager directly, instead relying
 * on the higher-level facilities in {@link android.app.Activity} and
 * {@link android.app.Dialog}.
 * 
 * <p>Even for low-level window manager access, it is almost never correct to use
 * this class.  For example, {@link android.app.Activity#getWindowManager}
 * provides a window manager for adding windows that are associated with that
 * activity -- the window manager will not normally allow you to add arbitrary
 * windows that are not associated with an activity.

提供Context操作與系統window的最低級別通信,這個對象的實例與正在運行的應用高度相關;

該對象實現了ViewManager接口,允許您將任何View子類添加到屏幕上的頂層窗口中,佈局參數用來控制這些View如何顯示在窗口中。這個類也實現了WindowManager接口;

應用程序中通常不會直接使用WindowManager,而是使用Activity和Dialog;

即使對於低級別的窗口管理器訪問,直接使用這個類也是不正確的。比如,{@link android.app.Activity#getWindowManager}提供了一個窗口管理器,用於添加與該活動相關聯的窗口,但是,窗口管理器通常不會允許添加與活動無關的窗口。
這是直譯,我的理解是,Activity提供封裝好的方法,比如setContentView(),用於往窗口中添加View,但是不允許直接使用WindowManger對象來自行添加無關的View。


WindowManagerGlobal
這個類是單例、全局存在。
它做兩件事,一件是添加/刪除/更新View(真正的執行者是ViewRootImpl),另一件就是通過List管理着所有View、ViewRootImpl、LayoutParamas等。

 * Provides low-level communication with the system window manager for
 * operations that are not associated with any particular context.
 *
 * This class is only used internally to implement global functions where
 * the caller already knows the display and relevant compatibility information
 * for the operation.  For most purposes, you should use {@link WindowManager} instead
 * since it is bound to a context.

與特定Context無關的操作,通過這個類,可以和系統窗口管理器實現低級別的通信;

這個類只是在內部用來實現全局功能,大多數情況下,你應該使用WindowManger,因爲它綁定了特定的Context;


ViewRootImpl
Window不會直接管理View的具體繪製,而是委託ViewRootImlp類來管理,View的測量、佈局、繪製流程就是由這個類發起的(performTraversals()方法)。

/**
 * The top of a view hierarchy, implementing the needed protocol between View
 * and the WindowManager.  This is for the most part an internal implementation
 * detail of {@link WindowManagerGlobal}.
 */

視圖層次結構的頂部,在View和WindowManager之間實現所需的協議。 這大部分是 WindowManagerGlobal的內部實現細節。


Activity的setContentView()源碼分析

查看Activity的setContentView()方法,發現是通過Activity所綁定的Window執行的

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

至於這個getWindow(),網上、書上很多人的分析都是通過PolicyManager的makePhoneWindow()得到的,我看的源碼是6.0的,是在Activity生成的時候直接通過new PhoneWindow()得到的,不過這不影響。下面就分析下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) {
        // 初始化mContentParent
        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 {
        // 把layoutResID佈局加載到mContentParent中
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

邏輯很簡單,就是創建mContentParent,把id爲layoutResID的佈局文件加載進去。layoutResID就是我們創建的Activity對應的佈局文件id,下面要分析mContentParent是什麼。看下installDecor()部分源碼:

 private void installDecor() {
    if (mDecor == null) {
        // 初始化mDecor
        // 該方法中通過new DecorView(getContext(), -1)得到了DecorView對象,這是PhoneWindow中的一個內部類,繼承自FrameLayout
        mDecor = generateDecor();
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    }
    if (mContentParent == null) {
        // 通過mDecor得到mContentParent
        mContentParent = generateLayout(mDecor);
        ......
        ......
        ......
     }
     ......
     ......
     ......
}

mDecor是一個FrameLayout,再看一下generateLayout()方法:

protected ViewGroup generateLayout(DecorView decor) {
    ......
    ......
    ......

    // 定義佈局id,通過不同的特性(實際上就是主題、風格等),給layoutResource賦不同的值(加載不同的佈局)
    int layoutResource;
    int features = getLocalFeatures();
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
    } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
        // System.out.println("Title Icons!");
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        // Special case for a window with only a progress bar (and title).
        // XXX Need to have a no-title version of embedded windows.
        layoutResource = R.layout.screen_progress;
        // System.out.println("Progress!");
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        // Special case for a window with a custom title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        // If no other features and not embedded, only need a title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
            layoutResource = a.getResourceId(
                    R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
        // System.out.println("Title!");
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }
    mDecor.startChanging();

    //加載根佈局
    View in = mLayoutInflater.inflate(layoutResource, null);
    //把根佈局添加到decor容器中
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;
    //找到根佈局中id爲ID_ANDROID_CONTENT的控件,ID_ANDROID_CONTENT的值爲R.id.content
    // 前面根據不同主題風格加載了不同的根佈局,但是所有根佈局中,都有id爲R.id.content的控件(FrameLayout)
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ......
    ......
    ......

    // 自此,根佈局已經添加到mDecor中了,根佈局中的contentParent已經初始化並返回
    return contentParent;
}

mContentParent已經清楚了,就是Window的根佈局中,負責顯示內容的FrameLayout。按照前面的邏輯,我們創建的Activity對應的佈局文件,將被加載到這個FrameLayout中。

到目前爲止,layout文件已經添加到PhoneWindow的DecorView中了,但是這個DecorView並沒有在系統中“註冊”,系統也並沒有爲它分配資源、顯示等。

最後,在Activity的onResume()方法中,會調用Activity的makeVisible()方法:

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

這裏會調用WindowManagerImpl的addView()方法把DecorView添加到系統中,上面已經講過,addView()最終會由WindowManagerGlobal來執行,而這個類就是負責全局的window管理的。

總結一下:
1、創建Activity的同時,會創建一個PhoneWindow,同時也會創建一個WindowManagerImpl對象,並與PhoneWindow綁定;
2、調用Activity的setContentView()方法,會委託到PhoneWindow,讓PhoneWindow來添加layout文件。這就印證了上一塊講的,Window是用來添加View、顯示View、UI交互的;
3、PhoneWindow中,首先會根據不同的主題、風格等,加載不同的佈局,把這個佈局添加到DecorView中。但不管是哪種佈局,都會有一個id爲R.id.content的FrameLayout控件,初始化這個FrameLayout,把我們創建的layout文件添加到這個FrameLayout文件中;
4、最後由WindowManagerGlobal把DecorView添加到系統中,爲其分配顯示資源、分配交互事件。


Dialog中setContentView()源碼分析

先看Dialog的構造方法,執行邏輯在代碼註釋中,就不單獨分析了:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
    if (createContextThemeWrapper) {
        if (themeResId == 0) {
            final TypedValue outValue = new TypedValue();
            context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
            themeResId = outValue.resourceId;
        }
        mContext = new ContextThemeWrapper(context, themeResId);
    } else {
        mContext = context;
    }

    // 獲取WindowManagerImpl實例
    mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);

    // new一個PhoneWindow實例
    final Window w = new PhoneWindow(mContext);
    mWindow = w;
    w.setCallback(this);
    w.setOnWindowDismissedCallback(this);
    // PhoneWindow綁定WindowManagerImpl
    w.setWindowManager(mWindowManager, null, null);
    w.setGravity(Gravity.CENTER);

    mListenersHandler = new ListenersHandler(this);
}

再看setContentView():

    public void setContentView(@NonNull View view, @Nullable ViewGroup.LayoutParams params) {
        mWindow.setContentView(view, params);
    }

又進入了PhoneWindow的setContentView()方法,這個流程與Activity一樣,上面已經分析過了。

再看show()和dismiss()方法,dialog的顯示和隱藏是通過WindowManagerGlobal的addView()和removeView()來實現的,下面就直接在註釋中給出執行邏輯,不再單獨分析:

// show()方法
public void show() {
    // 這是重複執行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);
    } else {
        // Fill the DecorView in on any configuration changes that
        // may have occured while it was removed from the WindowManager.
        final Configuration config = mContext.getResources().getConfiguration();
        mWindow.getDecorView().dispatchConfigurationChanged(config);
    }

    onStart();
    // 獲取DecorView。因爲在dismiss()時,會把mDecor置空
    mDecor = mWindow.getDecorView();

    if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
        final ApplicationInfo info = mContext.getApplicationInfo();
        mWindow.setDefaultIcon(info.icon);
        mWindow.setDefaultLogo(info.logo);
        mActionBar = new WindowDecorActionBar(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;
    }

    // 通過WindowManagerGlobal把mDecor添加到系統中
    mWindowManager.addView(mDecor, l);
    mShowing = true;

    sendShowMessage();
}


// dismiss()方法
void dismissDialog() {
    if (mDecor == null || !mShowing) {
        return;
    }

    if (mWindow.isDestroyed()) {
        Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
        return;
    }

    try {
        // 通過WindowManagerGlobal移除mDecor
        mWindowManager.removeViewImmediate(mDecor);
    } finally {
        if (mActionMode != null) {
            mActionMode.finish();
        }
        // mDecor置空
        mDecor = null;
        mWindow.closeAllPanels();
        onStop();
        mShowing = false;

        sendDismissMessage();
    }
}
發佈了35 篇原創文章 · 獲贊 52 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章