深入Android系统(十三)Android的窗口系统

Android的窗口系统由WindowManagerService管理,包括增加和删除窗口,确定窗口的大小和位置,以及实现窗口切换、窗口动画等功能。WindowManagerService名字较长,后面简称WMS

前言

一个小问题

在 Android 中Activity可以理解成 Android 中的一个页面,前面的章节我们说 SurfaceFlinger 最后会把 Surface 对应的 Layer 合成到屏幕上。那么Activity 和 Surface 的关系又是怎么关联的呢?

让我们一起排查下~~~

应用进程和WMS的联系

在学习WMS之前,我们先了解下应用和WMS之间的联系。对于应用来说:

  • Activity

    • Activity并不负责视图控制,它只是控制生命周期和处理事件
    • 真正控制视图的是Window。一个Activity包含了一个WindowWindow才是真正代表一个窗口
    • Activity就像一个控制器,统筹视图的添加与显示,以及通过其他回调方法,来与Window、以及View进行交互
  • Window

    • Window是视图的承载器,内部持有一个DecorView,而这个DecorView才是view的根布局
    • Window是一个抽象类,实际在Activity中持有的是其子类PhoneWindow
    • PhoneWindow中有个内部类DecorView,通过创建DecorView来加载Activity中``传递的布局
    • Window通过WindowManagerDecorView加载其中,并将DecorView交给ViewRoot,进行视图绘制以及其他交互
  • ViewRoot

    • 所有View的绘制以及事件分发等交互都是通过它来执行或传递的
    • ViewRoot对应的实现类是ViewRootImpl类,它是连接WindowManagerServiceDecorView的纽带
    • View的三大流程:测量(measure)、布局(layout)、绘制 (draw))均通过ViewRoot来完成

除了上面这三部分,还有一个是在Android应用中存在的一个唯一的WindowManagerGlobal对象,它们的类图关系如下:

应用中的Window对象

应用中的每个Activity对象都有一个类型为Window的成员变量mWindow,定义如下:

java 复制代码
class Activity {
    ...
    private Window mWindow;
    ...
}

Window对象在应用进程代表了一个窗口,这是一块矩形的图像绘制区域。

WMS的角度来说,它并不关系应用中的各种View,他管理和调度的对象是WindowActivity中的View树不管多复杂,最后输出的也仅仅是一张各个View合成的图像而已。

Window是一个抽象类,在Activity类的attach()方法中会对成员变量mWindow进行初始化,代码如下:

java 复制代码
    final void attach(...) {
        ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ...
    }

attach()方法中mWindow实例化成了一个PhoneWindow对象。

而对于PhoneWindow对象,需要我们关注的部分是如下两个成员变量:

java 复制代码
public class PhoneWindow extends Window implements MenuBuilder.Callback {
    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    ViewGroup mContentParent;
    ...
}
  • mDecor的类型是DecorView,它是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。

    • DecorView作为顶级View,它加载的资源文件内部会包含一个android:id="@android:id/content"ViewGroup
    • 这个ViewGroup便是后面用来添加setContentView()方法传入的layout布局文件
    • DecorView具体加载哪种布局文件和主题有关,这部分在PhoneWindowgenerateLayout()方法中体现
  • mContentParent的类型是ViewGroup,它其实就是android:id="@android:id/content"对应的组件

    • 跟踪generateLayout()方法就会发现如下代码:
    java 复制代码
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    • contentParent创建完成后便会赋值给mContentParent,而此处的ID_ANDROID_CONTENT的定义是在Window.java
    java 复制代码
        public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

应用中的WindowManager对象

应用中的每个Activity对象都有一个类型为WindowManager的成员变量mWindowManager,定义如下:

java 复制代码
class Activity {
    ...
    private WindowManager mWindowManager;
    ...
}

mWindowManager用于和WMS通信,不过WindowManager是一个接口类,定义如下:

java 复制代码
public interface WindowManager extends ViewManager {...}

mWindowManager也是在attach()方法中进行的初始化,相关代码如下:

