“一文读懂”系列:无处不在的WMS

04ef0fba58c6ac2ee17d1846f423816e.jpeg

/   今日科技快讯   /

近日,高德地图公布了基于北斗卫星导航系统的应用相关数据:截至2022年11月,高德地图调用北斗卫星日定位量已超过2100亿次,且在定位时北斗的调用率已超越了GPS等其他卫星导航系统。作为服务国家重要基础设施,北斗系统广泛进入中国大众消费、共享经济和民生领域,应用的新模式、新业态、新经济不断涌现,深刻改变着人们的生产生活方式。数据显示,今年上半年我国境内申请入网的智能手机中,有128款支持北斗定位,其出货量占上半年总出货量的98%以上。

/   作者简介   /

本篇文章来自小余的自习室的投稿,文章主要分享了 WMS 的内部原理,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。

小余的自习室的博客地址:

https://juejin.cn/user/1011206428035661

/   前言   /

前一篇文章介绍了关于 Android 中 Window 体系的介绍,主要介绍的是 View 层的 Window 体系概念,但是想要深入了解 Window 在系统中的机制,WMS 是绕不过去的坎。有句话说的好,AMS 和 WMS 占据了 Framework 层的半壁江山,所以了解这两个概念对我们在日常开发中的性能优化或有想转 Framework 开发的都大有帮助。

笔者花了几天时间对 WMS 相关知识进行了整理。首先我们来说下 WMS 在系统中的几大职责。

b8fdf274fd90248d24f7b6d6a39f09df.png

/   WMS 中窗口容器树的概念   /

为了更好的讲解个个核心成员类,我们需要先来理清 Window 中的容器和树的概念。

容器:这个大家都熟悉,在 WMS 中通过继承 WindowContainer 类来实现容器机制,而树就是说每个容器在作为父容器的时候,同时也可能是其他容器的子容器。具体我们来看 WindowContainer 整体结构:

class WindowContainer<E extends WindowContainer> implements Comparable<WindowContainer> {
    ...
    private WindowContainer mParent = null;//1
    protected final WindowList<E> mChildren = new WindowList<E>();//2

}

注释1处 mParent 是当前 WindowContainer 容器的父容器。注释2处的 mChildren 是一个继承 WindowContainer 的集合对象,说明当前 WindowContainer 可能会有好几个子容器。

画个图来说明下这里面的关系:

af896d50f4bc09366cff098ea9849632.png

有了容器的概念后下面按职责来介绍 WMS。

/   窗口管理   /

窗口管理核心类介绍

窗口管理使用到的 DisplayContent,WindowToken 和 WindowState。

DisplayContent

用来管理一个逻辑屏上的所有窗口,有几个屏幕就会有几个 DisplayContent。使用 displayId 来区分。

处于不同 DisplayContent 的两个窗口在布局、显示顺序以及动画处理上不会产生任何耦合。因此,就这几个方面来说,DisplayContent 就像一个孤岛,所有这些操作都可以在其内部独立执行。

DisplayContent 类声明:

class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer>{
    // Mapping from a token IBinder to a WindowToken object on this display.
    private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
}
  1. DisplayContent 的子容器是其内部类 DisplayChildWindowContainer

  2. DisplayContent 内部使用:IBinder 为 key,WindowToken 为 value 的键值对保存在 HashMap 中。

  3. DisplayContent 是在 Window 添加到 WMS 的时候初始化的。

WMS

public int addWindow(Session session, ..){
    final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
    ..
}

mRoot 是 RootWindowContainer 类型的对象,看名字就知道其是窗口容器的根。说明在 Window 体系中,RootWindowContainer 节点是容器最顶端的父容器。

class RootWindowContainer extends WindowContainer<DisplayContent> {
    DisplayContent getDisplayContentOrCreate(int displayId) {
        DisplayContent dc = getDisplayContent(displayId);
        if (dc == null) {
            final Display display = mService.mDisplayManager.getDisplay(displayId);
            if (display != null) {              
                dc = createDisplayContent(display);         
            }
        }
        return dc;
    }

