什么是Window
一台Android手机屏幕上显示的内容就是由一个个Window组合而成的,顶部的状态栏是一个WIndow,底部的导航栏也是一个Window,中间自己的应用显示区域也是一块大Window,Toast,Dialog也都对应一个自己的WIndow。而Android中对这些Window的管理是通过一个框架的服务–WMS。
上面展示了通常状况下,屏幕中可见的 Window,其中Activity对应中间的那个Window,该 Window对应着DecorView,它也是Activity中的顶级View(它实际上是一个FrameLayout),DecorView包含了标题栏和内容类栏。状态栏和导航栏都对应于 SystemUI 这个系统App。
几个概念
- Window: 代表了屏幕上某块显示区域,用来承载 View
- WMS:Android框架层的一个服务进程,用来管理 Window
- Surface:对应一块屏幕缓冲区,每个 Window对应一个Surface
- Canvas:提供了一系列绘制接口,用来在Surface上进行绘制操作
- SurfaceFlinger: Android的一个服务进程,负责管理Surface
WindowManagerService
WindowManagerService就是位于Framework层的窗口管理服务,它的职责就是管理系统中的所有窗口。窗口的本质就是一块显示区域,在Android中就是绘制的画布: Surface。
WindowManagerService添加一个窗口的过程,其实就是WMS 为其分配一块surface的过程。根据对surface的操作类型可以将Android显示系统分为三个层次:
WindowManager和WMS的交互是一个IPC过程
Window分类
Window分为三类,且不同类的Window对应不同的层级
Window | 层级 |
---|---|
应用 WIndow | 1~99 |
子 Window | 1000~1999 |
系统 Window | 2000~2999 |
层级范围对应着 WindowManager.LayoutParams 的type参数,当我们使用系统层级时,需要声明权限
WindowManager使用
我们对 Window 的操作时通过 WindowManager 来完成的, WindowManager 是一个接口
1 | (Context.WINDOW_SERVICE) |
2 | public interface WindowManager extends ViewManager{ |
3 | //.... |
4 | } |
ViewManager.java
1 | public interface ViewManager{ |
2 | public void addView(View view,ViewGroup.LayoutParams params); |
3 | public void updateViewLayout(View view, |
4 | ViewGroup.LayoutParams params); |
5 | public void removeView(View view); |
6 | } |
这三个方法实际上也是 WindowManager对提供的主要功能
WindowManager的内部机制
在实际使用中无法直接访问Window,对Window的访问必须通过WindowManager,WindowManager提供的三个API都是针对View的,
注意,这里并不是说已经有个Window了,占了屏幕一块区域,然后把View添加到这个区域,这种理解是错误的,addView实际上就是向屏幕上添加一个View实体,这个View实体就是Window实体,Window的实体就是这个顶级View,如果你还想要更复杂的布局,再将布局添加到该顶级View即可。
WindowManager是一个接口,它的真正实现是WindowManagerInmpl类:
1 |
|
2 | public void addView(View view,ViewGroup.LayoutParams params){ |
3 | mGlobal.addView(view,params,mDisplay,mParentWindow); |
4 | } |
5 | |
6 |
|
7 | public void updateViewLayout(View view,ViewGroup.LayoutParams params){ |
8 | mGlobal.updateViewLayout(view,params); |
9 | } |
10 | |
11 |
|
12 | public void removeView(View view){ |
13 | mGlobal.removeView(view,false); |
14 | } |
可以看到,WindowManagerImpl并没有直接实现Window的三大操作,而是交给了 WindowManagerGlobal 来处理,下面以addView() 为例,分析一下WindowManagerGlobal的实现过程
1 | public void addView(View view, ViewGroup.LayoutParams params, |
2 | Display display, Window parentWindow) |
3 | { |
4 | if (view == null) { |
5 | throw new IllegalArgumentException("view must not be null"); |
6 | } |
7 | if (display == null) { |
8 | throw new IllegalArgumentException("display must not be null"); |
9 | } |
10 | if (!(params instanceof WindowManager.LayoutParams)) { |
11 | throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); |
12 | } |
13 | |
14 | final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; |
15 | if (parentWindow != null) { |
16 | parentWindow.adjustLayoutParamsForSubWindow(wparams); |
17 | } else { |
18 | // If there's no parent, then hardware acceleration for this view is |
19 | // set from the application's hardware acceleration setting. |
20 | final Context context = view.getContext(); |
21 | if (context != null |
22 | && (context.getApplicationInfo().flags |
23 | & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { |
24 | wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; |
25 | } |
26 | } |
27 | |
28 | ViewRootImpl root; |
29 | View panelParentView = null; |
30 | |
31 | synchronized (mLock) { |
32 | // Start watching for system property changes. |
33 | if (mSystemPropertyUpdater == null) { |
34 | mSystemPropertyUpdater = new Runnable() { |
35 | public void run() { |
36 | synchronized (mLock) { |
37 | for (int i = mRoots.size() - 1; i >= 0; --i) { |
38 | mRoots.get(i).loadSystemProperties(); |
39 | } |
40 | } |
41 | } |
42 | }; |
43 | SystemProperties.addChangeCallback(mSystemPropertyUpdater); |
44 | } |
45 | |
46 | int index = findViewLocked(view, false); |
47 | if (index >= 0) { |
48 | if (mDyingViews.contains(view)) { |
49 | // Don't wait for MSG_DIE to make it's way through root's queue. |
50 | mRoots.get(index).doDie(); |
51 | } else { |
52 | throw new IllegalStateException("View " + view |
53 | + " has already been added to the window manager."); |
54 | } |
55 | // The previous removeView() had not completed executing. Now it has. |
56 | } |
57 | |
58 | // If this is a panel window, then find the window it is being |
59 | // attached to for future reference. |
60 | if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && |
61 | wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { |
62 | final int count = mViews.size(); |
63 | for (int i = 0; i < count; i++) { |
64 | if (mRoots.get(i).mWindow.asBinder() == wparams.token) { |
65 | panelParentView = mViews.get(i); |
66 | } |
67 | } |
68 | } |
69 | |
70 | root = new ViewRootImpl(view.getContext(), display); |
71 | |
72 | view.setLayoutParams(wparams); |
73 | |
74 | mViews.add(view); |
75 | mRoots.add(root); |
76 | mParams.add(wparams); |
77 | |
78 | // do this last because it fires off messages to start doing things |
79 | try { |
80 | root.setView(view, wparams, panelParentView); |
81 | } catch (RuntimeException e) { |
82 | // BadTokenException or InvalidDisplayException, clean up. |
83 | if (index >= 0) { |
84 | removeViewLocked(index, true); |
85 | } |
86 | throw e; |
87 | } |
88 | } |
89 | } |
1.检查参数合法性,如果是子Window做适当调整
1 | if(view == null){ |
2 | throw new IllegalArgumentException("view must not be null"); |
3 | } |
4 | |
5 | if(display == null){ |
6 | throw new IllegalArgumentException("display must not be null"); |
7 | } |
8 | |
9 | if(!(params instanceof WindowManager.LayoutParams)){ |
10 | throw new IllegalArgumentException("Params must be |
11 | WindowManager.LayoutParams"); |
12 | } |
13 | |
14 | final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; |
15 | if(parentWindow != null){ |
16 | // layoutParams转换 |
17 | parentWindow.adjustLayoutParamsForSubWindow(wparams); |
18 | } |
2.创建ViewRootImpl并将View添加到集合中
在WindowManagerGlobal内部有如下几个集合比较重要:
1 | private final ArrayList<View> mViews = new ArrayList<View>(); |
2 | private final ArrayList<ViewRootImpl> mRoots = .. ; |
3 | private final ArrayList<WindowManager.LayoutParams> mParams = .. ; |
4 | private final ArrayList<View> mDyingViews = ....; |
集合 | 存储内容 |
---|---|
mViews | 所有Window对应的View |
mRoots | 所有Window对应的ViewRootImpl |
mParams | 所有Window对应的布局参数 |
mDyingViews | 正在被删除的View对象(也是WIndow) |
addView 操作会将相关对象添加到对应集合中
1 | root = new ViewRootImpl(view.getContext(),display); |
2 | view.setLayoutParams(wparams); |
3 | |
4 | mViews.add(view); |
5 | mRoots.add(root); |
6 | mParams.add(wparams); |
3.通过ViewRootImpl来更新界面并完成Window的添加过程
在学习View的工作原理时,我们知道View的绘制过程是由ViewRootImpl来完成的,这里也不例外,具体是通过ViewRootImpl的setView方法来实现的。在setView内部会通过requestLayout来完成异步刷新请求:
1 | public void requestLayout(){ |
2 | if(!mHandlingLayoutInLayoutRequest){ |
3 | checkThread(); |
4 | mLayoutRequested = true; |
5 | //绘制入口 |
6 | scheduleTraversals(); |
7 | } |
8 | } |
继续看 scheduleTraversals 实现
1 | res = mWindowSession.addToDisplay(mWindow,mSeq,mWindowAttributes,getHostVisiblity(), |
2 | mDisplay.getDisplayId(),mAttachInfo.mContentInsets,mInputChannel); |
mWindowSession的类型是 IWindowSession,它是一个 Binder 对象,真正的实现类是 Session,这也就是之前提到的 IPC 调用的位置。在 Session 内部会通过 WindowManagerService 来实现 Window 的添加,代码如下:
1 | public int addToDisplay(IWindow window, int seq, |
2 | WindowManager.LayoutParams attrs, int viewVisibility, |
3 | int displayId, Rect outContentInsets, InputChannel |
4 | outInputChannel) |
5 | { |
6 | return mService.addWindow(this, window, seq, attrs, viewVisibility, |
7 | displayId, outContentInsets, outInputChannel); |
8 | } |
终于,Window的添加请求移交给了WindowManagerService手上,在WMS内部会为每一个应用保留一个单独的session。
具体Window在WMS内部是怎么添加的,就不进一步分析了,至此,我们对WIndow的添加这一从 应用层 到 Framework 层的流程已经清楚,下面通过图示总结一下:
Window的创建过程
1. Activity的Window创建过程
Activity的window创建发生在attach里,系统会创建Activity所属的Windwo对象并为其设置回调接口
下面分析一下Activity的视图是怎么附属到window上的(PhoneWindow的具体实现):
1) 没有DecorView就创建一个
2) 将View添加到 DecorView的mContentParent中
3) 回调Activity的onContentChanged方法通知视图以经发生改变
经过上面三个步骤,DecorView以经被创建并初始化完毕,Activity地布局文件也已经成功添加到了DecorView的mContentParent中,但是这时候,DecorView还没有被WindowManger正式添加到window中。
在ActivityThread的handleResumeActivity方法中(AMS和AppThread IPC, AppThread和ActivityThread线程间通信),首先会调用Activity的onResume 方法,接着会调用Activity的makeVisible方法(),正是在makeVisible方法中,DecorView才真正地完成了显示过程,到这里Activity的视图才被用户看到
1 | void makeVisible(){ |
2 | if(!mWindowAdded){ |
3 | ViewManager wm = getWindowManager(); |
4 | wm.addView(mDecor,getWindow().getAttributes()); |
5 | mWindowAdded = true; |
6 | } |
7 | mDecor.setVisibility(View.VISIBLE); |
8 | } |
Dialog和Toast
Dialog的window的创建过程与Activity类似,同样是通过PolicyManager的makeNewWindow方法来完成,创建后的对象也是PhoneWindow,然后初始化DecorView并将Dialog的视图添加到DecorView中,最后将DecorView添加到Window中并显示。
Toast则不同,它的工作过程更显复杂,首先Toast也是基于Window来实现的,但是由于Toast具有定时取消这一功能,所以采用了Handler。在Toast内部有两类IPC过程,一是Toast访问NotificationManagerService,二是NotificationManagerService回调Toast里的TN接口
Toast属于系统Window,它内部的视图可以是系统默认样式,也可以通过setView方法自定义View,不论如何它们都对应Toast的内部成员mNextView。
Toast提供show和cancel分别用于显示和隐藏Toast,他们内部是一个IPC过程。其实系统分层的核心思想都是一致的,也就是高层隐藏细节,但是很多操作和调用,都是从上至下,再回到上的。细品一下代码
1 | public void show() { |
2 | if (mNextView == null) { |
3 | throw new RuntimeException("setView must have been called"); |
4 | } |
5 | |
6 | INotificationManager service = getService(); |
7 | String pkg = mContext.getOpPackageName(); |
8 | TN tn = mTN; |
9 | tn.mNextView = mNextView; |
10 | |
11 | try { |
12 | service.enqueueToast(pkg, tn, mDuration); |
13 | } catch (RemoteException e) { |
14 | // Empty |
15 | } |
16 | } |
17 | |
18 | public void cancel(){ |
19 | mTN.hide(); |
20 | try{ |
21 | getService().cancelToast(mContext.getPackageName(),mTN); |
22 | }catch(RemoteException e){ |
23 | //Empty |
24 | } |
25 | } |
可以看到,显示和隐藏Toast都需要通过NMS来实现,TN是一个Binder类,当NMS处理Toast的显示或隐藏请求时会跨进程回调TN中的方法。由于TN运行在Binder线程池中,所以需要Handler将其切换到当前线程(Toast请求的线程)。
代码在显示Toast中调用了NMS的enqueueToast方法,enqueeuToast方法在内部将Toast请求封装为ToastRecord对象并将其添加到一个名为mToastQueeu的队列中,对于非系统应用来说,mToastQueue中最多同时存在50个ToastRecord。
当ToastRecord被添加到mToastQueue后,NMS就会通过showNextToastLocked方法来顺序显示Toast,但是Toast真正的显示并不是在NMS中完成的,而是由ToastRecord的callback来完成的:
1 | void showNextToastLocked (){ |
2 | ToastRecord record = mToastQueue.get(0); |
3 | while(record != null){ |
4 | if(DBG) |
5 | Slog.d(TAG,"show pkg=" + record.pkg + "callback=" + record.callback); |
6 | try{ |
7 | record.callback.show(); |
8 | scheduleTimeoutLocked(record); |
9 | return; |
10 | } |
11 | ...... |
12 | } |
TN的意思是 TransientNotification .TN类继承自 ITransientNotification.Stub, 它是一个Binder对象。callback就是Toast中的TN对象的远程Binder,最终被调用的TN钟的方法会运行在发起Toast请求的应用的Binder线程池中。从以上代码也可以看出,Toast显示之后,NMS还调用了 sheduleTimeoutLocked方法,此方法会进行延时,延时完后,NMS会通过cancelToastLocked方法来隐藏Toast并将它从mToastQueue中移除,这时如果queeue中还有其它Toast,那么NMS就继续显示其它Toast,Toast的隐藏也是通过ToastRecord的callback来完成的,同样是一次IPC
过程。
从上面的分析看,NMS只是起到管理Toast队列及其延时效果,Toast的显示和隐藏实际上通过Toast的TN类来实现的,TN类的两个方法show和hide,是被NMS以跨进程的方式调用的,因此他们运行在Binder线程池中,为了将执行环境切换到Toast所在的线程,在它们内部使用了Handler。
Toast毕竟是在Window中实现的,因此他最终还是要依附于WindowManager,TN的handleShow:
1 | mWM=(WIndowManager)context.getSystemService(Context.WINDOW_SERVICE); |
2 | mWM.addView(mView,mParams); |
好像在某个API级别之后,系统拒绝用户创建系统级别的窗口,所以通过申请permission已经不可行
为什么需要用NotificationManagerService? // 非官方,先权当参考下
Toast的效果要求浮在所有的视图(包括Activity、Dialog)最上方,以及其他应用(非本应用进程)的上方,所以要用系统级别的Window。虽然普通应用没有权限使用系统级window,但是普通应用有权限获得NMS的对象。所以就委托NMS来代为创建系统级window来实现Toast。