java 复制代码
    final void attach(...) {
        ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ...
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        ...
        mWindowManager = mWindow.getWindowManager();
        ...
    }

attach()方法中,对mWindowManager变量的赋值是调用mWindowgetWindowManager()方法完成的。

但是在这之前,attach()方法中还调用了mWindow对象的setWindowManager()方法。前面我们已经知道mWindow对象的类型是PhoneWindow,不过这个setWindowManager()方法是在基类Window中实现的,代码如下:

java 复制代码
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    public WindowManager getWindowManager() {
        return mWindowManager;
    }

setWindowManager()方法中,调用了WindowManagerImpl类的createLocalWindowManager()方法创建了mWindowManager对象,这个方法也很简单:

java 复制代码
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

createLocalWindowManager()方法中新创建了一个WindowManagerImpl对象,因此,每个Activity中的mWindowManager对象实际上是一个WindowManagerImpl对象,我们再看下这个类的定义:

java 复制代码
public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;
    ...
}

WindowManagerImpl中定义了一个mGlobal变量,它的值是通过WindowManagerGlobal.getInstance()得到的,看样子是个单例模式,代码如下:

java 复制代码
    public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }

getInstance()方法中创建了一个全局唯一的WindowManagerGlobal对象并保存到静态变量sDefaultWindowManager

WindowManagerImpl中各种方法的实现只是在转调WindowManagerGlobal类中的方法,由此可见,一个应用中所有Activity都是通过这个进程内唯一的WindowManagerGlobal对象和WMS通信

WindowManagerGlobal类中还有3个重要的成员变量:

java 复制代码
public final class WindowManagerGlobal {
    ...
    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
    ...
}
  • mViews:保存了应用中所有顶层View对象,也就是DecorView(稍后细讲)
  • mRoots:保存的是和顶层View对象关联的ViewRootImpl对象
  • mParams:保存的是创建顶层Viewlayout参数

建立应用和WMS的联系

我们知道在ActivityThread中的handleResumeActivity()中会创建ViewRootImpl对象并进行显示,我们来看下ViewRootImpl中关键的几个变量:

java 复制代码
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    ...
    final IWindowSession mWindowSession;
    ...
    final W mWindow;
    ...
}

变量的初始化是在构造方法中,代码如下:

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

重点是进行了mWindowSessionmWindow的创建,我们逐一看下

IWindowSession对象的创建

mWindowSession变量的类型是IWindowSession,它是WMS中某个Binder对象的引用对象,mWindowSession的值是通过WindowManagerGlobalgetWindowSession()方法获得的,代码如下:

java 复制代码
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (
            `sWindowSession`== null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

如果sWindowSession为空,先获取InputMethodManager对象,然后调用WMSopenSession()接口来进行创建,代码如下:

java 复制代码
    @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;
    }

openSession()直接创建了一个Session对象,这个Session类我们图像显示部分简单介绍过,它是一个Binder服务类,简要定义如下:

java 复制代码
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {...}

对于用户进程而言,获取到的便是这个Session对象的Binder引用对象,并保存在了WindowManagerGlobal的静态变量sWindowSession中。

因为WindowManagerGlobal在进程中属于单例的存在,因此,所有调用getWindowSession()方法返回的都是这个sWindowSession对象

因此,一个用户进程中的所有ViewRootImpl对象中的mWindowSession都引用的是相同的对象 。而这个mWindowSession对象的作用便是向WMS发起Window相关的Binder调用

应用进程通过sWindowSession对象可以调用到WMS的功能方法了,那么WMS是如何通知或者控制应用进程的呢?

W

图像显示部分已经介绍过ActivityThreadhandleResumeActivity()方法了,最后会调用到WindowManagerGlobal对象的addView()方法,方法内容如下:

java 复制代码
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            ...
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            try {
                root.setView(view, wparams, panelParentView);
            }
            ...
        }
    }

addView()方法中最重要的是创建了ViewRootImpl对象,并通过setView()把它和顶层的View对象关联在了一起。setView()方法简要如下:

