作者:不洗碗工作室 - catango
版权归作者所有,转载请注明出处
通常情况下,我们使用的Dialog,Activity等需要显示到屏幕上面的内容都需要WindowManager来操作的,WindowManager是一个非常重要的子系统.其中有关的类为WindowManager,WindowManagerService,Surface和SurfaceFlinger
什么是Window
Android系统中的Window是屏幕上的一块用于绘制各种UI元素并能够响应应用户输入的一个矩形区域和独自占有一个Surface实例的显示区域。 比如Dialog、Activity的界面、壁纸、状态栏以及Toast等都是Window.一般来说它是和具体的View绑定在一起的.
Window是一个抽象类,具体实现是PhoneWindow.创建Window只需要WindowManager即可,WindowManager是外界访问Window的入口,通过WindowManager能够操纵Window,其具体实现都在WMS中.
层级关系
Window有三种类型,应用类的Window(如Activity),子Window(如Dialog),系统Window(如Toast).Window是分层的,每个Window都是z-ordered,层级大的覆盖在层级小的上面,这些层级对应在WindowManager.LayoutParams的type参数.而系统级别的Window需要权限声明.
- 应用程序窗口
public static final int FIRST_APPLICATION_WINDOW = 1;//1
public static final int TYPE_BASE_APPLICATION = 1;//窗口的基础值,其他的窗口值要大于这个值
public static final int TYPE_APPLICATION = 2;//普通的应用程序窗口类型
public static final int TYPE_APPLICATION_STARTING = 3;//应用程序启动窗口类型,用于系统在应用程序窗口启动前显示的窗口。
public static final int TYPE_DRAWN_APPLICATION = 4;
public static final int LAST_APPLICATION_WINDOW = 99;//2
- 子窗口(必须有依赖的父窗口才可以)
public static final int FIRST_SUB_WINDOW = 1000;//子窗口类型初始值
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
public static final int LAST_SUB_WINDOW = 1999;//子窗口类型结束值
- 系统窗口
public static final int FIRST_SYSTEM_WINDOW = 2000;//系统窗口类型初始值
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;//系统状态栏窗口
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;//搜索条窗口
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;//通话窗口
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;//系统ALERT窗口
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;//锁屏窗口
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;//TOAST窗口
...
public static final int LAST_SYSTEM_WINDOW = 2999;//系统窗口类型结束值
Window的属性
Flags参数表示Window的属性,通过 WindowManager.LayoutParams的flags来设置
- FLAG_ALLOW_LOCK_WHILE_SCREEN_ON 只要窗口可见,就允许在开启状态的屏幕上锁屏
- FLAG_NOT_FOCUSABLE 窗口不能获得输入焦点,设置该标志的同时,FLAG_NOT_TOUCH_MODAL也会被设置
- FLAG_NOT_\TOUCHABLE 窗口不接收任何触摸事件
- FLAG_NOT_TOUCH_MODAL 在该窗口区域外的触摸事件传递给其他的Window,而自己只会处理窗口区域内的触摸事件
- FLAG_KEEP_SCREEN_ON 只要窗口可见,屏幕就会一直亮着
- FLAG_LAYOUT_NO_LIMITS 允许窗口超过屏幕之外
- FLAG_FULLSCREEN 隐藏所有的屏幕装饰窗口,比如在游戏、播放器中的全屏显示
- FLAG_SHOW_WHEN_LOCKED 窗口可以在锁屏的窗口之上显示
- FLAG_IGNORE_CHEEK_PRESSES 当用户的脸贴近屏幕时(比如打电话),不会去响应此事件
- FLAG_TURN_SCREEN_ON 窗口显示时将屏幕点亮
Token
在源码中token一般代表的是Binder对象,作用于IPC进程间数据通讯。并且它也包含着此次通讯所需要的信息,在ViewRootImpl里,token用来表示mWindow(W类,即IWindow),并且在WMS中只有符合要求的token才能让Window正常显示。 在Window中,token(LayoutParams中的token)分为以下2种情况
- 应用窗口 : token表示的是activity的mToken(ActivityRecord)
- 子窗口 : token表示的是父窗口的W对象,也就是mWindow(IWindow)
无论是应用窗口,还是子窗口,token 不能为空。否则会抛出异常,并且应用窗口的token 必须是某个 Activity 的 mToken,子窗口的token 必须是父窗口的 IWindow 对象。而且子窗口还需等父窗口添加成功之后才可添加到Window上。
什么是Surface
基本上我们看到的视图都是通过Surface来渲染的,Surface是一块画布,通过Canvas或者OpenGL在上面作画.然后通过SufaceFlinger将多块Surface按照Z-order排序后输出到FrameBuffer中展示到屏幕上.其中surface使用了双缓冲技术.Surface是通过WMS分配到Window上面的.
通过源码我们可以知道,Surface里面定义了一个Canvas,可以这么理解,Canvas就是其缓冲区内容的具现.最终都会保存到Surface里的buffer里,最后由SurfaceFlinger合成并显示。
什么是WindowManagerService
在WindowManager调用之前,首先是获取到WindowManagerService.各种系统级服务都会注册到ContextImpl的一个map中维护,然后通过相应的字符串来获取,比如LayoutInflate是通过Context调用getSystemService,传入LAYOUT _ INFLATER _ SERVICE字符来获取到的.WindowManager也一样,通过WINDOW _ SERVICE来获取到的.
WMS就是对所有Window的Surface进行协调管理的类,WMS为全部Window分配Surface,控制Surface尺寸,大小,动画等等.我们通过WindowManager来控制View的添加,删除等操作,其实都是通过WMS处理的.WindowManager通过Binder来进行跨进程通信,类似ActivityManager和AMS的关系.
什么是WindowManager
WindowManager继承自ViewManager,看名字就知道这个类是管理View的,而View又在Window中展示,也可以说WindowManager是管理Window(废话)的.我们想对Window进行添加删除等操作都可以使用WindowManager
其中ViewManager接口有三大方法
- addView
- updateViewLayout
- removeView
一般来说,我们常用的方法也就这三个,添加更新和删除,它的基本流程都是创建一个Window,并且向里面添加View,如果想删除一个Window,删除它对应的View就好.所以说WindowManager操纵Window的过程就是操作对应在Window里面View的过程.
具体的使用
BB了它的作用之后,其实最重要的还是怎么去用了.下面简单介绍下WindowManager的使用
TextView view = new TextView(this);
view.setText("Balabala");
WindowManager.LayoutParams params =
new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, PixelFormat.TRANSPARENT);
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
params.type = WindowManager.LayoutParams.TYPE_APPLICATION;
params.x = 200;
params.y = 300;
getWindowManager().addView(view, params);
这段代码就是将一个TextView添加到Window,屏幕坐标是200,300的位置.首先创建一个TextView,然后创建一个LayoutParams设置Window相应的属性,其中比较重要的是type和flags属性,参考上面的值按需设置即可.
如果当Window需要变化时,比如用户拖动悬浮框这种操作,我们只需要监听触摸事件,然后不停的调用updateViewLayout方法.
内部流程
可以看到它内部的几种方法都是针对View的,说明了View才是Window的主体,实际上我们是无法直接访问Window的.上面也说过了,Window的操作全都是通过WindowManager来操作的,WindowManager其实也是一个接口,它的具体实现是WindowMangerImpl类.
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
其中实现了这三个方法的具体实现也不是通过WindowManger的,而是通过mGlobal来实现的,这里其实用到了传说中的桥接模式, 那么Window和View的具体关系我们就可以从WindowManagerGlobal的添加删除更新的方法来分析了.WindowManagerGlobal本身是一个单例,因此它其实是管理所有Window的Manager.这点也可以从以下几个比较重要的参数可以看到
private final ArrayList<View> mViews = new ArrayList<View>();//存储所有Window对应的View
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); //所有Window对应的ViewRootImpl
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>(); //存储所有Window所对应的布局参数
private final ArraySet<View> mDyingViews = new ArraySet<View>(); //存储正在被删除但是操作还未完成的View对象
下面用添加的例子来分析下Window和View的关系.
View的添加
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//1.检查参数合法性,如果是子Window还要去调整下布局参数设置token等操作
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;
//2. parentWindow一般来说都是Activity的Window
if (parentWindow != null) {
//3.调整布局,adjustLayoutParamsForSubWindow主要是设置token,如果是应用窗口,token可能为null,就会给他赋值mAppToken,这个token是Activity的attach()方法中传入的token
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
//4.从Views中寻找当前的View的index,如果>0,在将移除的View中停止移除
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
}
//5.层级的判断,如果这是一个子Window,那么找到它的父window,将来引用。这里的param是其父Window调用adjustLayoutParamsForSubWindow方法获取到的,本质作用是为了使用父窗口的token
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
//6,创建ViewRootImpl与view绑定,并且将View和ViewRootImpl添加到列表中()
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
//7,通过setView方法来更新界面并且完成Window添加的逻辑,内部调用requestLayout方法完成异步刷新请求
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
关键步骤的说明在注释中,在最后异步我们可以看到调用了ViewRootImpl的setView方法,重点传入了view和相应的param参数,setView方法的源码重要部分如下:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
int res;
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();//View的绘制流程
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
//创建InputChannel
mInputChannel = new InputChannel();
}
try {
//通过WindowSession进行IPC调用,将View添加到Window上,其中View信息包含在mAttachINfo中
//mWindow即W类,用来接收WmS信息
//同时通过InputChannel接收触摸事件回调
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
......
//处理触摸事件回调
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
......
}
执行requestLayout()方法完成View的绘制流程(下篇文章专门讲解),并且通过WindowSession将View和InputChannel添加到WMS中,从而将View添加到Window上并且接收触摸事件。
通过mWindowSession来完成Window的添加过程 ,mWindowSession的类型是IWindowSession,是一个Bindler对象,真正的实现类是Session,也就是Window的添加是一次IPC调用. mWindowSession在ViewRootImpl的构造函数中通过WindowManagerGlobal.getWindowSession()方法创建
同时将mWindow(即 W extends IWindow.Stub)发送给WMS,用来接收WMS信息。 如此一来,Window的添加请求就交给WMS去处理了,在WMS内部会为每一个应用保留一个单独的Session。WMS会创建一个WindowState对象用来表示当前添加的窗口。 WMS负责管理这里些 WindowState 对象。至此,Window的添加过程就结束了。
Token
下来简单说明下token这个东西,这个不知道不影响理解window的添加...
token的作用上面说过了,在这个流程中token是怎么体现的呢?在WindowManagerImpl中,有个applyDefaultToken的方法,将params的token设置为默认defaultToken(activity的attach方法中会搞一个这样的token,本质其实是一个ActivityRecord).在ViewRootImpl的构造方法中会为attachInfo设置token,并且在performTraversals会将自己的mAttachInfo关联到所有的子view,保证所有的view都能调到token.
如果子窗口没有父窗口或者父窗口也是子窗口,那么程序将会崩溃,token一定不能为空,如果我们在onCreate方法中调用popupWindow或者dialog的context不是activity的token,那么就会崩溃的.具体原因可以看看popupWindow的Window创建过程和dialog的window的创建过程