   DisplayContent getDisplayContent(int displayId) {
      for (int i = mChildren.size() - 1; i >= 0; --i) {
          final DisplayContent current = mChildren.get(i);
           if (current.getDisplayId() == displayId) {
              return current;
           }
      }
      return null;
  }
}

其继承了 WindowContainer,这里的 DisplayContent 是一个泛型声明,表示其子容器的类型是 DisplayContent, 在 getDisplayContent 方法中也可知,其 mChildren 列表是 DisplayContent 的集合。这也变相的说明 DisplayContent 也是一个容器。

WindowToken

类声明:

class WindowToken extends WindowContainer<WindowState>

表明 WindowToken 也是子容器,其子容器是 WindowState,所以 WindowState 也是一个容器。

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

  1. 应用组件标识:将属于同一个应用组件的窗口组织在一起,这里的应用组件可以是:Activity、InputMethod、Wallpaper 以及 Dream。WMS 在对窗口的管理过程中用 WindowToken 来指代一个应用组件。例如在进行窗口的 Z-Order 排序时,属于同一个 WindowToken 的窗口会被安排在一起。

  2. 令牌作用:WindowToken 由应用组件或其管理者负责向 WMS 声明并持有,应用组件在需要更新窗口时,需要向 WMS 提供令牌表明自己的身份, 并且窗口的类型必须与所持有的 WindowToken 的类型一致。

但是系统窗口是个例外,并不需要提供 token,WMS 会隐式声明一个WindowToken。那是不是说谁都可以添加系统窗口了呢?非也,在 addWindow 开始处就会调用下面代码:

mPolicy.checkAddPermission()

它要求客户端必须拥有 SYSTEM_ALERT_WINDOW 或INTERNAL_SYSTEM_WINDOW 权限才能创建系统类型的窗口。Window 和WindowToken 关系如下:

4d670847e96b1f5ae57a09acd6302fde.png

WindowState

类声明:

class WindowState extends WindowContainer<WindowState>

表明 WindowState 也是一个 WindowContainer 容器,但是其子容器也是WindowState,一般窗口有子窗口 SUB_WINDOW 的情况下,WindowState 才有子容器节点。WindowState 在 WMS 中表示一个 Window 窗口状态属性,其内部保存了一个 Window 所有的属性信息。其与 View 以及 WindowToken 关系如下:

2d69a618b0d5165047b8bab14e69ffc7.png

如何查看当前设备 Window 窗口状态命令?

adb shell dumpsys window windows
Window #9 Window{884cb45 u0 com.android.messaging/com.android.messaging.ui.conversationlist.ConversationListActivity}:
    mDisplayId=0 stackId=3 mSession=Session{f1b7b8e 4307:u0a10065} mClient=android.os.BinderProxy@a512fbc
    mOwnerUid=10065 mShowToOwnerOnly=true package=com.android.messaging appop=NONE
    mAttrs={(0,36)(828xwrap) gr=BOTTOM CENTER sim={adjust=pan forwardNavigation} ty=APPLICATION fmt=TRANSLUCENT wanim=0x7f130015
      fl=DIM_BEHIND ALT_FOCUSABLE_IM HARDWARE_ACCELERATED
      vsysui=LIGHT_STATUS_BAR LIGHT_NAVIGATION_BAR}
    Requested w=828 h=290 mLayoutSeq=220
    mBaseLayer=21000 mSubLayer=0    mToken=AppWindowToken{3f9efb8 token=Token{2b272cc ActivityRecord{55a41e u0 com.android.messaging/.ui.conversationlist.ConversationListActivity t8}}}
    mAppToken=AppWindowToken{3f9efb8 token=Token{2b272cc ActivityRecord{55a41e u0 com.android.messaging/.ui.conversationlist.ConversationListActivity t8}}}