java 复制代码
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            ...
            requestLayout();
            ...
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                res = mWindowSession.addToDisplay(mWindow, ...);
            }
            ...
        }
    }
}

setView()方法比较长,我们需要关注的是:

  • 方法中对ViewRootImpl对象和顶层View之间的关联只会执行一次,如果mView不为空,不会再次赋值
  • 方法中调用了SessionaddToDisplay()方法,更为重要的是参数mWindow

mWindow的类型是W,它是一个ViewRootImpl的静态内部类,定义如下:

java 复制代码
    static class W extends IWindow.Stub {
        private final WeakReference<ViewRootImpl> mViewAncestor;
        private final IWindowSession mWindowSession;
        ...
    }

可以看出W是一个实现了IWindow接口的Binder服务类,并且在ViewRootImpl中通过Session.addToDisplay()方法将WBinder引用对象传递给了WMS

我们看下源码中对IWindow接口的描述:

API back to a client window that the Window Manager uses to inform it of interesting things happening.

可以看出WMS通过IWindow接口来通知应用,接下来看下WMS中是如何处理这个对象的

秋地麻袋~,还记得我们前言中的问题吗,快看setView中的requestLayout方法

是不是勾起了作为应用开发者的回忆

Surface 和 Activity 的联系

ViewRootImpl.setView() 会触发 requestLayout(),最终执行到核心方法 performTraversals()(这是 View 绘制三大流程 Measure/Layout/Draw 的入口)

performTraversals() 800 多行。。。简要代码如下:

java 复制代码
// 伪代码逻辑 ViewRootImpl.java
private void performTraversals() {
    // ...
    // 1. 通过 IPC 请求 WindowManagerService (WMS) 对窗口进行布局
    // mSurface 是 ViewRootImpl 的成员变量
    relayoutResult = mWindowSession.relayout(..., mSurface, ...);

    // 2. WMS 在底层创建 SurfaceControl,并将 Native 层的 Surface 信息
    // 回写(Copy)到 ViewRootImpl 的 mSurface 中。
    // 后面会细讲
    
    if (mSurface.isValid()) {
        // Surface 此时已经准备好,可以合成了
    }
    // ...
    performMeasure(...);
    performLayout(...);
    performDraw(...);
}

relayout 过程中,WMS 会与 SurfaceFlinger 通信,分配 Layer(图层)。WMS 将生成的 Surface 信息填充到 ViewRootImpl 传入的 mSurface 参数中。

这样,一个 Activity 就和 SurfaceFlinger 中的 Surface 产生了关联啦。

WMS中建立和应用的关系

前面提到ViewRootImpl对象会通过addToDisplay()方法将IWindowBinder引用对象传递到WMS中,我们先看下addToDisplay()方法:

java 复制代码
    // 方法定义在 com.android.server.wm.Session 中
    @Override
    public int addToDisplay(IWindow window, ...) {
        // mService 的类型便是 WindowManagerService
        return mService.addWindow(this, window, ...);
    }

调用了WMSaddWindow()方法,代码如下:

java 复制代码
public int addWindow(Session session, IWindow client, ...) {
    ...
    synchronized(mWindowMap) {
        ...
        if (mWindowMap.containsKey(client.asBinder())) {
            Slog.w(TAG_WM, "Window " + client + " is already added");
            return WindowManagerGlobal.ADD_DUPLICATE_ADD;
        }
        ...
        final WindowState win = new WindowState(this, session, client, token, parentWindow,
                appOp[0], seq, attrs, viewVisibility, session.mUid,
                session.mCanAddInternalSystemWindow);
        ...
        win.attach();
        mWindowMap.put(client.asBinder(), win);
        win.initAppOpsState();
        ...
    }
    ...
    return res;
}

addWindow()方法代码在200多行,此处我们需要关注的部分是:

  • 创建了WindowState对象并添加到mWindowMap集合中
  • 调用WindowState对象的attach()方法

WindowState后面会详细介绍,此处的关键定义如下:

