深入理解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();
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值