...

下面笔者以窗口的添加操作为例讲解 WMS 的窗口管理。

窗口的添加操作

public int addWindow(Session session, IWindow client, int seq,
        WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
        Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
        InputChannel outInputChannel) {
    ...
    int res = mPolicy.checkAddPermission(attrs, appOp);//1
    ...
    synchronized(mWindowMap) {


        final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);//2

        if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
            parentWindow = windowForClientLocked(null, attrs.token, false);//3

        }
        ...            
        WindowToken token = displayContent.getWindowToken(
                hasParent ? parentWindow.mAttrs.token : attrs.token);//4

        if (token == null) {                              
            final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
            token = new WindowToken(this, binder, type, false, displayContent,
                    session.mCanAddInternalSystemWindow);//5
        } 
        ...
        final WindowState win = new WindowState(this, session, client, token, parentWindow,
                appOp[0], seq, attrs, viewVisibility, session.mUid,
                session.mCanAddInternalSystemWindow);//6
        ...
        mPolicy.adjustWindowParamsLw(win.mAttrs);//7
        ...
        if  (openInputChannels) {
            win.openInputChannel(outInputChannel);//8
        }
        ...
        mWindowMap.put(client.asBinder(), win);//9
        ...
        win.mToken.addWindow(win);//10
        ...
        displayContent.assignWindowLayers(false /* setLayoutNeeded */);//11

        //12
        if (focusChanged) {
            mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
        }
        mInputMonitor.updateInputWindowsLw(false /*force*/);

    }
    ...
    return res;

}

注释1:检查当前 Window 的 token 等权限合法性。

注释2:这在介绍 DisplayContent 已经说过,使用 RootWindowContainer 的子容器中获取一个 DisplayContent,如果子容器集合中不存在,则去获取一个,并添加到 child 集合中

注释3:如果是 Dialog 等子窗口,则获取父窗口,没有就报找不到父窗口的异常。

注释4:使用 attr.token 去 displayContent 的键值对 mTokenMap 中获取对应的 WindowToken,WindowToken 中保存了一组 Window。

注释5:如果4中 WindowToken 为 null,则创建一个 WindowToken,传入 app 层传入的 attr.token 以及 displayContent 对象,内部会将这个创建的 WindowToken 保存到 displayContent 中

注释6:创建一个 WindowState,并传入所有关于 Window 相关的属性,这样 WindowState 在 WMS 中就是以一个 Window 性质存在了、WindowState 构造过程中会将其添加到 WindowToken 中去。

注释7:根据 mPolicy 调整 window 的 attr 属性,mPolicy 的实现类是PhoneManagerPolicy。

注释8:执行 WindowState 的 openInputChannel,这里主要是打通和 Input 系统的通道,用于接收 IMS 的输入事件请求。

注释9:将客户端 app 层的 Window 对象和 WindowState 关联上,这样 WMS 就可以通过 Window 找到 WMS 中的 WindowState 对象。

注释10:win.mToken 是前面创建的 WindowToken 对象,所以此处就是将WindowState 加入到 WindowToken 的子容器集合中。

注释11:分配窗口的层级,这里的 setLayoutNeeded 参数为 false,说明不需要进行 Layout 操作。

这里小结下 addWindow 方法,主要就是创建了一个和 Window 一一对应的 WindowState 对象,并将 WindowState 插入到父容器 WindowToken 的子容器集合中,而 WindowToken 又保存在 DisplayContent 的键值对集合中。三种关系可以简单总结如下:

6717fc5a94d0be76ac513833973007fd.png

/   窗口动画   /

我们知道在 Android 内部有两种动画,Window 切换移动动画以及 app 层的 View 的动画, 动画操作的是 View 而 Window 切换操作的是 Surface,对不同层级的 SurfaceControl 进行操纵,会产生不同的动画效果,注意区分。

我们这里涉及到的是 Window 切换移动动画。