java 复制代码
/** A window in the window manager. */
class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {
    ...
    final Context mContext;
    final Session mSession;
    final IWindow mClient;
    ...
    void attach() {
        if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
        mSession.windowAddedLocked(mAttrs.packageName);
    }
}
  • mClient是应用进程中Binder服务对象W的引用对象
  • mSession便是WMS对外封装的对象之一。以前面的addToDisplay()为例,实际上只是转调了WMSaddWindow()方法
  • attach()方法也只是调用了mSession对象的windowAddedLocked()方法

继续看下windowAddedLocked()方法:

java 复制代码
    void windowAddedLocked(String packageName) {
        mPackageName = packageName;
        mRelayoutTag = "relayoutWindow: " + mPackageName;
        if (mSurfaceSession == null) {
            mSurfaceSession = new SurfaceSession();
            mService.mSessions.add(this);
            if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
                mService.dispatchNewAnimatorScaleLocked(this);
            }
        }
        mNumWindow++;
    }

windowAddedLocked()方法

  • 首先检查了mSurfaceSession的值,当为空时才会创建SurfaceSession对象
    • 前面图像显示部分介绍过,SurfaceSession对象是用来创建Surface
  • 然后将Session对象本身添加到WMSmSessions列表中

到这里,应用进程就和WMS各自持有了对方的Binder引用对象,形成了一个双向Binder通道,示意图如下:

DecorView

关于DecorView,还是要从我们最熟悉的地方说起。

ActivityonCreate()中调用setContentView()方法时,我们看下发生了什么:

java 复制代码
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

ActivityonCreate()调用的是Window类的setContentView(),这是个抽象方法,具体的实现在PhoneWindow.java中,代码如下:

java 复制代码
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        }
        ...
    }

如果mContentParent对象还没有创建出来,则会调用installDecor()方法,方法如下:

java 复制代码
    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1); // 创建 DecorView
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor); // 为DecorView设置布局格式,并返回 mContentParent
        }
    }

installDecor()方法

  • 先根据成员变量mDecor是否为空来决定是否进行DecorView的创建

    • 对象的创建方法为generateDecor(),内容如下:
    java 复制代码
    protected DecorView generateDecor(int featureId) {
        Context context;
        ... // 省略 context 的创建过程
        return new DecorView(context, featureId, this, getAttributes());
    }
  • 然后通过generateLayout()方法为创建好的DecorView设置布局格式,并返回mContentParent对象

我们再看下DecorView的定义

java 复制代码
/** @hide */
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    ...
}

其实就是个自定义的ViewGroup,只是这个DecorViewAndroid中作为顶级View去使用

WindowManagerService服务

WMS服务的启动,要从SystemServer中的startOtherServices()方法说起,关键代码如下:

java 复制代码
private void startOtherServices() {
    ...
    wm = WindowManagerService.main(..., new PhoneWindowManager());
    ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
            DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
    ...
}

看下main()方法的定义:

java 复制代码
    public static WindowManagerService main(..., WindowManagerPolicy policy) {
        DisplayThread.getHandler().runWithScissors(() ->
                sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
                        onlyCore, policy), 0);
        return sInstance;
    }

main()方法主要是初始化了WindowManagerService对象,我们需要注意的是它的最后的参数WindowManagerPolicy policy,它的具体的实现类是PhoneWindowManager

再看下WindowManagerService的关键定义:

java 复制代码
public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
    ...
    final ArraySet<Session> mSessions = new ArraySet<>();
    final WindowHashMap mWindowMap = new WindowHashMap();
    final WindowManagerPolicy mPolicy;
    ...
    private WindowManagerService(..., WindowManagerPolicy policy) {
        ...
        mPolicy = policy;
        ...
    }
}

