WindowManager 添加 Window(伪代码):
WindowManager.addView(view, layoutParams);
layoutParams 是 WindowManager.LayoutParams,其中 flags 和 type 两个参数比较重要。
Flags 表示 WIndow 的属性,三种常用的:
- FLAG_NOT_FOCUSABLE
表示 Window 不需要获取焦点,也不需要输入事件。
- FLAG_NOT_TOUCH_MODAL
当前 WIndow 区域之外的点击事件会传递给下面的 Window,一般都会开启。
- FLAG_SHOW_WHEN_LOCKED
让 Window 显示在锁屏上。
Type 表示 Window 的类型,有三种,每种 Window 都有不同的层级:
- 应用 Window (Activity) 层级:1-99
- 子 Window (Dialog) 层级:1000-1999
- 系统 Window (Toast 和 系统状态栏) 层级:2000-2999
不同层级范围对应着 WindowManager.LayoutParams 的 type 参数,有很多对应的值。若采用系统 Window, 要声明相应权限,如使用TYPE_SYSTEM_ERROR 就要声明< user-permission android:name=”android.permission.SYSTEM_ALERT_WINDOW” />。
WindowManager 常用的有三个方法,即添加VIew,更新VIew,删除View。它们定义在 ViewManager 中,WindowManager 继承了 ViewManager。
Window 是一个抽象概念,每一个 Window 都对应着一个 View 和 一个 ViewRootImpl。View 是 Window 的实体,通过 ViewRootImpl 与Window 建立联系。
添加、删除和更新都是通过这样的过程来完成的:
WindowManager(接口)→
WindowManagerImpl (类)→
WindowManagerGlobal (工厂)。
实际上通过 WindowManagerGlobal.addView() 来完成。
过程:
通过 ViewRootImpl 来更新界面并完成 Window 的添加过程
View 的绘制过程都是由 ViewRootImpl 来完成的,所以这里也由ViewRootImpl 的 setView 方法来完成,setView 内部会通过 requestLayout 来完成异步刷新请求。
public void requestLayout(){
if (!mHandlingLayoutInLayoutRequest){
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
实际上通过 WindowManagerGlobal.removeView() 来完成。
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IlleagalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);//找到要删除的 view 的索引
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);//删除
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to" + curView);
}
}
进一步的删除在 removeViewLocked() 中完成,代码如下:
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if(view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null){
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null){
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
root 的 die 方法根据参数 imediate 判断是异步删除还是同步删除。
- 异步:die 会发一个消息给 ViemRootImpl 的 Handler, Handler 会处理此消息并调用 doDie 方法。
- 同步:直接调用 doDie 方法。
若是异步,则 die 方法后 view 并没有被删除,view 将被放入 mDyingViews 中等待删除。
实际上通过 WindowManagerGlobal.updateViewLayout() 来完成。
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
updateViewLayout 方法比较简单,先更新 View 的 LayoutParams 并替代老的 LayoutParams,接着通过 ViewRootImpl 的 setLayoutParams 方法来更新 ViewRootImpl 的 LayoutParams。
Activity 的启动过程很复杂,最终会由 ActivityThread 中的 performLaunchActivity() 来完成整个启动过程,在这个方法内部会通过类加载器创建 Activity 的实例对象,并调用其 attach() 方法为其关联运行过程中所依赖的一系列上下文环境变量。
在 Activity 的 attach() 方法中,系统会通过 PolicyManager.makeNewWindow() 创建 Activity 所属的 Window 对象并为其设置回调接口。
实际中,PolicyManager 的真正实现是 Policy 类,Policy 类中的 makeNewWindow 会返回一个 PhoneView 对象。
大致过程如下(==> 为内部,—> 为之后):
performLaunchActivity() ===>
activity = mInstrumentation.newActivity(...) --->
activity.attach() ===>
mWindow = PolicyManager.makeNewWindow(this) ===>
return new PhoneWindow(context);
现在 Window 已经创建完成,下面分析 Activity 的视图是怎样附属在 Window 上的,大致过程如下:
Activity.setContentView() ==>
getWindow.setContentView() ==>
PhoneWindow.setContentView()
PhoneWindow.setContentView() 方法大致有以下步骤:
1. 如果没有 DecorView , 就创建它
2. 将 View 添加到 DecorView 的 mContentParent 中
3. 回调 Activity 的 onContentChanged 方法通知 Activity 视图已经发生变化
Dialog 的 Window 创建过程与 Activity 类似,有一下几个步骤:
1. 创建 Window
2. 初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中
3. 将 DecorView 添加到 Window 中并显示
Dialog 的 Window 创建过程与 Activity 很类似,几乎没什么区别。当 Dialog 被关闭,它会通过 WindowManager 来移除 DecorView:mWindowManager.removeViewImmediate(mDecor)(同步)。
普通的 Dialog 必须采用 Activity 的 Context,因为需要 Activity 的应用 Token(?)。
Toast 的 Window 的创建过程就比较复杂了。由于 Toast 具有定时取消这一功能,所以系统采用了 Handler。
Toast 提供了 show 和 cancel 分别用于显示和隐藏 Toast,它们内部是一个 IPC 过程。
Toast 的显示:
INotificationManager service = getService() --->
service.enqueueToast(pkg, tn, mDuration) ===>
将 Toast 请求封装为 ToastRecord 添加到 mToastQueue 队列中 --->
showNextToastLocked() ===>
ToastRecord.callback.show() (显示) --->
scheduleTimeoutLocked(record) (延时)
Toast 的隐藏与显示类似,也是通过 ToastRecord 的 callback 来完成的。
ToastRecord.callback.hide() (隐藏)
callback 实际上是 Toast 中的 TN 对象的远程 Binder,通过 callback 来访问 TN 中的方法是需要跨进程来完成的,最终被调用的 TN 中的方法会运行在发起 Toast 请求的应用的 Binder 线程池中。
所以,Toast 的显示和隐藏过程实际上是通过 Toast 中 TN 这个类来实现的,它有两个方法 show 和 hide,即显示和隐藏。由于这两个方法被 NotificationManagerService 以跨进程的方式调用的,因此它们运行在 Binder 线程池中。为了将执行环境切换到 Toast 请求所在线程,在它们内部使用了 Handler。