Skip to content

WMS核心分析

zhangpan edited this page Jul 25, 2021 · 17 revisions

概述

WindowManagerService(简称WMS)是负责Android的窗口管理,但是它其实只负责管理,比如窗口的添加、移除、调整顺序等,至于图像的绘制与合成之类的都不是WMS管理的范畴,WMS更像在更高的层面对于Android窗口的一个抽象,真正完成图像绘制的是APP端,而完成图层合成的是SurfaceFlinger服务。

这里通过一个简单的悬浮窗口来探索一下大概流程:

    TextView textView = new TextView(context);
    ...
    // 设置颜色 样式
    WindowManager mWindowManager##  = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
    WindowManager.LayoutParams wmParams = new WindowManager.LayoutParams();
    wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;
    wmParams.format = PixelFormat.RGBA_8888;
    wmParams.width = 800;
    wmParams.height = 800;
    mWindowManager.addView(textView, wmParams);

以上代码可以在主屏幕上添加一个TextView并展示,并且这个TextView独占一个窗口。在利用WindowManager.addView添加窗口之前,TextView的onDraw不会被调用,也就说View必须被添加到窗口中,才会被绘制,或者可以这样理解,只有申请了依附窗口,View才会有可以绘制的目标内存。当APP通过WindowManagerService的代理向其添加窗口的时候,WindowManagerService除了自己进行登记整理,还需要向SurfaceFlinger服务申请一块Surface画布,其实主要是画布背后所对应的一块内存,只有这一块内存申请成功之后,APP端才有绘图的目标,并且这块内存是APP端同SurfaceFlinger服务端共享的,这就省去了绘图资源的拷贝,示意图如下:

以上是抽象的图层对应关系,可以看到,APP端是可以通过unLockCanvasAndPost直接同SurfaceFlinger通信进行重绘的,就是说图形的绘制同WMS没有关系,WMS只是负责窗口的管理,并不负责窗口的绘制

Window的添加过程

添加一个只有TextView的悬浮窗Window,代码如下:

private void addTextViewWindow(Context context){

    TextView mview=new TextView(context);
    ...<!--设置颜色 样式-->
    <!--关键点1-->
    WindowManager mWindowManager = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
    WindowManager.LayoutParams wmParams = new WindowManager.LayoutParams();
    <!--关键点2-->
    wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;
    wmParams.format = PixelFormat.RGBA_8888;
    wmParams.width = 800;
    wmParams.height = 800;
    <!--关键点3-->
    mWindowManager.addView(mview, wmParams);
}

关键点1获取WindowManagerService的代理对象--WindowManagerImpl的实例。接着通过WindowManager.LayoutParams.type指定窗口类型为TOAST。最后调用addView将View添加到WMS。

// WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}

WindowManagerImpl中委托WindowManagerGlobal来进行添加。WindowManagerGlobal是一个单利,添加窗口精简后的代码如下:

// WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        // 创建ViewRootImpl
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
   // 将View添加到ViewRootImpl
    try {
        root.setView(view, wparams, panelParentView);
    }           
     ...  
}

WindowManagerGlobal添加View时首先创建了一个ViewRootImpl。ViewRootImpl是Window和View之间的通信纽带,负责将View添加到WMS,处理WMS传入的触摸事件,通知WMS更新窗口大小,以及负责通知View的绘制与更新。

ViewRootImpl的构造方法如下:

public ViewRootImpl(Context context, Display display) {
    mContext = context;
    mWindowSession = WindowManagerGlobal.getWindowSession();
    mWindow = new W(this);
}

通过WindowManagerGlobal.getWindowSession获取Binder服务代理WindowSession,它是APP端向WMS发送消息的通道。mWindow是一个W extends IWindow.Stub Binder服务对象,可视为APP端的窗口对象,主要作用是传递给WMS,并作为WMS向APP端发送消息的通道。

首先通过getWindowManagerService 获取WMS的代理,之后通过WMS的代理在服务端open一个Session,并在APP端获取该Session的代理:

 public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                <!--关键点1-->
                IWindowManager windowManager = getWindowManagerService();
                <!--关键点2-->
                sWindowSession = windowManager.openSession(***
                ...
        return sWindowSession;
    }
}

getWindowManagerService其实就是获得WindowManagerService的代理。openSession会打开一个Session返回给APP端。而Session extends IWindowSession.Stub,是一个Binder通信的Stub端,封装了每一个Session会话的操作。

@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
        IInputContext inputContext) {
    if (client == null) throw new IllegalArgumentException("null client");
    if (inputContext == null) throw new IllegalArgumentException("null inputContext");
    Session session = new Session(this, callback, client, inputContext);
    return session;
}

接下调用Session的addToDisplayWithoutInputChannel方法添加一个窗口:

@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
        Rect outOutsets, InputChannel outInputChannel) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
            outContentInsets, outStableInsets, outOutsets, outInputChannel);
}

最终又通过WMS来addWindow。

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
        ...
        synchronized(mWindowMap) {
        ...
        		// 1.不能重复添加窗口
            if (mWindowMap.containsKey(client.asBinder())) {
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }
            // 2.对于子窗口类型的处理 1、必须有父窗口 2,父窗口不能是子窗口类型
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }}
           ...
           boolean addToken = false;
            //3.根据IWindow 获取WindowToken WindowToken是窗口分组的基础,每个窗口必定有一个分组
            WindowToken token = mTokenMap.get(attrs.token);
          //4.对于Toast类系统窗口,其attrs.token可以看做是null, 如果目前没有其他的类似系统窗口展示,token仍然获取不到,仍然要走新建流程
            if (token == null) {
            ...    
                token = new WindowToken(this, attrs.token, -1, false);
                addToken = true;
            } 
            ...
             // 5. 新建WindowState,WindowState与窗口是一对一的关系,可以看做是WMS中与窗口的抽象实体
            WindowState win = new WindowState(this, session, client, token,
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
            ...
            if (addToken) {
                mTokenMap.put(attrs.token, token);
            }
            win.attach();
            mWindowMap.put(client.asBinder(), win);
            ... 
          
          addWindowToListInOrderLocked(win, true);
        return res;
    }

至此,APP向WMS添加窗口的流程就结束了。但是WMS还要向SurfaceFlinger申请Surface才算真正完成窗口的添加。

关于Surface的申请过程移步:SurfaceFlinger

Android窗口管理分析(1):View如何绘制到屏幕上的主观理解

公众号:玩转安卓Dev

Java基础

面向对象与Java基础知识

Java集合框架

JVM

多线程与并发

设计模式

Kotlin

Android

Android基础知识

Android消息机制

Framework

View事件分发机制

Android屏幕刷新机制

View的绘制流程

Activity启动

性能优化

Jetpack&系统View

第三方框架实现原理

计算机网络

算法

其它

Clone this wiki locally