其中的关键成员如下:

  • mSessions中储存的是Session服务类的对象,每个应用在WMS中都有一个对应的Session对象,它们都保存在mSessions

  • mWindowMap中类型是WindowHashMap,保存的是所有窗口的WindowState对象。WindowHashMap定义如下:

    java 复制代码
    class WindowHashMap extends HashMap<IBinder, WindowState> {
    }
    • 此处并未做任何删减哈,源码中就是简单地继承了HashMap<IBinder, WindowState>
    • WindowState表示一个窗口的所有属性,它便是是WMS中的窗口。等下细讲
  • mPolicyWMS中执行窗口管理策略的对象,类型是WindowManagerPolicy,这是一个接口类

    • SystemServerstartOtherServices()中可以看出具体的实现类是PhoneWindowManager
    • AndroidWMS只是负责窗口管理的框架部分,通过策略模式将框架和窗口实现细节进行了分离

对比9.05.0源码,WMS关于窗口部分设计变化很大,在了解Android如何添加一个窗口前,我们先看下Android中窗口类型的定义

窗口类型

WMS中定义了3中窗口:应用窗口子窗口系统窗口,相关的定义都是在frameworks/base/core/java/android/view/WindowManager.java中,确切的说应该是在WindowManager.LayoutParams类中定义

应用窗口(application window)

应用窗口是Activity使用的全屏窗口,Android定义了4种不同类型的应用窗口

  • TYPE_BASE_APPLICATION:基础应用窗口,应用中所有窗口都位于它的上层。只能由系统创建
  • TYPE_APPLICATIONActivity的顶层窗口,应用中最常见的一种
  • TYPE_APPLICATION_STARTING:应用启动时由系统创建的窗口,显示一些信息,直到应用创建的窗口显示出来
  • TYPE_DRAWN_APPLICATION:类似于普通的TYPE_APPLICATIONWMS针对类型额外增加了等待机制

子窗口(sub-window)

子窗口是指依附于应用窗口或系统窗口的窗口类型,它们一般随所依附的窗口一起出现或切换到后台。Android中定义了6种子窗口类型

  • TYPE_APPLICATION_PANEL:应用Panel窗口,显示在依附窗口的上层
  • TYPE_APPLICATION_MEDIA:应用Media窗口,通常包含独立的Surface,显示在依附窗口的下层
  • TYPE_APPLICATION_SUB_PANEL:应用子Panel窗口,显示在依附窗口和TYPE_APPLICATION_PANEL的上层
  • TYPE_APPLICATION_ATTACHED_DIALOG:和TYPE_APPLICATION_PANEL类似,但是窗口的布局方式和Activity的顶层窗口相同
  • TYPE_APPLICATION_MEDIA_OVERLAY:一种显示在TYPE_APPLICATION_MEDIA和所依附窗口之间的半透明窗口。一般用来显示Video的字幕或者相机的操作提示界面
  • TYPE_APPLICATION_ABOVE_SUB_PANEL:一种显示在TYPE_APPLICATION_SUB_PANEL之上的窗口

系统窗口(system window)

系统窗口大都用于系统生成的UI中,一般比较独立,种类非常多,我们挑几个常见的看下

  • TYPE_STATUS_BAR:状态栏窗口,系统中只能有一个,在屏幕的最上方
  • TYPE_SEARCH_BAR:搜索栏窗口,系统中只能有一个,在屏幕的最上方
  • TYPE_PHONE:电话窗口,提供用户与电话的交互界面,通常位于所用应用窗口的上方,但是在状态栏的后面
  • TYPE_SYSTEM_ALERT:系统警告窗口,位于所用应用窗口的上方
  • TYPE_TOAST:临时通知窗口,显示一些提示信息

还有很多就不一一列举了,Android系统窗口的定义有39种之多,源码中有着详细的注释,大家可以在WindowManager.java中找到(传送门)

那么WMS中是怎么定义窗口的呢?

WMS中窗口的设计

前面讲过(WMSaddWindow()方法),当向WMS添加一个窗口时,WMS会为其创建一个WindowState。对于WindowState来说,它包含了一个窗口的所有属性,所以它是WMS中真正意义上的窗口

先看下WindowState的关键定义:

java 复制代码
/** A window in the window manager. */
class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {
    ...
    WindowToken mToken;
    AppWindowToken mAppToken;
    ...
    final int mBaseLayer;
    final int mSubLayer;
}

WindowState继承了WindowContainer,又是和书中不一样的地方

