理解Window和WindowManager

Window是一个抽象类,它的具体实现是PhoneWindow。创建一个Window是很简单的事,只需要通过WindowManager即可完成。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。

一、Window和WindowManager

通过WindowManager添加Window:

将一个Button添加到屏幕为(100,300)的位置上

Flags参数表示Window的属性:

FLAG_NOT_FOCUSABLE

表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的Window。

FLAG_NOT_TOUCH_MODAL

在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法收到单击事件。

FLAG_SHOW_WHEN_LOCKED

开启此模式可以让Window显示在锁屏的界面上。

type参数表示Window的类型,Window有三种类型,分别是应用Window、子Window和系统Window。应用类Window对应着一个Activity。子Window不能单独存在,需要附属在特定的父Window之中,比如常见的Dialog。系统Window是需要声明权限在能创建的Window,比如Toast和系统状态栏。

Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window的上面。

应用Window的层级范围时1~99,子Window的层级范围时1000~1999,系统Window的层级范围是2000~2999,这些层级范围对应着WindowManager.LayoutParams的type参数

WindowManager所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager。

二、Window的内部机制

每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此Window并不是实际存在的,是以View的形式存在。

2.1 Window的添加过程

Window的添加过程需要通过WindowManager的addView来实现,WindowManager是一个接口,它的真正实现是WindowManagerImplement类。其三大操作:

可以发现,WindowManagerImpl并没有直接实现Window的三大操作,而是全部交给了WindowManagerGlobal来处理,WindowManagerGlobal以工厂的形式向外提供自己的实例。

WindowManagerGlobal的addView方法主要分为如下几步:

1、检查参数是否合法,如果是子Window那么还需要调整一些布局参数

2、创建ViewRootImpl并将View添加到列表中

在上面的声明中,mViews存储的是所有Window所对应的View,mRoots所存储的是所有Window所对应的ViewRootImpl,mParams存储的是所有Window所对应的布局参数,而mDyingViews则存储了那些正在被删除的View对象,或者说是那些已经被调用removeView方法但是删除操作还未完成的Window对象。在addView中通过如下方式将Window的一系列对象添加到列表中:

3、通过ViewRootImpl来更新界面并完成Window的添加过程

这个步骤由ViewRootImpl的setView方法来完成:

接着会通过WindowSession最终来完成Window的添加过程。

mWindowSession的类型是IWindowSession,它是一个Binder对象,真正的实现类是Session

在Session内部会通过WindowManagerService来实现Window的添加:

2.2 Window的删除过程

Window的删除过程和添加过程一样,都是先通过WindowManagerImpl后,再进一步通过WindowManagerGlobal来实现的。下面是WindowManagerGlobal的removeView的实现:

removeView通过findViewLocked来查找待删除的View的索引,这个查找过程就是建立的数组遍历,然后再调用removeViewLocked来做进一步的删除:

removeViewLocked是通过ViewRootImpl来完成删除操作的。在WindowManager中提供了两种删除接口removeView和removeViewImmediate,它们分别表示异步删除和同步删除。

removeView是由ViewRootImpl的die方法来完成。而die方法只是发送了一个请求删除的消息后就立刻返回了,这个时候View并没有完成删除操作,所以最好会将其添加到mDyingViews中,mDyingViews表示待删除的View列表。

在de方法内部只是做了简单的判断,如果是异步删除,那么就发送一个MSG_DIE的消息,ViewRootImpl中的Handler会处理此消息并调用doDie方法,如果是同步删除(立即删除),那么久不乏消息直接调用doDie方法,这就是两种删除方式的区别。在doDie内部会调用dispatchDetachedFromWindow方法,这个方法主要做四件事:

1、垃圾回收相关的工作,比如清除数据和消息、移除回调

2、通过Session的remove方法删除Window:mWindowSession.remove(mWindow),这同样是一个IPC过程,最终会调用WindowManagerService的removeWindow方法

3、戴傲勇View的dispatchDetachedFromWindow方法,在内部会调用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。当View从window中移除时,会调用onDetachedFromWindow,可在这个方法内部做一些资源回收的工作。

4、调用WindowManagerGlobal的doRemoveView方法刷新数据,,包括mRoots、mParams以及mDyingViews,需要将当前Window所关联的这三类对象从列表中删除。

2.3 Window的更新

查看WindowManagerGlobal的updateViewLayout方法:

首先需要更新View的LayoutParams并替换掉老的LayoutParams,接着在更新ViewRootImpl中的LayoutParams。接着再更新ViewRootImpl中的LayoutParams,这一步是通过ViewRootImpl的setLayoutParams方法来实现的。在ViewRootImpl中会通过scheduleTraversals方法来对View重新布局,包括测量、布局、重绘这三个过程。在通过WindowSession来更新Window的视图,这个过程是有WindowManagerService的relayoutWindow来具体实现。


三、Window的创建过程

3.1 Activity的Window创建过程

在Activity的启动过程中,会调用attach方法,这个方法会创建Activity所属的Window对象并为其设置回调接口,Window对象的创建时通过PolicyManager的makeNewWindow方法实现的,由于Activity实现了Window的Callback接口,所以当Window接收到外界的状态改变时就会回调Activity的方法。代码如下:

