这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战
前言
在第一篇文章,知道了一个App的视图层级,并且分析了我们写的XML是如何添加到content中的,知道了顶级视图DecorView和PhoneWindow的关系。
在第二篇文章里,介绍了Window的addView,removeView,upDateViewLayout三个重要方法。知道了View是由ViewRootImpl控制绘制,Window是由WMA控制显示到窗口中。
这篇文章,我将再次窥探一下Window。以下常量定义在接口WindowManager中
不同类型的Window
Window有三个分类。
Window | 层级 |
---|---|
应用Window | 1-99 |
子Window | 1000~1999 |
系统Window | 2000-2999 |
想来这个分级大家也经常看到吧。我们知道Activity用的就是应用应用Window,然后我们自己写的弹窗,是子Window,子Winodw必须依赖在一个应用Window上才可以显示出来,要不然怎么叫子Window呢,比如我们的的弹窗,其次是系统Window,这个一般是系统级别的使用,比如我们的toast,我们当然也可以使用啦。
下面我在代码里论证一下,这个1-99 ,1000-1999是咋定义的。
android.view.WindowManager
@WindowType
public int type;
应用Window
/**
* Start of window types that represent normal application windows.
*/
public static final int FIRST_APPLICATION_WINDOW = 1;
*/
public static final int TYPE_APPLICATION = 2;
public static final int TYPE_APPLICATION_STARTING = 3;
public static final int TYPE_DRAWN_APPLICATION = 4;
/**
* End of types of application windows.
*/
public static final int LAST_APPLICATION_WINDOW = 99;
下面是子Window
/**
* Start of types of sub-windows. The {@link #token} of these windows
* must be set to the window they are attached to. These types of
* windows are kept next to their attached window in Z-order, and their
* coordinate space is relative to their attached window.
*/
public static final int FIRST_SUB_WINDOW = 1000;
......
/**
* End of types of sub-windows.
*/
public static final int LAST_SUB_WINDOW = 1999;
下面的是系统Window
/**
* Start of system-specific window types. These are not normally
* created by applications.
*/
public static final int FIRST_SYSTEM_WINDOW = 2000;
/**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
......
复制代码
我们看到对于应用Window就定义了5个,可没有从1定义到99。当然我只要设置的值在1-99之间,这个Window就是应用Window类型了。
不知道大家有没有写过类似悬浮球的应用,然后可以也可以跟随手指移动更新位置,最早见到这种我是在苹果手机上看到的。一开始以为很难,其实就是通过上一篇提到的upDateViewLayout传入坐标系参数就可以完成了,非常简单。当然如果想要定义成在切换到其他App的时候,悬浮球还存在,我们就需要申请悬浮窗权限,并且将悬浮球Window设置为TYPE_SYSTEM_ALERT。
视图分析
我们在一个简单的Demo上,弹起一个弹窗,看看视图层级会是哪样的。
先看一张手机截图的
再看一下AS 视图分析的
在Activity的界面里,我弹起一个弹窗,同时也弹出一个toast,然后打开了个第三方的悬浮窗时间的APP。在视图分析里,因为AS的分析工具只能分析当前进程,所以只我们看到了右边有2个DecorView,也就是2个PhoneWindow。
在同一个屏幕里,是会出现多个Window的,而管理这些Window的,就是WindowManagerService
再看WindowManagerService,WindowManagerGlobal,ViewRootImpl
WindowManagerGlobal
之前说到attach内的wm.addView。方法最后执行处是在WindowManagerGlobal,内部还有几个变量。
android.view.WindowManagerGlobal
负责和WMS通信
private static IWindowSession sWindowSession;
当前应用内所有View
private final ArrayList<View> mViews = new ArrayList<View>();
当前应用内所有ViewViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
当前应用内所有View对应的参数
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
复制代码
sWindowSession。
WindowManagerGlobal本身就是单例对象,sWindowSession又被static修饰。毫无意义,一个application就只有一个sWindowSession对象。
通过上一篇的分析,ViewRootImpl会通过sWindowSession去通知WMS对屏幕相关的显示管理。
但是这个sWindowSession到底是什么类型呢?
@UnsupportedAppUsage
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
// Emulate the legacy behavior. The global instance of InputMethodManager
// was instantiated here.
// TODO(b/116157766): Remove this hack after cleaning up
InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
IWindowManager windowManager = getWindowManagerService();
关键代码
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
复制代码
我们知道windowManager其实就是WindowManageService。
com.android.server.wm.WindowManageService
@Override
public IWindowSession openSession(IWindowSessionCallback callback) {
return new Session(this, callback);
}
复制代码
sWindowSessionq原来是Session类。
mViews,mRoots,mParams
上一篇对mViews的描述是存储了所有Window所对应的View。这是我《Android开发艺术探索》看到的描述,第一次看到这句话的时候,我可能会对这句话有一些过多的思考。
- mViews存储的是所有Window所对应的DecorView
- mViews存储的是所有Window所对应的所有View
这其实细想还是有区别的。比如说我简单的Demo里,一个Actvity里有一个TextView,一个ImgView。 对于第一种理解,那mViews的大小是1,而对于第二种理解,那mViews就不只是1了,有TextView,有ImgView,还有id为content的FrameLayout等等等等。
mViews有两个操作,添加和删除。
android.view.WindowManagerGlobal
添加
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
......
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
......
}
删除
void doRemoveView(ViewRootImpl root) {
boolean allViewsRemoved;
synchronized (mLock) {
final int index = mRoots.indexOf(root);
if (index >= 0) {
mRoots.remove(index);
mParams.remove(index);
final View view = mViews.remove(index);
mDyingViews.remove(view);
}
allViewsRemoved = mRoots.isEmpty();
}
}
复制代码
这两个方法在上一篇分析过,只不过重点不在mViews,mRoots,mParams,现在再来看一看就明白了。mViews,mRoots,mParams这三兄弟的添加和操作永远保持一致,也就是说数组的长度都是一样的,一一对应的。比如mViews第二个DecorView,他的布局参数存在mParams的第二个元素里,他的ViewRoot也在mRoots的第二个元素里存着。
那也就是说WindowManagerGlobal只对Window的DecorView做管理,内部嵌套了多少层,是内部自己的事,他不关心,也关心不过来。 mViews存储的就是所有Window所对应的DecorView。DecorView又对应着一个Window,所以WindowManagerGlobal管理着Window
mDyingViews
mDyingViews这个就好理解了,立马就是一些将要被删除DecorView
简单了解ViewRootImpl
在setView,方法开始,他会requestLayout刷新布局
android.view.ViewRootImpl
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
复制代码
在checkThread方法里,我们看到了熟悉的报错:不能在子线程更新UI
scheduleTraversals之前也提供是View绘制的入口,内部会触发 View 绘制流程:measure –> layout –> draw。然后内部通过WindowSession与WMS通信,完成界面的渲染流程。
关于ViewRootImpl具体的解析不在当前系列中,后面对View系统的分析再做详解。
简单了解WindowManagerService
不知道大家有没有一种感觉,虽然各种很多地方好像看起来操作的是Window,但是最后实际操作的对象还是View,或者是Window下的DecorView。因为Window是一个抽象的存在,实际上显示的是他的DecorView。所以WMS是管理当前状态下,哪个View在最上层显示,所以他其实是也是管理View,并非Window,但是View又在于Window中。所以会给人一种WMAS其实管理的是Window。他管理的其实是属于哪个Window下的DecorView罢了
com.android.server.wm.WindowManagerService
final ArraySet<Session> mSessions = new ArraySet<>();
复制代码
WindowManagerService内部有一个Sessions列表,联系上之前ViewRootImpl与WindowManagerService通信用到的sWindowSessionq似乎都联系上了。
WMS内部维护了个Session的集合,与对应的Application进行通信
至于WMS是如何控制屏幕显示哪个View的就不说了,也不说啥不能陷入代码的森林。主要原因还是我也只迷迷糊糊的知道个大概。等猴年马月我自己吃透了,我再出来献丑。
WindowManagerService(WMS)和ActivityManagerService(AMS)是Android中FrameWork即为重要的一环。
总结
- 初始化Activity的时候,在其attach方法会创建一个PhoneWindow,与Activity绑定,并且将PhoneWindow和WindowManger,这里有2个绑定操作。
- 在setContent的时候,会为PhoneWindow创建默认的DecorView,经过一些系列方法调用后,选择一个与Theme匹配的布局给DecorView,同时把我们写XML放到其内部一个id为Content的FrameLayout中
以上是视图层级初始化梳理,下面是视图渲染的梳理.由handleResumeActivity方法下wm.addView开始
- addView最后是由WindowManagerGlobal触发
- WindowManagerGlobal是个单例,内部有个当前Application与WMS通信的Session静态变量,内部还有存储当前Application下所有Window对应DecorView的集合有mViews,,当然还mRoots,mParams
- ViewRooyImpal负责View的绘制同时通过Session与WMS进行通信
- WMS和AMS一样是系统服务,WMS主要负责窗口的管理,当然还有我文中没有提到的事件分发。