但是不管是 View 的动画还是 Window 切换操作,对底层屏幕刷新来说都是针对不同帧动画来说,所以会涉及到 VSync 同步信号相关知识。

核心类

WindowStateAnimator

类声明:

Keep track of animations and surface operations for a single WindowState.

用来管理一个 Window 的动画操作的,在 WindowState 构造方法中创建,说明每个 Window 窗口都对应一个 WindowStateAnimator。

WindowState(WindowManagerService service...){
    mWinAnimator = new WindowStateAnimator(this);
}

WindowAnimator

/**
   
    * Singleton class that carries out the animations and Surface operations in a separate task
    * on behalf of WindowManagerService.
      */
       public class WindowAnimator

看方法说明,这个类还是用于 WMS 中的窗口动画以及 Surface 操作的单例工具类,WMS 将动画的工作都委托他来处理。其在 WMS 构造的时候创建了实例。

WindowAnimator(final WindowManagerService service) {
    mService = service;
    ..
    mWindowPlacerLocked = service.mWindowPlacerLocked;//1 这个类用于Surface的摆放
    AnimationThread.getHandler().runWithScissors(
            () -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);//2

mAnimationFrameCallback = frameTimeNs -> {//3
    synchronized (mService.mWindowMap) {
        mAnimationFrameCallbackScheduled = false;
    }
    animate(frameTimeNs);
};
}

注释1处创建了一个 WindowSurfacePlacer 对象,这个对象是用于 Surface 的摆放的操作,说明 WindowAnimator 还支持 Surface 的各种操作 注释2处使用AnimationThread 线程进行 Window 的动画操作,AnimationThread 内部使用的是 HandlerThread 机制,说明其内部也创建了一个异步消息处理机制。注释3处 mAnimationFrameCallback 类型是 Choreographer.FrameCallback。

FrameCallback 在这篇文章中有讲过,其就是给 Choreographer 设置一个回调,在 Choreographer 接收到 VSync 信号时,在 doFrame 中触发这个回调,一般是用来监听帧率等操作。原文地址:

https://juejin.cn/user/1011206428035661

而这里是在接收到 doFrame 的时候回调的是一个 animate(frameTimeNs) 动画处理的方法。animate 函数执行流程很长,包括更新壁纸、转屏动画等逻辑均包含在其中。那么 mAnimationFrameCallback 回调是什么时候注册到 Choreographer 中去的呢?

WindowAnimator的scheduleAnimation 方法:

void scheduleAnimation() {
    if (!mAnimationFrameCallbackScheduled) {
        mAnimationFrameCallbackScheduled = true;
        mChoreographer.postFrameCallback(mAnimationFrameCallback);
    }
}

在外部需要进行动画的时候,就会优先 scheduleAnimation,将 mAnimationFrameCallback 注册到 Choreographer 中去。我们重点来看 animate 方法,这个方法内部有这么段代码。

dc.updateWindowsForAnimator(this);

表示为了动画去更新 Windows,可以进入看看。

void updateWindowsForAnimator(WindowAnimator animator) {
    mTmpWindowAnimator = animator;
    forAllWindows(mUpdateWindowsForAnimator, true /* traverseTopToBottom */);
}
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {


...
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
    final DisplayChildWindowContainer child = mChildren.get(i);
    if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
        // In this case the Ime windows will be processed above their target so we skip
        // here.
        continue;
    }
    if (child.forAllWindows(callback, traverseTopToBottom)) {
        return true;
    }
}

return false;
}

forAllWindows 方法会遍历整个容器树都去调用 mUpdateWindowsForAnimator 回调。这个回调内部就会去执行 winAnimator.stepAnimationLocked 去更新 Window 的更新操作。stepAnimationLocked,代表单步动画。这里面的操作大家自行查看也不难。

