4-WMS

View 绘制流程

Activity 的界面是在 onResume 之后才开始进行绘制的,onWindowFocusChanged 回调里能确认 UI 可见可操作。

1
onCreate - onStart - onResume - measure - layout -measure - layout - draw - onWindowFocusChanged
  • 每个 Activity 包含了一个 Window 对象,这个对象是由 PhoneWindow 做的实现(Window 是一个抽象类,而 PhoneWindow 是它的子类)。而 PhoneWindow 将 DecorView 作为了一个应用窗口的根 View,这个 DecorView 又把屏幕划分为了两个区域:一个是 TitleBar,一个是 ContentView,而我们平时在 Xml 文件中写的布局正好是展示在 ContentView 中的.
  • PhoneWindow 是 Android 系统中最基本的窗口系统,每个 Activity 会创建一个。同时,PhoneWindow 也是 Activity 和 View 系统交互的接口。

unknown_filename|300

微信截图_20191116113902|600

整个流程

  • ActivityThread 初始启动应用,创建 Activity 的方法 handleLaunchActivity 方法中调用了 Activity 的 attach 方法
  • attach 把 window,windowmanager 创建出来,和 activity 建立联系
  • 然后 mInstrumentation. callActivityOnCreate 调 prePerformCreate
  • Activity. onCreate () 调用 setContentView
  • setContview 中解析 xml,最后通过 phoneWindow 把 DecorView 创建出来了
  • 然后在 onResume,WindowManager 的 addview 方法将 DecorView 与 WindowManager 建立联系,然后创建了 viewRootImpl,并将 ViewRootImpl 对象和 DecorView 建立关联(assignParent)
  • 接着 ViewRootImpl 的 setView 里面有 requestLayout——>performTraversals——>三大流程绘制

详细

Window WindowManager Activity 之间联系

WindowManager: 接口类, 同时实现了 ViewManager. 定义了大量 Window 的状态值
WindowManagerlmpl: WindowManager 的接口实现类, 但具体的方法实现交给了 WindowManagerGlobal.
WindowManagerGlobal: 真正的 WindowManager 接口方法的处理类, 如: 创建 ViewRootlmpl 等..
Window/WindowManager 均在 Activity 的 attach 中完成

Attach

Window 实例化

  • ActivityThread 初始启动应用创建 Activity 的方法 handleLaunchActivity 方法中调用了 Activity 的 attach 方法,创建了 Window 对象为 PhoneWindow
  • Window 的 Callback 对象指向的是当前 Activity 对象,方便事件分发传递回 Activity
1
2
3
4
5
6
7
8
final void attach(Context context, ActivityThread aThread ...) {
attachBaseContext(context);
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);//1
...
mWindow.setCallback(this);//2
...
}

Window 和 Activity 以及 WindowManager 什么时候建立的关系?

attach

  1. new 一个 PhoneWindow 对象并传入当前 Activity 引用,建立 Window 和 Activity 的一一对应关系。此 Window 是 Window 类的子类 PhoneWindow 的实例。Activity 在 Window 中是以 mContext 属性存在。
  2. 调用 PhoneWindow 的 setWindowManager 方法,在这个方法中让 Window 和 WindowManager 建立了一一关系。此 WindowManager 是WindowManagerImpl的实例

setContentView

unknown_filename.28|1000

Window 什么时候和 View 进行关联?

setContentView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void setContentView(int layoutResID) {
...
if (mContentParent == null) {
installDecor();//注释1
}
mLayoutInflater.inflate(layoutResID, mContentParent);//注释2
...
}

private void installDecor() {
...
if (mDecor == null) {
mDecor = generateDecor(-1);
} else {
mDecor.setWindow(this);
}
}

protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}

布局加载: 首先,Activity 会加载布局文件。布局文件通常是一个 XML 文件,其中定义了界面上的各种元素及其位置和属性。布局文件被解析并转化为一个视图树 (View Tree)
这是一个由 View 对象组成的层次结构, 视图树的构建: 一旦布局文件被解析,系统会根据布局文件的定义构建视图树。每个 View 对象在树中占据一个节点,它们之间的关系和层级关系由布局文件的 XML 结构决定。

