1. Activity 的 Window 创建过程
Activity 中 Window 的创建过程与 Activity 的启动过程紧密相关。Activity 的启动过程很复杂,最终会由 ActivityThread 中的
performLaunchActivity()
方法来完成整个启动过程Activity 实现了 Window 的 Callback 接口,Callback 接口中的方法很多,常见的有
onAttachedToWindow()
、onDetachedFromWindow()
、dispatchTouchEvent()
等方法分析源码,Activity 的 Window 是通过 PolicyManager 的实现类 Policy 的
makeNewWindow()
方法创建的,由方法实现可知,Window 的具体实现类是 PhoneWindow:1
2
3public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}Activity 的视图附属在 Window 上的原理(即
setContenView()
方法的原理),因为 Window 的具体实现是 PhoneWindow,所以 PhoneWindow 的setContentView()
方法的主要步骤:1
2
3
4public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}如果没有 DecorView,那么就创建它
- DecorView 是一个 FrameLayout,而且是 Activity 中的顶级 View,一般来说它的内部包含标题栏和内部栏,但这个会随着主题的改变而发生改变
- 无论如何,内容栏是一定要存在的,并且内容栏有具体固定的 id,即
content
,它的完整 id 是android.R.id.content
将 View 添加到 DecorView 的 mContentParent 中
- 直接将 Activity 的视图添加到 DecorView 的 mContentParent 中:
mLayoutInflater.inflate(layoutResID, mContentParent);
- 因为 Activity 的布局文件只是被添加到 DecorView 的 mContentParent 中,因此不加
setView()
而是叫setContentView()
更准确
- 直接将 Activity 的视图添加到 DecorView 的 mContentParent 中:
回调 Activity 的
onContentChanged()
方法通知 Activity 视图已经发生改变由于 Activity 实现了 Window 的 Callback 接口,表示 Activity 的布局文件已经被添加到 DecorView 的 mContentParent 了,于是需要通知 Activity,使其可以做相应的处理
Activity 的
onContentChanged()
方法是个空实现,可以在子 Activity 中处理这个回调:1
2
3
4final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
然后在 ActivityThread 的
handleResumeActivity()
方法中,首先会调用 Activity 的onResume()
方法,接着会调用 Activity 的makeVisible()
方法,正是在makeVisible()
方法中,DecorView 真正地完成了添加和显示这两个过程,到这里 Activity 的视图才能被用户看到1
2
3
4
5
6
7
8void makeVisible() {
if (!mWindowAdded) {
ViewManager vm = getWindowManager();
vm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
2. Dialog 的 Window 创建过程
创建 Window
- Dialog 中 Window 的创建同样是通过 PolicyManager 的
makeNewWindow()
方法来完成的 - 创建后的对象实际上就是 PhoneWindow,这个过程和 Activity 的 Window 的创建过程是一致的
- Dialog 中 Window 的创建同样是通过 PolicyManager 的
初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中
这个过程和 Activity 的类似,都是通过 Window 去添加指定的布局文件:
1
2
3public void setContentView(int layoutResID) {
mWindow.setContentView(layoutResID);
}
将 DecorView 添加到 Window 中并显示
- 在 Dialog 的
show()
方法中,会通过 WindowManager 将 DecorView 添加到 Window 中 - 当 Dialog 被关闭时,它会通过 WindowManager 来移除 DecorView:
mWindowManager.removeViewImmediate(mDecor);
- 在 Dialog 的
需要注意的一点:普通的 Dialog 有一个特殊之处:必须采用 Activity 的 Context,如果采用 Applicaiton 的 Context 会报错:没有应用 token
应用 token 一般只有 Activity 拥有,所以这里需要使用 Activity 作为 Context 来显示对话框
另外,系统 Window 比较特殊,它可以不需要 token
1
2
3
4
5// WindowManager.LayoutParams 中的 type 表示 Window 的类型,系统 Window 的层级范围是 2000 ~ 2999
dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_OVERLAY);
// 在 AndroidManifest.xml 文件中声明权限从而可以使用系统 Window
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
3. Toast 的 Window 创建过程
- Toast 也是基于 Window 来实现的,但由于 Toast 具有定时取消这一功能,所以系统采用了 Handler
- 在 Toast 的内部有两类 IPC 过程,一类是 Toast 访问 NotificationManagerService,一类是 NotificationManagerService 回调 Toast 里的 TN 接口
- Toast 属于系统 Window,它内部的视图由两种方式指定,一种是系统默认的样式,一种是通过
setView()
方法来指定一个自定义 View - Toast 提供了
show()
和cancel()
方法分别用于显示和隐藏 Toast,这两个方法的内部都是一个 IPC 过程 - Toast 的显示和隐藏逻辑底层使用了 Handler 切换线程,所以 Toast 无法在没有 Looper 的线程中弹出,这是因为 Handler 需要使用 Looper 才能完成切换线程的功能
- 分析源码,对于非系统应用来说,mToastQueue 中最多能同时存在 50 个 ToastRecord,这样做是为了防止 DOS(Denial of Service)。如果不这么做,如果使用大量循环去连续弹出 Toast,这将会导致其他应用没有机会弹出 Toast,那么对于其他应用的 Toast 请求,系统的行为就是拒绝服务
- Toast 显示以后,NMS 还会通过
scheduleTimeoutLocked()
方法来发送一个延时消息,具体的延时时长取决于 Toast 的时长,LONG_DELAY 是 3.5s,SHORT_DELAY 是 2s - 除了 Activity、Dialog 和 Toast 之外,PopupWindow、菜单及状态栏都是通过 Window 来实现的,任何 View 都是附属在一个 Window 上面的