从上面可看出,Activity的Window是通过PolicyManager的一个工厂方法来创建的,但是从PolicyManager的类名可以看出,他不是一个普通类,它是一个策略类。PolicyManager中实现的几个工厂方法全部在策略接口中IPolicy中声明,IPolicy的定义如下:

而方法makeNewWindow实现如下:

分析Activity的视图是怎么附属在Window上:

由于Activity的视图由setContentView方法提供,我们只需要看setContentView方法的实现即可:

可看出Activity将具体实现交给了window处理,而Window的具体实现是PhoneWindow,所以只需看PhoneWindow的相关逻辑即可,其setContentView方法遵循如下步骤:

1、如果没有DecorView,那么就创建它

DecorView是Activity的顶级View,一般来说它的内部包含标题栏和内部栏,但是这个会随着主题的变换而发生改变,不管怎么样,内容栏是一定要存在的,并且内容来具体固定的id,那就是“content”,它的完整id是android.R.id.content。DecorView的创建过程由installDecor方法来完成,在内部会通过generateLayout方法来直接创建DecorView。

为了初始化DecorView的结构,PhoneWindow还需通过generateLayout方法来加载具体的布局文件到DecorView中,具体的布局文件和系统版本以及主题有关:

其中ID_ANDROID_CONTENT的定义如下,这个id所对应的ViewGroup就是mContentParent:

2、将View添加到DecorView的mContentParent中

直接将Activity的视图添加到DecorView的mContentParent中即可:没LayoutInflater.inflate(layoutResID,MContentParent)。

3、回调Activity的onContentChanged方法通知Activity视图已经发生改变

可以直接在Activity的onContentChanged方法是个空实现,可在子Activity中处理这个回调:

经过上面三个步骤,activity的布局文件已经成功添加到了DecorView的mContentParent中,但是这个时候DecorView还没有被WindowManager正式添加到Window中。只有在ActivityThread的handleResumeActivity方法中,首先会调用Aactivity的onResume方法,接着会调用Activity的makeVisible(),正是在makeVisible方法中,DecorView真正完成了添加和显示这两个过程:

3.2 Dialog的Window创建过程

Dialog的Window创建过程和Activity类似,有如下步骤:

1、创建Window

Dialog中Window的创建同样是通过PolicyManager的makeNewWindow方法来完成的:

2、初始化DecorView并将Dialog的视图添加到DecorView中

3、将DecorView添加到Window中并显示

在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中,如下所示:

当Dialog被关闭时,它会通过WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor).

普通的Dialog有一个特殊之处,那就是必须采用Activity的Context,如果采用Application的Context,那么就会报错。

3.3 Toast的Window创建过程

Toast也是基于Window来实现的,但是由于Toast具有定时取消这一功能,所以系统采用了Handler。

Toast内部有两类IPC过程,第一类是Toast访问NotificationManagerService(NMS),第二类是NotificationManagerService回调Toast里的TN接口。

Toast属于系统Window,它内部的视图由两种方式指定,一种是系统默认的样式,另一种是通过setView方法来指定一个自定义View,不管如何,他们都对应Toast的一个View类型的内部成员mNextView。其show与cancel方法实现如下:

显示和隐藏Toast都需要通过NMS来实现,由于NMS运行在系统的进程中,所以只能通过远程调用的方式来显示和隐藏Toast。而TN这个类,它是一个Binder类,在Toast和NMS进行IPC的过程中,当NMS处理Toast的显示或隐藏请求时会跨进程回调TN中的方法,这个时候由于TN运行在Binder线程中,所以需要通过Handler将其切换到当前线程中。

Toast的显示过程:

NMS的enqueueToast方法的第一个参数表示当前应用的包名,第二个参数tn表示远程回调,第三个参数表示Toast的时长。enqueueToast首先将Toast请求封装为ToastRecord对象并将其添加到一个名为mToastQueue的队列中。

当ToastRecord被添加到mToastQueue中后,NMS就会通过showNextToastLocked方法来显示当前的Toast。其中,Toast的显示是由TsatRecord的callback来完成的,这个callback实际上就是Toast中的TN对象的远程Binder,通过callback来访问TN中的方法是需要跨进程来完成的,最终被调用的TN中的方法虎运行在发起Toast请求的应用的Binder线程池中。

Toast显示以后,NMS还会通过scheduleTimeoutLocked方法来发送一个延时消息,具体的延时取决于Toast的时长:

在上面额代码证,LONG_DELAY是3.5s,而SHORT_DELAY是2s。延迟相应的时间后,NMS会通过cancelToastLocked方法来隐藏Toast并将其从mToastQueue中移除,这个时候如果mToastQueue中还有其他Toast,那么NMS就继续显示其他Toast。

Toast的隐藏也是通过ToastRecord的callback来完成:

Toast的显示和隐藏过程实际上是通过Toast中的TN这个类来实现的,它有两个方法show和hide,分别对应Toast的显示和隐藏。由于这两个方法是被NMS以跨进程的方式调用,因此它们运行在线程池中:

mShow和mHide是两个Runnable,分别调用了handleShow和handleHide方法,TN的handleShow中会将Toast的视图添加到Window中:

而NT的handleHide中会将Toast的视图从Window中移除:

推荐阅读更多精彩内容