这里对动画做个小结,通过在需要动画的时候,post 一个 FrameCallBack 给Choreographer,在 VSync 信号到来的时候,会优先执行动画操作。动画回调内部会去遍历整个容器树模型,依次更改每个 Window 对应的 Surface 的状态。然后在绘制完成后,提交给 SurfaceFlinger。过程图示:

bc34c0676888494f8a01baa30c9846cf.png

/   输入事件处理   /

关于 Input 事件在这篇文章中已经有讲过。输入子系统从驱动文件中读取事件后,再封装提交给 IMS,IMS 再发送给 WMS 进行处理。

输入系统整体架构:

d39922a6ad771df51e5ad74e55307a0c.png

今天我们从WMS的角度来分析下输入事件。

核心类

InputChannel

类声明:

/**

 * An input channel specifies the file descriptors used to send input events to
 * a window in another process.  It is Parcelable so that it can be sent
 * to the process that is to receive events.  Only one thread should be reading
 * from an InputChannel at a time.
 * @hide
   */
    public final class InputChannel implements Parcelable {

注释中说明了 InputChannel 是一个使用文件描述符 fd 来发送 input 事件给其他进程的一个输入通道,且只有一个线程可以同时读取 InputChannel 中的数据,说明 InputChannel 是线程安全的。其内部使用的是 socket 通讯。

前面在分析 Window 添加过程的时候说过在 WMS 的 addWindow 中会调用。

win.openInputChannel(outInputChannel)

void openInputChannel(InputChannel outInputChannel) {
    ...
    String name = getName();
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);//1
    mInputChannel = inputChannels[0];
    mClientChannel = inputChannels[1];
    mInputWindowHandle.inputChannel = inputChannels[0];
    if (outInputChannel != null) {
        mClientChannel.transferTo(outInputChannel);//2
        mClientChannel.dispose();
        mClientChannel = null;
    }
    ...
    mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);//3
}
InputChannel.java:
public static InputChannel[] openInputChannelPair(String name) {
    ...
    return nativeOpenInputChannelPair(name);
}
android_view_InputChannel.cpp:
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env...) {
    ...
    sp<InputChannel> serverChannel;
    sp<InputChannel> clientChannel;
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);



jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
        std::make_unique<NativeInputChannel>(serverChannel));
jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
        std::make_unique<NativeInputChannel>(clientChannel));
...
env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
return channelPair;


}

status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        ...
        return result;
    }
    ...
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;
}

通过以上代码可以看出 InputChannel 使用的是 sockets 通讯,且 WindowState 的 openInputChannel 中注释1处:

InputChannel[] inputChannels = InputChannel.openInputChannelPair(name),

返回的 inputChannels 是一个服务端和客户端的输入通道数组。其中:

  • 下标0表示服务端的 InputChannel

  • 下标1表示客户端的 InputChannel

在注释3处 registerInputChannel 传入的是 server 端 InputChannel 给 IMS。而注释2处将 client 端的 InputChannel 与 app 端传入的 outInputChannel 关联起来了。

这样服务端在 InputChannel 就可以写入 input 事件,然后在 app 端的InputChannel 就可以接受到数据了。输入事件通讯模型如下:

a39cdb35b152f80a661f9e838c58ab53.png

/   Surface管理   /

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

Surface的创建

Surface 的创建在 WMS 中使用 WindowStateAnimator 代理创建,而WindowStateAnimator 中又创建了一个 WindowSurfaceController 对 Surface 进行管理。

核心类:WindowSurfaceController

public WindowSurfaceController(SurfaceSession s, String name, int w, int h...){
    mSurfaceControl = new SurfaceControl(s, name, w, h, format, flags, windowType, ownerUid);
}
public SurfaceControl(SurfaceSession session, String name, ...){
    mNativeObject = nativeCreate(session, name, w, h, format, flags,...);
}

在其构造方法中创建了一个 SurfaceControl,SurfaceControl 最终进入 native 层,在 native 层创建了一个 Surface,并返回 native surface 地址。实际在 native 层也是创建一个 SurfaceControl。

