几个概念:
Window是一个窗口的概念。
Window是一个抽象类,它的具体实现是PhoneWindow。可以通过WindowManager来完成Window的创建。
Window的具体实现位于WindowMangerService中,WindowManager和WindowManagerService的交互是一个IPC 过程。
Android中的所有的视图是通过Window来呈现的。不管是Activity、Dialog、还是Toast,它们的视图实际上都是附加在Window上的,因此Window实际是View的直接管理者。
Window和WindowManager
WindowManager.LayoutParams中的flags和type。
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 也有层级的概念,应用Window的层级范围是1~99,子Window的层级范围是1000~1999,系统Window的层级范围是2000~2999。
WindowManger( extends ViewManager)三大功能
添加View(addView)
更新View(updateViewLayout)
删除View(removeView)
Window的内部机制
Window 和View 通过ViewRootImpl来建立联系。
Window的添加过程
WindowManager的真正实现是WindowManagerImpl,WindowManagerImpl委托给WindowManagerGlobal,
WindowManagerGlobal的addView方法主要分为如下几步:(具体分析看书上的源码)
1.检查参数是否合法,如果是子Window那么需要调整一些布局参数。
2.创建ViewRootImpl并将View添加到列表中。
mViews(Window 对应的Views)
mRoots(Window对应的ViewRootImpls)
mParams(所有Window的布局参数)
mDyingViews(正在被删除的Views)
3.通过ViewRootImpl来更新界面并完成Window的添加过程。
ViewRootImpl.setView()---->
ViewRootImpl.requestLayout()---->
ViewRootImpl.scheduleTraversals()
4.接着通过WindowSession最终来完成Window的添加过程,WindowSession内部通过WindowManagerService来实现Window的添加,Window的添加是一个IPC过程。
WindowSession.addToDispaly()---->
WindowManagerService.addWindow()
在WindowManagerService内部会为每一个应用保留一个单独的Session。
Window的删除过程
WindowMangerImpl.removeView()---->
WindowMangerGlobal.removeView()---->
WindowMangerGlobal.removeViewLocked(int index=WindowMangerGlobal.findViewLocked())---->
ViewRootImpl.die()---->ViewRootImpl.doDie()---->
ViewRootImpl.dispatchDetachedFromWindow()
dispatchDetachedFromWindow方法主要做了四件事:
(1)垃圾回收相关的工作,比如清除数据和消息,移除回调。
(2)通过Session 的remove方法删除Window:mWindowSession.remove(mWindow),这同样是一个IPC过程,最终会调用WindowManagerService的removeWindow方法。
(3)调用View的dispatchDetachedFromWindow方法,在内部会调用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。对于onDetachedFromWindow()大家一定不陌生,当View从Window中移除时,这个方法就会被调用,可以在这个方法内部做一些资源回收的工作,比如终止动画、停止线程等。
(4)调用WindowManagerGlobal的doRemoveView方法刷新数据,包括mRoots.mParams以及mDyingViews,需要将当前Window所关联的这三类对象从列表中删除。
Window的更新过程
WindowManagerGlobal.updateViewLayout()----->
ViewRoot
Impl.setLayoutParams()---->
ViewRootImpl.scheduleTraversals()---->
(measure,layout,draw)---->
IPC调用WindowMangerService.relayoutWindow();
Window的创建过程
1.Activity的Window创建的过程
调用链:
ActivityThread.performLaunchActivity()---->
Activity.attach()---->
PolicyManager.makeNewWindow()
Activity的启动过程很复杂,最终会由ActivityThread中的performLaunchActivity()来完成整个启动过程。
在Activity的attach方法中,创建PhoneWindow对象和为其设置回调接口,比如onAttachedToWindow,onDetachedFromWindow,dispatchTouchEvent.
到这里,Window 已经创建完成了。下面分析Activity的视图是怎么附属在Window上的。
Activity.setContentView()---->
PhoneWindow.setContentView()
PhoneWindow大致遵循以下几个过程:
1。如果没有DecorView, 那么就创建它。
为了初始化DecorView 结构,PhoneWindow还需要通过generateLayout 方法来加载具体的布局文件到DecorView中,具体的布局文件和系统版本以及主题有关。
DecorView.generateDecor()---->
DecorView.generateLayout()
2.将View添加到DecorView的mContentParent中
3.回调Activity的onContentChanged方法通知Activity视图已经发生改变。
调用onContentChanged
经过以上三个步骤,含有Content 的DecorView已经创建完成,但是这个时候DecorView还没被WindowManager正式添加到Window中。还要经过以下流程:
ActivityThread.handleResumeActivity()---->
回调Activity.onResume()---->
Activity.makeVisible()----->
WindowManager.addView(mDecor)
2.Dialog的Window创建过程
Dialog的Window的创建过程和Activity类似,有如下几个步骤:
1.创建Window
PolicyManager.makeNewWindow()
2.初始化DecorView并将Dialog的视图添加到DecorView中
Dialog.setContentView()---->
PhoneWindow.setContentView()
3.将DecorView添加到Window中并显示(WindowManager.addView(mDecor) ),当Dialog被关闭时,它会通过WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor)
普遍的Dialog有一个特殊之处,那就是必须要采用Activity的context,如果使用Application的Context,就会报没有应用token的Exception,因为应用token一般只有Activity才有,如果使用系统Window 就没有上述问题,不过要修改Window type和加入相应权限。
3.Toast的Window创建过程
Toast属于系统Window
public void show(){
if(mNextView == null){
throw new RuntimeException("setView must have been called");
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try{
service.enqueueToast(pkg,tn,mDuration);
} catch (RemoteException e){
//Empty;
}
}
}
public void cancel(){
mTN.hide();
try{
getService().cancelToast(mContext.getPackageName(),mTN);
} catch (RemoteException e){
// Empty
}
}
从上面的代码可以看到,显示和隐藏Toast都需要NMS来实现,由于NMS运行在系统的进程中,所以只能通过远程调用的方式来显示和隐藏Toast。需要注意的是TN这个类,它是一个Binder类,在Toast和NMS进行IPC的过程中,当NMS处理Toast的显示或隐藏请求时会垮进程回调TN中的方法,这个时候由于TN运行在Binder线程池中,所以需要通过Handler将其切换到当前线程中,所以这意味着Toast无法在没有Looper的线程中弹出,这是因为Handler需要使用looper才能完成切换线程的功能。
先看NMS的Toast的显示过程到隐藏过程:
NMS.enqueueToast()---->
NMS.showNextToastLocked()---->
(TN)callback.show()、scheduleTimeoutLocked()--handler-->
NMS.cancelToastLocked()---->
callback.hide()
enqueueToast首先将Toast请求封装为ToastRecord对象并将其添加到一个名为mToastQueue的队列中。mToastQueue其实是一个ArrayList。对于非系统应用来说,mToastQueue中最多能同时存在50个ToastRecord,这样做是为了防止DOS(Denial of Service)。
showNextToastLocked方法中调用callback.show(),整个callback实际上就是Toast中的TN对象的远程Binder,通过callback来访问TN中的方法是需要跨进程来完成的,最终被调用的TN中的方法会运行在发起Toast请求的应用的Binder线程池中。
Toast的隐藏也是通过ToastRecord的callback来完成的,这同样也是一次IPC过程。
通过上面的分析,Toast的显示和影响过程实际上是通过Toast中的TN这个类来实现的,它有两个方法show和hide,分别对应Toast的显示和隐藏。由于这两个方法是被NMS以跨进程的方式调用的,因此它们 运行在Binder线程池中。为了将执行环境切换的Toast请求所在的线程,在它们的内部使用了Handler。
//schedule handleShow into the right thread
@Override
public void show(){
if(localLOG) Log.v(TAG."SHOW: "+this);
mHandler.post(mShow);
}
//schedule handleHide into the right thread
@Override
public void hide(){
if(localLOG) Log.v(TAG."HIDE: "+this);
mHandler.post(mHide);
}
上述代码中,mShow和mHide是两个Runnale,它们内部分别调用了handleShow和撼动了Hide方法,由此可见,handleShow和handleHide才是真正完成显示和隐藏Toast的地方。TN的handleShow中会将Toast的视图添加到Window中
callback.show()----->
Handler.post(mShow)---->
TN.handleShow()--->
WindowManager.addView();
callback.hide()----->
Handler.post(mHide)---->
TN.handleHide()---->
WindowManager.removeView();