onResume

  • handlerResumeActivity
  • WindowManager 的 addview 方法将 DecorView 与 WindowManager 建立联系,也就是从 addview 方法触发了绘制界面,实际上调用的是 WindowManagerImpl 的 addview 方法
  • 又调了 WindowManagerGlobal 的 addview 方法,这里创建了 ViewRootImpl
  • ViewRootImpl,每个 ViewRootImpl 对应一个 Window

unknown_filename.29

View 是如何一步一步添加到屏幕上的?更新?删除呢?

屏幕中所有的 View 首先需要经过 WindowManager 的处理,最后提交给 WMS 来处理。

handlerResumeActivity ——>WindowManagerImpl. addView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final class WindowManagerImpl implements WindowManager{
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
...
}

可以看到这三个方法都是委托给了 WindowManagerGlobal 进行处理,这是设计模式中的桥接模式 [[设计模式2(结构型模式)#桥接模式]]。

addView

WindowManagerGlobal 的addView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
//将params强转为WindowManager.LayoutParams类型
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
synchronized (mLock) {

//创建一个ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
//给View设置LayoutParams参数
view.setLayoutParams(wparams);
mViews.add(view);
//存储root到mRoots列表
mRoots.add(root);
//存储wparams到mParams列表
mParams.add(wparams);

try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}

最后调用了 ViewRootImpl 的 setView 方法

ViewRootImpl

ViewRoot 不是 View,它的实现类是 ViewRootImpl,ViewRoot 是 DecorView 的“管理者”。它是 DecorView 和 WindowManager 之间的交互。View 的三大流程均是通过 ViewRoot 来完成的。

ViewRootImpl 身负了很多职责:

  1. 管理 View 树,且其是 View 的根
  2. 触发三大绘制流程:测量,布局,绘制
  3. 输入事件中转站
  4. 管理 Surface
  5. 负责与 WMS 通讯
1
2
3
4
5
6
7
8
9
10
11
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();//注释1
...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);//注释2,看下面
...
}

requestLayout

requestLayout调用 scheduleTraversals

frameworks/base/core/java/android/view/ViewRootImpl. java

  • 如下源码,ViewRootImpl 的 scheduleTraversals 方法注释1处在主线程 Handler 插入一个异步空消息,等到 Vsync 信号来时,Handler 会优先执行这个异步消息,保证绘制的优先级
  • 注释2执行了 Choreographer. postCallback 方法,Choreographer 能够监听 Vsync 信号,当 Vsync 信号到来时执行 callback,也就是执行 mTraversalRunnable,它是 Runnable 对象

[[Handler消息机制#「9、同步屏障和异步消息有具体的使用场景吗?」]]

1
2
3
4
5
6
7
8
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//1
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//2
pokeDrawLockIfNeeded();
}
}

performTraversals

View 的绘制流程是从 ViewRoot(ViewRootImpl)的 performTraversals 方法开始的。所以 performTraversals 方法依次调用 performMeasure, performLayout 和 performDraw 三个方法。然后各自经历 measure、layout、draw 三个流程最终显示在用户面前。

mTraversalRunnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
}
}

微信截图_20191116143525

addToDisplay

将 Window 添加到屏幕上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mWindowSession = WindowManagerGlobal.getWindowSession();
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();//1
sWindowSession = windowManager.openSession(//2
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}

mWindowSession 是 IWindowSession(看到 i 就是代理)类型的,它是一个 binder 对象,用于进程间通讯,IWindowSession 是 C 端代理,在 S 端使用的是 Session 类实现。addToDisplay 运行在WMS 进程中。

==调用 WMS 的 addWindow 方法添加 Window,在 WMS 眼里,一切 View 都是以 Window 形式存在的,剩下的工作就交由 WMS 进行处理了==

View.post ()

为什么可以获取到宽高信息

  • View 是 Android 中视图的程序方式, View 不能单独存在, 必须依附 Window 这个抽象概念上, 因此有视图的地方就有 Window。
  • activity 所有界面的呈现都是在 window 上的,window 中的 type 是用来控制它的显示层级关系。
  • 内部执行是通过 handler 去执行的, View. post 调用时会把 Runnable 保存到一个缓存数组中(handle. post, HandlerActionQueue),等到 View 加载到 Window 时会调用 View 的 dispatchAttachedToWindow 方法,然后通过 handler 执行 Runnable 方法,就可以获取到他的宽高。
  • 在 onCreate () 和 onResume () 中是无法获取到宽高的,而 View. post () 回调中可以
  • setContentView () 逻辑很复杂,但干的事情很直白。创建 DecorView ,然后根据我们传入的布局文件 id 解析 xml,将得到的 view 塞进 DecorView 中。注意,到现在,我们得到的只是一个空壳子 View 树,它并没有被添加到屏幕上,其实也不能添加到屏幕上。所以,在 onCreate () 回调中获取视图宽高显然是不可取的。