static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
        jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject,
        jint windowType, jint ownerUid) {

    ```
    sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj));
    SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);
    sp<SurfaceControl> surface = client->createSurface(
            String8(name.c_str()), w, h, format, flags, parent, windowType, ownerUid);
    ...
    return reinterpret_cast<jlong>(surface.get());
    ```
}

那 app 层是什么时候发起 WMS 的 Surface 创建任务的?看 ViewRootImpl 的 relayoutWindow 方法:

private int relayoutWindow(WindowManager.LayoutParams params..){
    int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params...mSurface);
}

调用 mWindowSession 的 relayout 方法,并传入最后 mSurface 对象,这是空Surface,在 WMS 中会被填充返回。最终调用到 WMS 中的 relayoutWindow。

public int relayoutWindow(Session session, IWindow client, int seq..){
    ...
    result = createSurfaceControl(outSurface, result, win, winAnimator);           
}
private int createSurfaceControl(Surface outSurface, int result, WindowState win,
        WindowStateAnimator winAnimator) {
    
    ```
    WindowSurfaceController surfaceController;
    try {
        surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid);
    } finally {
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }
    if (surfaceController != null) {
        surfaceController.getSurface(outSurface);//1
        if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  OUT SURFACE " + outSurface + ": copied");
    } 
    return result;
    ```
    
}

最终会调用 WindowStateAnimator 的 createSurfaceLocked 这个前面已经分析过了。返回的 surfaceController 对象在注释1处调用 getSurface(outSurface),将 native 层的 Surface 填充到 App 传递过来的 outSurface 进入 getSurface 看看。

WindowSurfaceController.java
void getSurface(Surface outSurface) {
    outSurface.copyFrom(mSurfaceControl);
}
Surface.java
public void copyFrom(SurfaceControl other) {
    if (other == null) {
        throw new IllegalArgumentException("other must not be null");
    }
    
    ```
    long surfaceControlPtr = other.mNativeObject;
    if (surfaceControlPtr == 0) {
        throw new NullPointerException(
                "null SurfaceControl native object. Are you using a released SurfaceControl?");
    }
    long newNativeObject = nativeGetFromSurfaceControl(surfaceControlPtr);
    
    synchronized (mLock) {
        if (mNativeObject != 0) {
            nativeRelease(mNativeObject);
        }
        setNativeObjectLocked(newNativeObject);
    }
    ```

}
private void setNativeObjectLocked(long ptr) {
    if (mNativeObject != ptr) {
        mNativeObject = ptr;
    }
}

getSurface 方法将 WMS 中创建的 WindowSurfaceController 中 SurfaceControl 对象的 mNativeObject 对象传递给新的 Surface,并使用这个对象去 native 层获取一个新的 NativeObject 赋值给当前 Surface 的 mNativeObject,这样 App 层的 Surface 就获取到了 WMS 在 native 中创建的 SurfaceControl 对象,可以在 app 层操作 native 层的 Surface 了。

那么为什么谷歌要绕这么大圈来创建 Surface 呢?直接在 App 层去创建不就可以了么?

个人见解谷歌是希望统一管理 Surface 而不是单独让某个应用持有,且 Surface 的摆放操作等都是得由 WMS 进行处理,所以就直接让 WMS 去创建,然后返回给 App 层去绘制 Surface 操作。

Surface的摆放

Surface 在创建之后还需要进行屏幕位置的确认,那这个在哪里操作呢?

核心类:WindowSurfacePlacer

WMS 在构造的时候就创建了 WindowSurfacePlacer 对象。这个对象主要用来给Surface 进行位置的定位。定位到 WindowSurfacePlacer 的 performSurfacePlacement 方法,这个方法可以说是 WMS 最核心的方法,其负责了所有窗口的摆放工作。如何显示?显示在屏幕什么位置?区域大小等。这些将在确认后,下发给 SurfaceFlinger 进行处理。

