Window的概念:
官方的解释:
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.
The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.
大概的意思是,Window是关于顶级窗体的外观、行为的抽象基类,它被作为顶层的View被添加到WindowManager当中。它还规定了手机界面的标准规范,比如:背景、标题栏等等。
它只有一个实现类PhoneWindow,当你需要一个Window的时候就需要实例化PhoneWindow对象。
关于官方解释的困惑:
看了上述的解释,如果会感觉的一些困惑!可能是因为平常我们很少接触到Window,并且理解的top-level-view(顶层视图)就是setContentView()中的contentView或者是DecorView。
有上述感觉原因是没有了解Window的整个工作机制,所以对Window的认识不是很全面;下面我们首先会讲一些关于Window的结论,然后再一步步探索它的工作流程。
总之,暂时就把Window理解成是View的根容器。
Window的分类:
Window是分层的,每个Window都有对应的z-ordered,层级范围数大的会覆盖在层级范围小的Window上面。
应用类Window
:应用类的Window一般指的是Activity对应的Window,它的层级范围数为:(1~99)。
子Window
:子Window必须要依附在特定的父Window之上,它的层级范围为:(1000~1999)。
系统Window
:系统Window一般需要声明权限才能创建,它的层级范围为:(2000~2999)。比如常见的系统Window有Toast、系统状态栏。
通过
WindowManger
添加Window:分析Window的工作机制之前,还是先要了解下Window是如何被添加的?需要添加Window首先需要使用到WindowManager,下面是具体的使用代码。
// 将一个Button添加到窗体上面屏幕坐标为(100,300)的位置上。 WindowManager manager = getWindowManager(); Button button = new Button(this); button.setText("button"); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT); layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; layoutParams.gravity = Gravity.LEFT | Gravity.TOP; layoutParams.x = 100; layoutParams.y = 300; manager.addView(button, layoutParams);
WindowManger顾名思义,它就是Window的管理器(负责Window的添加View,更新View,删除View等操作)同时也是外界(
可以理解为用户进程
)访问Window的入口,由于Window的具体实现过程位于WindowManagerService(下面简称WMS)这个系统远程服务当中,为了与WMS进行IPC通信,Android给我们提供了WindowManager去获取WMS的Binder引用,方便用户进程与WMS的交互。(ps:如果不懂Binder进程通信机制的同学,请务必把Binder机制的工作流程弄清楚。)
WindowManager.LayoutParams.flags
属性的常见参数:FLAG_NOT_FOCUSABLE
:表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启动
FLAG_NOT_TOUCH_MODAL
,最终事件会传递给下层具有焦点的Window处理。FLAG_NOT_TOUCH_MODAL
:该模式下,系统会将Window以外的单击事件传递给下层的Window,Window区域范围内的事件会自己处理。一般来说都需要开启这个标记,否则其他的Window将无法收到单击事件。
FLAG_SHOW_WHEN_LOCKED
:开启此模式可以让Window显示在锁屏界面上面。
WindowManager.LayoutParams.type
属性的常见参数:前面说的Window的分类,就是通过type属性来控制的。
TYPE_SYSTEM_ERROR
设置Window的类型为系统层级,注意:使用此参数时需要声明权限:android.permission.SYSTEM_ALERT_WINDOWS。
- Window的内部工作机制:
Window的内部工作机制.png
Window其实是一个抽象的概念,每一个Window都对应一个View和一个ViewRootImpl,Window和View的联系是通过ViewRootImpl来完成的。为什么这么说呢,因为这是从WindowManager定义的三个方法(addView()、updateView()、removeView())都是针对View看出来的,所以接下会从这三个方法进行分析。
WindowManager的源码分析流程.png
Window的添加过程–
WindowManager.addView()
:通过分析源码Window.setWindowManager()我们发现最后执行添加View的是WindowManagerGlobal。
检测参数是否合法,如果是子Window就需要调整布局参数。
if (view == null) { throw new IllegalArgumentException("view must not be null"); } if (display == null) { throw new IllegalArgumentException("display must not be null"); } if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); }
创建ViewRootImpl对象并且View添加到列表中。
root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams);
通过ViewRootImpl的setView()方法来更新界面并完成Window的添加。
requestLayout(); try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); }
Window的删除过程–
WindowManager.removeView()
:删除过程与添加过程一样都是WindowManagerGlobal来完成的。
查找待删除的View的索引
final int index = mViews.indexOf(view); if (required && index < 0) { throw new IllegalArgumentException("View=" + view + " not attached to window manager"); } return index;
删除View会有两种方式(同步删除、异步删除),同步删除将会立即执行删除动作,并且执行dispatchDetachedFromWindow()
if (mAdded) { dispatchDetachedFromWindow(); } if (mAdded && !mFirst) { destroyHardwareRenderer(); if (mView != null) { int viewVisibility = mView.getVisibility(); boolean viewVisibilityChanged = mViewVisibility != viewVisibility; if (mWindowAttributesChanged || viewVisibilityChanged) { // If layout params have been changed, first give them // to the window manager to make sure it has the correct // animation info. try { if ((relayoutWindow(mWindowAttributes, viewVisibility, false) & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { mWindowSession.finishDrawing(mWindow); } } catch (RemoteException e) { } } mSurface.release(); } } mAdded = false; WindowManagerGlobal.getInstance().doRemoveView(this);
同步删除只是发送了一个请求删除的消息后就返回了,此时View并没有完成删除操作,会添加到mDyingViews当中。
if (immediate && !mIsInTraversal) { doDie(); return false; } if (!mIsDrawing) { destroyHardwareRenderer(); } else { Log.e(TAG, "Attempting to destroy the window while drawing!\n" + " window=" + this + ", title=" + mWindowAttributes.getTitle()); } mHandler.sendEmptyMessage(MSG_DIE);
Window的更新过程–
WindowManager.updateViewLayout()
:if (view == null) { throw new IllegalArgumentException("view must not be null"); } if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; view.setLayoutParams(wparams); synchronized (mLock) { int index = findViewLocked(view, true); ViewRootImpl root = mRoots.get(index); mParams.remove(index); mParams.add(index, wparams); root.setLayoutParams(wparams, false); }
view重新设置布局参数setLayoutParams(),删除旧的布局参数,添加新的布局参数,最后通过IWindowSession进行远程删除。