原理

根据 ViewRootImpl 是否已经创建,View.post () 会执行不同的逻辑。如果 ViewRootImpl 已经创建,即 mAttachInfo 已经初始化,直接通过 Handler 发送消息来执行任务。如果 ViewRootImpl 未创建,即 View 尚未开始绘制,会将任务保存为 HandlerAction,暂存在队列 HandlerActionQueue 中,等到 View 开始绘制,执行 performTraversal () 方法时,在 dispatchAttachedToWindow () 方法中通过 Handler 分发 HandlerActionQueue 中暂存的任务。

performTraversal——》dispatchAttachedToWindow

unknown_filename.18
unknown_filename.19

  • 如果 View 是 new 出来的,并且没有通过 addView 等方法依赖到 DecorView 上面,它的 post 方法也是不会执行的,因为它没有机会和 ViewRootImpl 进行互动了

WindowManagerService

  • 简称Wms,WindowManagerService管理窗口的创建、更新和删除,显示顺序等,是WindowManager这个管理接品的真正的实现类。它运行在System_server进程
  • WindowManager和WMS通过binder进行通讯,真正对Window添加,更新,删除等操作的执行者
  • Window(phoneWindow)只控制视图

image.png

  • 一个Activity包含一个Window,如果Activity没有Window,那就相当于Service
  • Activity只负责生命周期和事件处理
  • WindowManager 的 addView 方法调 viewRoot,和 wms 交互,display 显示

问题

Window有哪些属性?类型?层级关系?z-order?Window标志?

  • 子窗口看名字就知道其是一个窗口的子窗口,所以不能独立存在,需要依附于父窗口存在,比如PopupWindow,Dialog等就属于子窗口

  • z-order:在Z轴上处于最高位置

  • 类型值越大,越靠近用户。通过上面分析可知:系统窗口>子窗口>应用程序窗口。 这也就是系统窗口会覆盖应用窗口最直接的原因。

  • 那么如果多个窗口都是应用程序窗口如何显示呢? WMS会结合实际情况,去显示窗口合适的层级。
    unknown_filename.15

Window 软键盘模式(SoftInputModel)

软键盘也是窗口的一种,属于系统窗口,层级较高,所以在一些场景下会覆盖层级较低的应用窗口

WMS中窗口容器树的概念

image.png

容器:这个大家都熟悉,在WMS中通过继承WindowContainer类来实现容器机制:

就是说每个容器在作为父容器的时候,同时也可能是其他容器的子容器。
树结构
image.png

WindowToken

WindowToken在窗口体系中有两个作用

  • 1.应用组件标识:将属于同一个应用组件的窗口组织在一起,这里的应用组件可以是:Activity、InputMethod、Wallpaper以及Dream。 WMS在对窗口的管理过程中:用WindowToken来指代一个应用组件。例如在进行窗口的Z-Order排序时,属于同一个WindowToken的窗口会被安排在一起。
  • 2.令牌作用:WindowToken由应用组件或其管理者负责向WMS声明并持有,应用组件在需要更新窗口时,需要向WMS提供令牌表明自己的身份, 并且窗口的类型必须与所持有的WindowToken的类型一致。

但是系统窗口是个例外,并不需要提供token,WMS会隐式声明一个WindowToken
image.png

输入事件处理

[[5-事件分发机制#INPUT]]
通过sockets通信
image.png

Surface管理

WMS负责创建Surface以及对Surface的摆放工作,之后将Surface提交给SurfaceFlinger进行合并。 在App层也创建了一个Surface对象,但是那个是空对象,用于WMS的填充


4-WMS
http://peiniwan.github.io/2024/04/edc256d7fb68.html
作者
六月的雨
发布于
2024年4月6日
许可协议