Android Tech And Thoughts.

Window

Word count: 3kReading time: 12 min
2019/12/19 Share

什么是Window

一台Android手机屏幕上显示的内容就是由一个个Window组合而成的,顶部的状态栏是一个WIndow,底部的导航栏也是一个Window,中间自己的应用显示区域也是一块大Window,Toast,Dialog也都对应一个自己的WIndow。而Android中对这些Window的管理是通过一个框架的服务–WMS。

可见的WIndow.png

上面展示了通常状况下,屏幕中可见的 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显示系统分为三个层次:
window.png
WindowManager和WMS的交互是一个IPC过程

Window分类

Window分为三类,且不同类的Window对应不同的层级

Window 层级
应用 WIndow 1~99
子 Window 1000~1999
系统 Window 2000~2999

层级范围对应着 WindowManager.LayoutParams 的type参数,当我们使用系统层级时,需要声明权限

WindowManager使用

我们对 Window 的操作时通过 WindowManager 来完成的, WindowManager 是一个接口

1
@SystemService(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
@Override
2
public void addView(View view,ViewGroup.LayoutParams params){
3
	mGlobal.addView(view,params,mDisplay,mParentWindow);
4
}
5
6
@Override
7
public void updateViewLayout(View view,ViewGroup.LayoutParams params){
8
	mGlobal.updateViewLayout(view,params);
9
}
10
11
@Override
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
                    @Override 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.png

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中并显示。
dialog.png

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。

Thanks

带你彻底理解Window和WindowManager

CATALOG
  1. 1. 什么是Window
  2. 2. 几个概念
  3. 3. WindowManagerService
  4. 4. Window分类
  5. 5. WindowManager使用
  6. 6. WindowManager的内部机制
    1. 6.1. 1.检查参数合法性,如果是子Window做适当调整
    2. 6.2. 2.创建ViewRootImpl并将View添加到集合中
    3. 6.3. 3.通过ViewRootImpl来更新界面并完成Window的添加过程
  7. 7. Window的创建过程
    1. 7.1. 1. Activity的Window创建过程
    2. 7.2. Dialog和Toast
    3. 7.3. Thanks