WMS 中任何窗口状态发生改变都会触发该方法,整个方法进行容器树的遍历,确认窗口可见性等。

final void performSurfacePlacement(boolean force) {
    
    ```
    int loopCount = 6;
    do {
        mTraversalScheduled = false;
        performSurfacePlacementLoop();
        loopCount--;
    } while (mTraversalScheduled && loopCount > 0);
    ```
    
}

private void performSurfacePlacementLoop() {
    ...
    mInLayout = true;
    mService.mRoot.performSurfacePlacement(recoveringMemory);
    mInLayout = false;
    if (mService.mRoot.isLayoutNeeded()) {
        if (++mLayoutRepeatCount < 6) {
            requestTraversal();
        } else {
            Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
            mLayoutRepeatCount = 0;
        }
    } else {
        mLayoutRepeatCount = 0;
    }   
}

performSurfacePlacement 最终会调用到 mService.mRoot.performSurfacePlacement, mService.mRoot.performSurfacePlacement 中最终会执行到对窗口容器树做以下遍历操作,中间代码跳转太多,就略过了。主要做了下面三件事:

1.DisplayContent 的 mPerformLayout 操作。计算所有 DisplayFrame 以及 WindowFrame 的大小和位置。

private final Consumer<WindowState> mPerformLayout = w -> {
    ...
    mService.mPolicy.layoutWindowLw(w, null);
    ...
}
mPolicy = PhoneManagerPolicy
PhoneManagerPolicy.java
public void layoutWindowLw(WindowState win, WindowState attached) {
    //这里面都是对不同给的Window的位置进行确认
    computeFrameLw(....);//这个方法会计算所有DisplayFrame以及WindowFrame的大小和位置
}

2.DisplayContent 的 mApplySurfaceChangesTransaction 操作。

private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
    ..前面一大推处理
    winAnimator.setSurfaceBoundariesLocked(mTmpRecoveringMemory /* recoveringMemory */);
  }
  winAnimator = WindowStateAnimator
  WindowStateAnimator.java

  void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
    calculateSurfaceBounds(w, attrs);//1
    mSurfaceResized = mSurfaceController.setSizeInTransaction(
                      mTmpSize.width(), mTmpSize.height(), recoveringMemory);//2
    ...
    mSurfaceController.setPositionInTransaction((float) Math.floor(posX),
                      (float) Math.floor(posY), recoveringMemory);//3

}

注释1处计算 Surface 的 size 大小,然后在注释2处使用 mSurfaceController 设置到 native 层的 SurfaceController 对象中, 注释3处在计算好位置后,也使用 mSurfaceController 设置到 native 层的 SurfaceController 对象中。这样就将 Surface 在屏幕中给的位置以及大小都确认下来了。

3.WindowStateAnimator的commitFinishDrawingLocked();提交事务。

boolean commitFinishDrawingLocked() {

    ```
    mDrawState = READY_TO_SHOW;
    boolean result = false;
    final AppWindowToken atoken = mWin.mAppToken;
    if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
        result = mWin.performShowLocked();
    }
    return result;
    ```

}

boolean performShowLocked() {
    final int drawState = mWinAnimator.mDrawState;
    if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW)
            && mAttrs.type != TYPE_APPLICATION_STARTING && mAppToken != null) {
        mAppToken.onFirstWindowDrawn(this, mWinAnimator);
    }

    ```
    if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
        return false;
    }
    
    logPerformShow("Showing ");
    
    mService.enableScreenIfNeededLocked();
    mWinAnimator.applyEnterAnimationLocked();
    
    mWinAnimator.mDrawState = HAS_DRAWN;
    mService.scheduleAnimationLocked();//1
    
    if (mHidden) {
        mHidden = false;
        final DisplayContent displayContent = getDisplayContent();  
        //2
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowState c = mChildren.get(i);
            if (c.mWinAnimator.mSurfaceController != null) {
                c.performShowLocked();
            }
        }
    }
    ```
    
    }
    ````

