WMS
WMS 基础
Window
见: [[Window]]
WMS 职责?
1、窗口管理
WMS 是窗口的管理者,它负责窗口的启动、添加和删除,另外窗口的大小和层级也是由 WMS 进行管理的;窗口管理的核心成员有 DisplayContent、WindowToken 和 WindowState。
2、窗口动画
窗口动画由 WMS 的动画子系统来负责,动画子系统的管理者为 WindowAnimator。
3、输入系统的中转站
InputManagerService 会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS 是窗口的管理者,它作为输入系统的中转站再合适不过了
4、Surface 管理
窗口并不具备绘制的功能,因此每个窗口都需要有一块 Surface 来供自己绘制,为每个窗口分配 Surface 是由 WMS 来完成的。而 SurfaceFlinger 会将 WMS 维护的 Surface 按一定次序混合后显示到屏幕上。
WMS 启动流程
SystemServer.startOtherServices() →
WindowManagerService.main →
通过 runWithScissors() 方法在 android.display 线程初始化 WMS
如何理解 system_server、android.display 和 android.ui 三个线程之间的关系?
ViewRootImpl
什么是 ViewRootImpl?
ViewRootImpl 是 View 和 WindowManager 的纽带。
ViewRootImpl 作用
ViewRootImpl 作为连接 WindowManager 和 DecorView 的纽带,同时实现了 ViewParent 接口,ViewRootImp 作为整个控件树的根部,它是 View 树正常运作的动力所在,控件的测量、布局、绘制以及输入事件的分发都由 ViewRootImpl 控制。
- 一个 Window 对应一颗 View 数,对应一个 ViewRootImpl 实例
- ViewRootImpl 控制 View 的 measure、layout 和 draw
- ViewRootImpl 控制输入事件分发
ViewRootImpl 何时被创建?
WindowManagerGlobal.addView 的时候
1
2
3
4
5
6
7
8
9
ActivityThread#handleLaunchActivity() →
ActivityThread#performLaunchActivity() →
ActivityThread#handleResumeActivity() →
ActivityThread#performResumeActivity() →
Activity#onResume()/makeVisible() →
WindowManager#addView(View view/DecorView/, ViewGroup.LayoutParams params) →
WindowManagerImpl#addView() →
WindowManagerGlobal#addView()(new ViewRootImpl)→
ViewRootImpl#setView(View view, WindowManager.LayoutParams attrs, View panelParentView) →
WMS 相关问题
从 Activity 创建到 View 呈现中间发生了什么?
Activity、Window、WindowManager、DecorView、ViewRootImpl 作用
- Activity Activity 像是一个指挥官,它不处理具体的事务,只在适当的时候指挥 Window/WindowManager 工作。例如:在 attach 时创建 Window 对象、onResume 后通知 WindowManager 添加 view。
- Window Window 是一个窗口,它是 View 的容器。Android 中的视图以 View 树的形式组织在一起,而 View 树必须依附在 Window 上才能工作。一个 Window 对应着一个 View 树。启动 Activity 时会创建一个 Window,显示 Dialog 时也会创建一 Window。因此 Activity 内部可以有多个 Window。由于 View 的测量、布局、绘制只是在 View 树内进行的,因此一个 Window 内 View 的改动不会影响到另一个 Window。Window 是一个抽象类,它只有一个实现类 PhoneWindow。
- WindowManager WindowManager 是 Window 的管理类。它不直接操作 Window,而是操作 Window 内的 DecorView。WindowManager 是一个接口。它的具体实现类是
WindowManagerImpl
。 - DecorView 是 View 树的顶级 View,它是 FrameLayout 的子类。根据 Activity 设置的 Theme,DecorView 会有不同布局。但无论布局怎么变,DecorView 都有一个 Id 为 R.id.content 的 FrameLayout。Activity.setContentView() 方法就是在这个 FrameLayout 中添加子 View。
ViewRootImpl
是连接 WindowManager 和 DecorView 的纽带,View 的三大流程均是通过ViewRootImpl
来完成的。
从 Activity 创建到 View 呈现中间发生了什么?
Activity 创建
ActivityThread.handleLaunchActivity
我们通过 startActivity 时,如果目标 Activity 的进程未创建,AMS 会通过 Socket 通知 Zygote 进程 fork 出应用进程;然后一堆调用链,最终会调用到 ActivityThread 的 handleLaunchActivity()
方法
1
2
3
4
5
6
7
8
9
10
11
12
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// ...
// performLaunchActivity
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
// handleResumeActivity
handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed);
// ...
}
}
handleLaunchActivity() 主要调用了两个方法:performLaunchActivity() 和 handleResumeActivity()
- performLaunchActivity:完成 Activity 的创建,以及调用 Activity 的 onCreate() 和 onStart() 方法。
- 创建 Activity,通过反射
- 创建 Context
- 调用 Activity.attach(),创建 PhoneWindow;关联 WindowManager
- 调用 Activity.onCreate()
- 调用 Activity.onStart()
- handleResumeActivity:调用 Activity 的 onResume() 方法,处理 View 的呈现
PhoneWindow.setContentView 创建 DecorView
在 Activity.onCreate 会调用 setContentView 方法,它是调用的 PhoneWindow 的 setContentView 方法,主要是根据不同的 Activity Theme 初始化 DecorView,加载不同的布局。
handleResumeActivity 创建 Activity
- 通过 performResumeActivity() 处理 Activity 的 onRestart onResume 的生命周期。
- 将 DecorView 设置为 InVisible
- 通过 WindowManager.addView() 将 DecorView 绘制完成
- 将 DecorView 设置为 Visible
- IPC 通知 AMS Activity 启动完成
WindowManager.addView
- WindowManagerImpl.addView 会调用 WindowManagerGlobal.addView()
- 先创建 ViewRootImpl,随后把 View、ViewRootImpl、LayoutParams 都保存在各自的 List 中,以供将来更新 UI 使用;调用 ViewRootImpl.setView() 方法
- ViewRootImpl.setView() 会调用 requestLayout() 方法;requestLayout() 方法首先会检查当前执行的线程是不是 UI 线程,随后调用 scheduleTraversals()。scheduleTraversals 会把本次请求封装成一个 TraversalRunnable 对象,这个对象最后会交给 Handler 去处理。最后 ViewRootImpl.performTraversals() 被调用;performTraversals() 主要是处理 View 树的 measure、layout、draw 等流程
ViewRootImpl.scheduleTraversals
- ViewRootImpl.scheduleTraversals 方法中会发送一个同步屏障,目的是执行异步消息,阻塞同步消息,直到同步屏障被移除,让 TraversalRunnable 更快地被执行
- 然后通过 Choreographer postCallback 会通过 Handler 发一个异步消息,action 是 TraversalRunnable
- 而 TraversalRunnable 里面的逻辑就是 View 的 measure/layout/draw 等逻辑
为什么要有设计 Window?
- 假如没有 Window,那 Window 管理 View 树的代码必然会放到 Activity 中。这样 Activity 就变得十分庞大,这与我们前面说的 Activity 指挥官的角色相违背。
- 把 View 树的管理工作封装到 Window 后,在调用 Dialog.show()、Dialog.hide() 等 Window 切换时,Activity 只需要负责 Window 的显示和隐藏即可。
- View 的测量、布局、绘制只是在 View 树内进行的,把一个 View 树封装在一个 Window 中方便视图管理。
Window、View 和 ViewRootImpl
每个 Window 对应一个 ViewTree,其根节点是 ViewRootImpl,ViewRootImpl 自上而下地控制着 ViewTree 的一切(绘制、事件和 UI 更新)
子线程真的不能更新 UI 吗?
主线程、UI 线程和子线程概念?
UI 线程: 创建 ViewRootImpl
的线程,最终执行 View 的 measure/layout/draw 等 UI 操作的线程;且需是 Looper 线程
主线程: 创建 ActivityThread 的线程;主线程也是 UI 线程;也是 Looper 线程
子线程:非 UI 线程和主线程的子线程,可能是 Looper 线程
子线程能 requestLayout?
- 在 ViewRootImpl 还没创建出来之前子线程可以更新 UI
- UI 修改的操作没有线程限制 (Activity.onCreate、onStart 和 onResume)。
- 在 ViewRootImpl 创建完成之后
- 保证「创建 ViewRootImpl 的操作(WindowManager.addView 的操作)」和「执行修改 UI 的操作」在同一个线程即可。
对应的线程需要创建 Looper 并且调用 Looper#loop 方法,开启消息循环。
- 创建 ViewRootImpl 和执行 UI 更新的线程不在同一个线程,会抛异常
子线程能不能 invalidate?
看情况。子线程能更新 ui 的情况:
- ViewRootImpl 未创建,可以更新
在 ViewRootImpl 创建之前 invalidate 不受线程限制,Activity 的 onResume 后,ViewRootImpl 创建了
- ViewRootImpl 已创建
- Android8.0 及以上得分情况
- 硬件加速可用,子线程可以更新 UI
- 硬件加速不可用,走软件绘制逻辑,子线程不能更新 UI
- Android8.0 以下,不能子线程更新 UI
为什么 Google 设计成创建 ViewRootImpl 和执行 UI 更新的线程需要在同一个线程?
- 如果在不同的线程去操纵一个控件,由于网络延迟或大量耗时操作,会使 UI 绘制混乱,出了问题也很难去排查是哪个线程出了问题
- UI 线程非安全线程,如果要保证安全就需要加锁,锁的阻塞会导致其他线程对 View 的访问效率低下
ViewRootImpl 的线程?CalledFromWrongThreadException 哪里来?checkThread 时机?ViewRootImpl 创建时机?
1
2
3
4
5
6
7
8
9
10
11
12
class ViewRootImpl {
final Thread mThread;
public ViewRootImpl() {
mThread = Thread.currentThread();
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
}
- mThread 表示的是创建 ViewRootImpl 实例的线程
- checkThread 检测当前线程和创建 ViewRootImpl 的线程是否一致
checkThread() 的时机?
- requestLayout() 重点关注,重绘
- requestFitSystemWindows()
- invalidateChildInParent()
ViewRootImpl 何时被创建?
在 WindowManagerGlobal.addView 的时候
View 已经被 attach 到 Window 后,为什么非 UI 线程不能更新 UI?
当更新 UI 时,ViewRootImpl 会调用 checkThread 方法去检查当前访问 UI 的线程是否为创建 UI 的那个线程,如果不是。则会抛出异常。
在 Activity 的 onCreate/onStart/onResume 是可以在子线程更新 UI
onResume 生命周期回调前,ViewRootImpl 还没创建,requestLayout 未调用,那么 checkThread() 也就调用不到。
使用子线程更新 UI 有实际应用场景吗?
拥有窗口(Window)展示的 View,其 UI 线程可以独立于 App 主线程,如 Dialog、DialogFragment、PopupWindow、Toast、SnackBar 及自定义通过 WindowManager 添加的等
- SurfaceView 和 TextureView
这两个 View 是根红苗正用来子线程更新 View 的,SurfaceView 使用自带 Surface 去做画面渲染,TextureView 同样可以通过 TextureView#lockCanvas() 使用临时的 Surface,所以都不会触发 View#requestLayout()。
- Dialog
将弹窗(dialog 的实例化、inflate)与 App 其他业务相对独立的场景移到子线程运行(条件是创建 ViewRootImpl 的线程和更新 UI 的线程是一致的)
不适用多 UI 线程场景:
- WebView WebView 的所有方法调用必须在主线程,代码强制了主线程校验
- Activity 的使用必须在主线程
Activity 的 onCreate 方法为什么无法获取 View 的宽和高?
这个问题和子线程不能更新 UI 的问题很像,也是方法执行时机的一个问题。View 的 measure、layout、draw 发生在 Activity.onResume() 之后,因此在 onResume() 之前都是无法获取 View 的宽、高等信息的。