文章

WMS

WMS

WMS 基础

Window

见: [[Window]]

WMS 职责?

hv9q4

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 三个线程之间的关系?

oh4ki

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 呈现中间发生了什么?

idtux

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?

  1. 假如没有 Window,那 Window 管理 View 树的代码必然会放到 Activity 中。这样 Activity 就变得十分庞大,这与我们前面说的 Activity 指挥官的角色相违背。
  2. 把 View 树的管理工作封装到 Window 后,在调用 Dialog.show()、Dialog.hide() 等 Window 切换时,Activity 只需要负责 Window 的显示和隐藏即可。
  3. 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 创建完成之后
    1. 保证「创建 ViewRootImpl 的操作(WindowManager.addView 的操作)」和「执行修改 UI 的操作」在同一个线程即可。

对应的线程需要创建 Looper 并且调用 Looper#loop 方法,开启消息循环。

  1. 创建 ViewRootImpl 和执行 UI 更新的线程不在同一个线程,会抛异常

子线程能不能 invalidate?

看情况。子线程能更新 ui 的情况:

  1. ViewRootImpl 未创建,可以更新

在 ViewRootImpl 创建之前 invalidate 不受线程限制,Activity 的 onResume 后,ViewRootImpl 创建了

  1. ViewRootImpl 已创建
  • Android8.0 及以上得分情况
    • 硬件加速可用,子线程可以更新 UI
    • 硬件加速不可用,走软件绘制逻辑,子线程不能更新 UI
  • Android8.0 以下,不能子线程更新 UI

为什么 Google 设计成创建 ViewRootImpl 和执行 UI 更新的线程需要在同一个线程?

  1. 如果在不同的线程去操纵一个控件,由于网络延迟或大量耗时操作,会使 UI 绘制混乱,出了问题也很难去排查是哪个线程出了问题
  2. 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 的宽、高等信息的。

本文由作者按照 CC BY 4.0 进行授权