前置知识
Window是什么?Window是一个组件,View是由Window呈现出来的。Window实际上就是管理着View,对Window的操作最终都会转化成对View的操作。- 经常使用的
Window:Activity、Dialog、PopupWindow、Toast等。系统中常见的Window:StatusBar、NavigationBar、InputMethod(软键盘)等。StatusBar、NavigationBar等是在单独的进程中使用的。
问题
1.WindowManager.LayoutParam(继承ViewGroup.LayoutParams)类很重要,flags,type 等等。
type,相当于Window 的类型,主要分为3大类。
- 1-99对应应用Window(token 必须设置为Activity 的token,即Context需要设置为Activity)。Activity 中的Window 对应的type 是
TYPE_BASE_APPLICATION,Dialog默认的是TYPE_APPLICATION,WindowManager.LayoutParams默认构造函对应的type也是TYPE_APPLICATION。 - 1000-1999对应子Window。PopupWindow 默认是
TYPE_APPLICATION_PANEL。 - 2000-2999 对应的是系统Window。
Toast对应TYPE_TOAST,StatusBar对应TYPE_STATUS_BAR,NavigationBar对应TYPE_NAVIGATION_BAR,键盘对应着”TYPE_INPUT_METHOD”。
flags,各种不一样属性,控制Window 的一些特殊显示。
2.Dialog和PopupWindow的异同。
Dialog和PopupWindoow都会在一个新的Window中展示。Dialog不能使用ApplicationContext,上面就可以知道Dailog是应用Window,只能使用Activity。PopupWindow不能在Activity.onCreate中创建。PopupWindow默认是一个子Window,需要在Activity创建以后使用。PopupWindow一定需要设置width和height,默认是0。Dialog是创建了一个PhoneWindow然后获取的DecorView,所有默认有Title,而PopupWindow没有。PopupWindow默认不会响应Back键,可以设置popupWindow.setFocusable(true);。- 通过
Activity来管理Dialog的时候,Activity.mManagedDialogs,当Activity后台销毁的时候再次进入可以恢复(参考Dialog.onSaveInstanceState、Dialog.onRestoreInstanceState)。而PopupWindow没有恢复机制。
至于网上很多说PopupWindow是阻塞式的而Dialog是非阻塞式的,是非常误解人的。关于坑爹的PopupWindow的“阻塞”争议问题:Android没有真正的“阻塞式”对话框
3.Toast展示以后可以将Toast点击的事件传递到下面的View,Dialog以及Popupindow都不行,跟WindowManager.LayoutParams.flags有关。
4.Activity设置windowSoftInputMode属性的时候,会对Activity的Window有一些影响。
5.一个App 中有多少个Window?应该是某个状态下Window的个数,Activity+Dialog+PopupWindow+Toast+WindowManage.addView。
基本操作
WindowManager继承ViewManager,基本的操作如下。操作的实际上是View。WindowManagerImpl保存着一族对应的mViews、mRoots、mParams。对于每一个展示的Window实际上对应着mViews[k]、mRoots[k]、mParams[k]。具体可以看后面的流程分析。
|
|
流程分析
Window.setStatusBarColor流程(api 21以上)。
StatusBar、NavigationBar都是一个View展示在最上面和最下面??
1.Window.setStatusBarColor`。
|
|
2.DecorView.updateColorViews。从代码中看就是修改了mNavigationColorViewState和mStatusColorViewState里面包含的View的backgroundColor。
|
|
3.DecorView.updateColorViewInt
|
|
从setStatusBarColor(api 21以上)这个流程可以分析出来,Activity中DecoeView的StatusBar和NavigationBar对应的是一个普通的View(系统中的StatusBar和NavigationBar都对应着一个Window,StatusBar中展示的电量、wifi等都是Window中的View,NavigationBar中展示的返回键、Home键等也是Window中的View)。可以通过反射改变StatusBar颜色和大小来验证(看源码也行的)DecoeView对应着的是普通的View。
|
|
WindowManager.addView()流程。
1.执行到WindowManagerImpl.addView()(WindowManagerImpl继承WindowManager)。
|
|
2.执行到WindowManagerGlobal.addView() 方法。
|
|
一个WindowManager中对应着一系列的mViews、mRoots、mParams,也就是WindowManager管理着多个Window,每一个Window都对应着mViews[k]、mRoots[k]、mParams[k]。如下所示。
|
|
3.执行到ViewRootImpl.setView()方法。ViewRootImpl相当于View和ViewManager之间的桥梁。里面的代码比较的长,会执行到ViewRootImpl.requestLayout()。
4.执行到ViewRootImpl.requestLayout()方法以及mWindowSession.addToDisplay,其中ViewRootImpl.requestLayout()会接着执行到了ViewRootImpl.scheduleTraversals()。
|
|
5.执行到TraversalRunnable.run(),最后执行到了ViewRootImpl.doTraversal()。
|
|
6.执行到ViewRootImpl.performTraversals()。计算希望的大小,
判断是否是第一次,判断视图是否修改(例如:内容区域),判断Window 是否需要调整大小,判断是否改变了View 的可见性。最后会执行到ViewRootImpl.performMeasure() 、ViewRootImpl.performLayout()、ViewRootImpl.performDraw()
7.ViewRootImpl.performMeasure()会调用View.measure对View进行测量;ViewRootImpl.performLayout()会调用View.layout对View进行布局。ViewRootImpl.performDraw()会调用ViewRootImpl.draw,然后会调用到ViewRootImpl.drawSoftware,最后就会调用到View.draw。这一系列的操作完成以后就会相当于展示创建了一个View,大小、位置以及视图也完全处理完毕。
8.第4步中执行到ViewRootImpl.requestLayout()以后的过程已经分析完毕,相当于是对View 进行了测量、布局以及绘制。接下来执行mWindowSession.addToDisplay。mWindowSession对应的是一个IWindowSession.aidl,Java 层实现的类是com.android.server.wm.Session。
|
|
9.执行WindowManagerService.addWindow。这里就是Window的添加等,没有细看。
总结:通过WindowManager对Window的操作实际上就是对里面View的操作,展示的也是里面View的信息。
WindowManager.removeView()流程。
这里有一个问题需要考虑一下,通过Activity.getWindowManager().removeView(View view),这里removeView的参数是什么?是Activity中设置的View中的一个子View`吗?
addView第2步就有说明,这个View其实是一个整体的View,不是某个View中的子View,而是一个Window 中展示的View。
1.直接看到WindowManagerGlobal.removeView()。
|
|
2.执行WindowManagerGlobal.removeViewLocked
|
|
总结:removeView也是对View的操作。
PopupWindow显示流程
showAsDropDown(View anchor)/showAtLocation(View parent, int gravity, int x, int y) 到 preparePopup(WindowManager.LayoutParams p),最后invokePopup(WindowManager.LayoutParams p),这里不再分析。
Dialog显示流程
show(),这里不再分析。
其它内容
1.查看当前Window的信息。
|
|
2.adb shell dumpsys window 查看当前所有的Window信息,包括Window的大小、位置。下面一个例子,拿中Window #3 来展示mAttrs就展示了大小,mHasSurface展示了起始位置。WindownManage.LayoutParams.mTitle 对应了名称。
|
|
一个详细的例子。下面总共有9个Window(0,1,..8),可以看到NavigationBar,StatusBar(StatusBarWindowManager管理着StatusBar Window的)等其实都是Window。也非常好验证,将StatusBar向下滑动唤出全屏的通知栏,执行adb shell dumpsys window会发现StatusBar 变成了全屏(从mAttrs可以看出)。
PS:PhoneStatusBar.java控制着StatusBar和NavigationBar。详情可以查看源码frameworks.base.packages.SystemUI包下面的代码,包含了StatusBar、NavigationBar、截屏Window、锁屏Window等。
|
|
3.PhoneWindowManager.java拦截键盘消息(比如home操作、截屏操作等)的处理类。
4.通过Android Studio查看View Hierarchy等信息。分析布局更加全面。
|
|