performShowLocked 方法中有大量的对窗口状态的判断,窗口的显示过程共有五个状态。

NO_SURFACE

在创建 WindowState 后的默认状态,表示当前窗口还创没有执行 relayout() 方法创建 Surface。

DRAW_PENDING

执行 relayout() 方法后,创建完成 Surface 后的状态,表示等待绘制。

COMMIT_DRAW_PENDING

窗口 Surface 上完成绘制后的状态,执行WindowStateAnimator#finishDrawingLocked() 方法设置,表示已经完成绘制,等待下次刷帧进行提交。

READY_TO_SHOW

表示窗口已经绘制完成并且完成提交,此时如果该窗口的兄弟窗口全部完成绘制且满足显示要求,则直接进行 HAS_DRAWN 的转变完成显示,否则等待其他兄弟窗口完成绘制后,再进行 HAS_DRAWN 转变。

HAS_DRAWN

表示该窗口正式显示。

在注释2处又对窗口容器树进行了遍历,都指向 performShowLocked 方法。在注释1处调用了 WMS 的 scheduleAnimationLocked 方法,如果你还有印象,在前面分析窗口动画的时候说过,scheduleAnimationLocked 方法会将动画帧回调 FrameCallback 设置到 Choreographer 中去。然后在 VSYNC 信号到来的时候,指向 CallBack 动画回调、最后执行 animate。

private void animate(long frameTimeNs) {
    //..
    for (int i = 0; i < numDisplays; i++) {
        final int displayId = mDisplayContentsAnimators.keyAt(i);
        final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
        ...
        dc.prepareWindowSurfaces();
}
void prepareWindowSurfaces() {
    forAllWindows(mPrepareWindowSurfaces, false / traverseTopToBottom */);
}

private final Consumer<WindowState> mPrepareWindowSurfaces =
        w -> w.mWinAnimator.prepareSurfaceLocked(true);

void prepareSurfaceLocked(final boolean recoveringMemory) {
    boolean prepared = mSurfaceController.prepareToShowInTransaction(mShownAlpha,..);//1
    
    ```
    mSurfaceController.setLayer(mAnimLayer);//2
    
    showSurfaceRobustlyLocked()//3
    ```

}
private boolean showSurfaceRobustlyLocked() {
    final Task task = mWin.getTask();
    if (task != null && StackId.windowsAreScaleable(task.mStack.mStackId)) {
        mSurfaceController.forceScaleableInTransaction(true);
    }

boolean shown = mSurfaceController.showRobustlyInTransaction();
    if (!shown)
        return false;
    
    if (mWin.mTurnOnScreen) {
        if (DEBUG_VISIBILITY) Slog.v(TAG, "Show surface turning screen on: " + mWin);
        mWin.mTurnOnScreen = false;
        mAnimator.mBulkUpdateParams |= SET_TURN_ON_SCREEN;
    }
    return true;
    ```

}

最终在 showSurfaceRobustlyLocked 中调mSurfaceController.showRobustlyInTransaction() 方法进行 Surface 的提交给 SurfaceFlinger 进行合成并显示在屏幕上。

可以看到 Surface的size,postion 以及状态管理,提交执行等操作还是一个比较繁琐的过程。

时序图如下(原图可以去原文查看):

f531ee9890ad1a591e4c94d96e7c0fb7.png

/   总结   /

本篇文章笔者通过对 WMS 的责任进行划分,并分别对几个责任进行了较为详细的讲解。

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

Android自定义View之跑马灯-光圈

Android 13启动速度提升,优化AMS加速启动

欢迎关注我的公众号

学习技术或投稿

d579c811a1cf9aab9553820dae87ae64.png

313f6f134fe18e9accc6f781a8f81ca6.jpeg

长按上图,识别图中二维码即可关注

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值