对比5.0源码,很明显在Window这部分Android做了重构(设计模式看上去像是组合模式),优化了类结构,抽象出了WindowContainer用来支撑起整个窗口体系

基于Android 9.0源码,窗口体系的关系梳理如下:

类图中涉及到的类的信息下:

  • WindowContainer

    • 成员变量mChildren的类型是WindowList,看实现其实就是ArrayList<WindowState>。又因为WindowToken继承了WindowContainer,所以该成员变量子类都会继承过来
    • 所有拥有相同Token的窗口(WindowState)都会存放在mChildren中,
  • WindowToken

    • 成员变量tokenIBinder对象,具有系统唯一性,向WMSmWindowMap中添加对象时都是使用这个token
    • 定义了AppWindowToken asAppWindowToken(),默认返回null
  • AppWindowToken

    • 继承自WindowToken并重写了AppWindowToken asAppWindowToken()方法,返回this
    • 这样便可以通过asAppWindowToken()的返回值判断是那种Token
  • DisplayContent

    • 继承自WindowContainer,真正意义上的屏幕,WMS中一个DisplayContent实例表示一个屏幕。多屏幕会对应多个实例
    • 内部定义了4种存放Window的容器,都是直接或间接继承自WindowContainer
      • mTaskStackContainers:用来保存所有的应用窗口
      • mAboveAppWindowsContainers:用来保存显示在应用窗口的上面的非应用窗口
      • mBelowAppWindowsContainers:用来保存显示在应用窗口的下面的非应用窗口
      • mImeWindowsContainers:用来保存 IME Window
  • RootWindowContainer

    • 代表WMS中窗口的根容器,所有DisplayContent的实例都会添加到其中
    • RootWindowContainerWMS的构造方法中初始化为mRoot对象

还是回到WindowState,类中定义了两个常量mBaseLayermSubLayer,通过这两个常量便可以确定一个Window所在的层级。这部分就不细讲了哈,涉及点有点多,后面用到再来补充吧,嘻嘻

我们重点看下面两个知识点:WMSaddWindowrelayoutWindow的设计

addWindow方法

addWindow()WMS中建立和应用的关系已经介绍,不再赘述

relayoutWindow方法

前面介绍过,在ActivityonResume阶段,通过ViewRootImplsetView方法开始渲染View的流程。

setView方法中会通过requestLayout()开始测量渲染,这个方法中有一个核心的逻辑就是调用WMSrelayoutWindow(),重新测量Window,简要流程如下:

java 复制代码
//ViewRootImpl.java
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        ...
        scheduleTraversals();
    }
}
private void performTraversals() {
    ...
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
    ...
}
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
        boolean insetsPending) throws RemoteException {
    ...
    int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
            (int) (mView.getMeasuredWidth() * appScale + 0.5f),
            (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
            insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
            mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
            mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
            mPendingMergedConfiguration, mSurface);
    ...
    return relayoutResult;
}

relayoutWindow()通过Sessionrelayout()方法调用到WMSrelayoutWindow()方法

java 复制代码
    @Override
    public int relayout(IWindow window, int seq, ...) {
        ...
        int res = mService.relayoutWindow(this, window, seq, attrs,
                requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
                outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
                outStableInsets, outsets, outBackdropFrame, cutout,
                mergedConfiguration, outSurface);
        ...
        return res;
    }

WMS中的relayoutWindow()代码比较复杂,我们简单看下:

java 复制代码
    public int relayoutWindow(...) {
        ...
        synchronized(mWindowMap) {
            // 从 mWindowMap 取出对应的 WindowState 对象
            WindowState win = windowForClientLocked(session, client, false);
            if (win == null) {
                return 0;
            }
            displayId = win.getDisplayId();
            ...
            // 调用 WindowSurfacePlacer 的 performSurfacePlacement 方法
            // 这个方法其实就是检查清理无用的 Surface 对象,为后面创建 Surface 做准备
            // 详细的方法就不贴出来了,大家可以自行阅读
            mWindowPlacerLocked.performSurfacePlacement(true /* force */);
            if (shouldRelayout) {
                result = win.relayoutVisibleWindow(result, attrChanges, oldVisibility);
                try {
                    // 创建 Surface ,确切的说应该是创建 WindowSurfaceController 对象
                    // WindowSurfaceController 其实就是对 Surface 和 SurfaceControl 的封装
                    // Surface 和 SurfaceControl 的关系在十二章已经介绍啦
                    result = createSurfaceControl(outSurface, result, win, winAnimator);
                } 
                ...
            } else {
                ...
            }
            if (focusMayChange) {
                // 如果窗口发生了变化,调用 updateFocusedWindowLocked 方法重新测量窗口大小
                // 这个方法才是真正的核心测量窗口大小逻辑
                // 方法中会调用 DisplayContent.performLayout 方法开始真正的测量
                if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
                        false /*updateInputWindows*/)) {
                    imMayMove = false;
                }
            }
            ...
            win.mInRelayout = false;
        }
        ...
    }

WMSrelayoutWindow() 方法中,核心逻辑在于 createSurfaceControl 的调用。

java 复制代码
result = createSurfaceControl(outSurface, result, win, winAnimator);

这里的参数 outSurface 至关重要。回看 ViewRootImpl 调用 relayout 的代码:

java 复制代码
// ViewRootImpl.java
mWindowSession.relayout(..., mSurface);

我们可以明确看到,ViewRootImpl 将自己的成员变量 mSurface 传递给了 WMS。 在 WMS 内部,createSurfaceControl 会请求 SurfaceFlinger 创建图层(Layer),并将生成的 NativeSurface 信息回写(Copy)到传入的 outSurface 参数中。

这意味着: 当 relayoutWindow 调用返回时,应用进程 ViewRootImpl 中的 mSurface 对象就已经持有了真正的图形缓冲区引用,随后应用便可以利用这个 Surface 进行绘图。

怎么说~怎么说~。前言的问题是不是明了啦

总结

Android 的窗口系统是一个典型的 C/S 架构,由应用进程(Client)和 WindowManagerService(Server)共同构成:

应用端 (Client)

  • Activity 负责生命周期,持有一个 PhoneWindow 对象来管理视图策略。

  • DecorView 是视图树的根节点。

  • ViewRootImpl 是核心管理者,它连接了 DecorView 和 WMS,并持有 Surface 对象,负责发起绘制流程(Measure/Layout/Draw)。

  • WindowManagerGlobal 是进程单例,负责管理当前进程所有的 ViewRootImplView

通信层 (IPC)

  • IWindowSession:应用向 WMS 发送请求的通道(如 addToDisplay, relayout)。

  • IWindow (W类):WMS 回调应用的通道(如窗口焦点变化、配置改变)。

服务端 (WMS)

  • WindowStateWMS 中代表一个窗口的实体,保存窗口的所有属性。

  • WindowContainer:构建了层级化的窗口容器结构(DisplayContent -> TaskStack -> WindowState)。

  • Surface 管理:WMSrelayoutWindow 过程中计算窗口大小与位置,并与 SurfaceFlinger 通信创建 SurfaceControl,最终将可用的 Surface 返回给应用端。

相关推荐
k***85841 小时前
【SpringBoot】【log】 自定义logback日志配置
android·前端·后端
S***q1922 小时前
Kotlin内联函数优化
android·开发语言·kotlin
小墙程序员2 小时前
在Android中,kotlin 的一些开发技巧(二)
android·kotlin
曾经的三心草2 小时前
JavaEE初阶-多线程1
android·java·java-ee
q***73553 小时前
windows配置永久路由
android·前端·后端
m***9823 小时前
万字详解 MySQL MGR 高可用集群搭建
android·mysql·adb
u***u6853 小时前
Kotlin多平台开发实践
android·开发语言·kotlin
Digitally4 小时前
如何将照片从安卓手机传输到电脑?
android·智能手机·电脑
打工人你好4 小时前
Android 应用逆向分析与架构研究笔记
android·笔记·架构