【Android 13源码分析】WMS-添加窗口(addWindow)流程-1-应用进程处理

在了解完Activity启动流程后,现在目标应用的进程已经启动了,但是离用户在屏幕上看到Activity下的UI内容还有一段距离。 一个窗口想要显示在屏幕上,还需要经过3大步骤:

    1. addWindow流程

    这一步是创建 WindowState,并且挂载到窗口树上。

    1. relayout流程

    addWindow执行后,WindowState就创建和挂载好了,但是WindowState毕竟也是一个容器,没有真正的UI内容。 执行relayout流程时会触发真正持有UI数据的Surface的创建,然后会将这个Surface返回到应用进程,应用进程在进行View的绘制三部曲。 除了创建Surface的逻辑,relayoutWindow流程还会触发窗口位置的摆放逻辑。

    1. finishDrawing流程

    真正显示到屏幕上的内容不是Activity也不是Window,而是View树绘制的内容。 经过上面2步,应用进程已经有Surface了, 并且执行绘制三部曲,也就是说View树的UI数据也就都绘制到Surface下的buff中了。绘制完成就需要通知SurfaceFlinger进行合成了, 只有经过SurfaceFlinger处理后,才能真正显示到屏幕上。

绝大部分情况下,一个窗口的显示,这三步是是必经流程。(开机动画是直接通过SurfaceFlinger绘制的)

本篇分析第一步:addWindow流程

当前为Activity短暂的一生系列的第三块内容, 建议先看完 WindowContainer窗口层级Activity启动流程

1. 概述

先对比一下一个应用启动后窗口的区别:

红色部分就是启动应用后多出来的部分,在 DefaultTaskDisplayArea 节点下多出来这么一个层级:

arduino 复制代码
Task
    ActivityRecord
        WindowState (就是那个 9c20028)

其中 Task 和 ActivityRecord 是如何挂载上去的在Activity启动流程已经介绍了,本篇要分析的 addWindow 的流程最重要的就是搞明白窗口对应的WindowState是如何创建并且挂载到窗口树中的。

整个流程框图如下:

    1. 应用进程首先会创建出一个的Window
    1. 执行WindowManagerGlobal::addView方法,最终触发ViewRootImpl::setView方法来触发夸进程通信,通知WMS执行addWindow逻辑
    1. 应用和WMS通信是通过Session这个类,具体是调用了 Session::addToDisplayAsUser这个方法
    1. system_server进程的WMS执行addWindow方法时,会根据参数创建出一个WindowState,并且将其挂载到对应的WindowToken下(也就是挂载到窗口树中)

后面的内容也是围绕着这4点做详细解释首先介绍App进程的处理,然后介绍system_server进程的处理。

2. APP进程流程

先看应用层是需要做哪些事:应用进程启动后,会执行LaunchActivityItem和ResumeActivityItem 这2个事务,对应执行到Activity的onCreate和onResume生命周期,这其中肯定也涉及到了Window相关的操作。

调用链如下:

arduino 复制代码
LaunchActivityItem::execute
    ActivityThread::handleLaunchActivity
        ActivityThread::performLaunchActivity
            Instrumentation::newActivity      --- 创建Activity
            Activity::attach                  --- 创建Window
                Window::init
                Window::setWindowManager
            Instrumentation::callActivityOnCreate  
                Activity::performCreate
                    Activity::onCreate 

ResumeActivityItem::execute
    ActivityThread::handleResumeActivity
        ActivityThread::performResumeActivity   
            Activity::performResume   
                Instrumentation::callActivityOnResume
                    Activity::onResume        
        WindowManagerImpl::addView           --- 创建ViewRootImpl
            WindowManagerGlobal::addView   
                ViewRootImpl::setView        --- 与WMS通信 addView

根据这个调用链可知:先执行onResume,再执行addView。所以执行了onResume只是Activity可见,不代表View都显示了,可能都还没触发WMS的绘制,如果后续的任何一个地方出了问题,我们写在XML里的布局都不会显示出来。(以前写App的时候以为执行了onResume屏幕上就显示UI了)

2.1 创建Window逻辑

这块的执行是在 LaunchActivityItem 事务的执行流程中,到Activity的onCreate的生命周期之间, 具体的位置可以看上面的调用链,开始撸代码.

ini 复制代码
# ActivityThread
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            ......
            // 定义window
            Window window = null;
            if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                // 正常不执行这里
                window = r.mPendingRemoveWindow;
                r.mPendingRemoveWindow = null;
                r.mPendingRemoveWindowManager = null;
            }
            ......
            // 注意token传递的是ActivityRecord的token
            // 这里的window正常逻辑为null
            activity.attach(...,r.token,, window, ...);
            ......
    }

# Activity
    final void attach(......) {
        ......
        // 创建PhoneWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        // 一些设置
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        // 留意这边将Activity设置为setCallback
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ......
        // 设置window的token为 ActivityRecord
        mToken = token;
        ......
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        ......
    }

我们在Activity通过getWindow方法返回的就是这个mWindow,首先得确认这个,别分析了半天分析错了Window对象,毕竟framework的代码这么多。

这里有个面试点,在Activity::attach中看到mWindow 原來是一个PhoneWindow的对象,PhoneWindow是Window的唯一子类,Window是个抽象类,所以也确实没有办法直接new。

然后是一堆设置,这里需要注意 setCallback 方法,是将Activity设置给了Window,这里有什么用呢? 像configruation的改变和input事件的传递流程都是先走到Window的,因为在WMS模块没有Activity的概念,只有Window,那么最后是怎么走到Activity呢?就是这里设置的setCallback。当然这个在当前分析的addWindow流程没有关系,但是需要有点印象。

再下面的setWindowManager和getWindowManager两个方法也很有意思,因为咋一看有点矛盾,在一个地方set又get感觉很多余,因为这里set和get返回的对象,其实不是同一个对象。

2.1.1 setWindowManager,getWindowManager

ini 复制代码
# Window
    // 应用Token
    private IBinder mAppToken;

    // wm :WindowManager对象,注意下传进来的值
    // appToken :这个就是AMS中与当前Activity对应的ActivityRecord
    // appName :Activity全类名
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        // 将ActivityRecord设置给mAppToken
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        // 根据强制也能看出 mWindowManager 是WindowManagerImpl的类型
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    public WindowManager getWindowManager() {
        return mWindowManager;
    }

这里将传递进来的 wm, 强转成WindowManagerImpl 后调用其 createLocalWindowManager方法。

该函数重新创建返回了一个 WindowManagerImpl 对象。 所以说setWindowManager 和 setWindowManager 的不是同一个对象, WindowManagerImpl::createLocalWindowManager方法如下:

ini 复制代码
# WindowManagerImpl
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken);
    }
    private WindowManagerImpl(Context context, Window parentWindow,
            @Nullable IBinder windowContextToken) {
        mContext = context;
        mParentWindow = parentWindow;
        mWindowContextToken = windowContextToken;
    }

这边注意的是将 Window 设置给了 mParentWindow。 相当于通过新创建的PhonWindow创建了一个WindowManagerImpl,作为其mWindowManager的对象。

到这里创建Window相关的就分析完了,创建的这个Window其实是 PhoneWindow 接下来看APP层的addWindow是如何触发的。

建议要理清楚 Window, PhoneWindow ,WindowManagerImpl,WindowManager这几个类的区别和联系,不要搞混了。

2.2 addWindow相关

执行到onCreate之前, 已经创建好了Window,所以我们在Activity::onCreate可以把我们的XML布局设置过去,也能通过 getWindow来做一些操作。

Window创建好后就要执行 addWindow逻辑了,根据调用链,是 ResumeActivityItem事务触发的,这个事务最终会执行到 Activity::onResume生命周期。

接下来看一遍代码的执行流程:

ini 复制代码
# ActivityThread
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) {
        ......
        // 触发onResume
        if (!performResumeActivity(r, finalStateRequest, reason)) {
            return;
        }
        ......
        // 拿到activity
        final Activity a = r.activity;
        ......
        if (r.window == null && !a.mFinished && willBeVisible) {
            // 将本地的window设置到activityRecord中
            r.window = r.activity.getWindow();
            // 获取DecorView
            View decor = r.window.getDecorView();
            // 设置不可见  在后面调用Activity::makeVisible会设为可见
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            // 获取参数
            WindowManager.LayoutParams l = r.window.getAttributes();
            // DecorView设置给Activity
            a.mDecor = decor;
            // 设置Activity的windowType,注意这个type,才是应用的窗口类型
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            ......
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    // 重点:执行addView,并设置mWindowAdded=true
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }

        } else if (!willBeVisible) {
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }
    }

主要是一些属性的设置然后执行addView, 这里比较需要注意的就是Activity的windowType为TYPE_BASE_APPLICATION = 1, 还有个TYPE_APPLICATION=2,目前已知是在创建ActivityRecord时使用。 然后我们已经知道wm就是WindowManagerImpl了,但是不应该是addWindow么,怎么成addView了呢?接着去看下面流程。

less 复制代码
# WindowManagerImpl
    // 单例
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance()

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyTokens(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

这个方法并没有啥复杂的,直接调到了WindowManagerGlobal,不过这里也有2个需要注意的点:

    1. WindowManagerGlobal是个单例,那就是说一个进程仅此一个
    1. 这里将mParentWindow传递了过去,上面分析的时候知道这个mParentWindow其实就是我们创建的PhoneWindow
csharp 复制代码
# WindowManagerGlobal

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        ......
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            // 调整window参数,设置token,比如title,和硬件加速的标志位
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            ......
        }
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            ......
            // 这一段的意思是如果执行过addView的话,再执行就报错
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }
            ......
            IWindowSession windowlessSession = null;
            ......
            // 对应应用来说windowlessSession是为null的
            if (windowlessSession == null) {
                // 重点* 创建ViewRootImpl
                root = new ViewRootImpl(view.getContext(), display);
            } else {
                root = new ViewRootImpl(view.getContext(), display,
                        windowlessSession);
            }
            // 设置参数到 decorView
            view.setLayoutParams(wparams);
            // 添加到对应集合,看得出来在WindowManagerGlobal中这3个对象应该是要一一对应的
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            // do this last because it fires off messages to start doing things
            try {
                // 重点 * 调用ViewRootImpl::setView
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

这段代码最重要的就是做了2件事:

    1. ViewRootImpl的创建 (ViewRootImpl在整个WMS系统中是非常重要的一个类)
    1. 执行ViewRootImplL::setView方法, 这里也是应用进程处理的终点,剩下的就是跨进程交给WMS处理了

2.3 ViewRootImpl::setView

scss 复制代码
# ViewRootImpl

    final IWindowSession mWindowSession;
    
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            // 当前第一次执行肯定为null
            if (mView == null) {
                mView = view;
                ......
                mAdded = true; // 表示已经add
                int res; // 定义稍后跨进程add返回的结果
                requestLayout();  // 非常重要的方法--请求布局更新
                InputChannel inputChannel = null; // input事件相关
                if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                inputChannel = new InputChannel();
                }
                ......
                try {
                    ......
                    // 重点* 这里通过binder通信,调用WMS的 addWindow方法
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
                            mTempControls);
                    ......
                }
                // 后续流程与addWindow主流程无关,但是也非常重要
                ......
                // 计算window的尺寸
                final Rect displayCutoutSafe = mTempRect;
                state.getDisplayCutoutSafe(displayCutoutSafe);
                final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
                mWindowLayout.computeFrames(mWindowAttributes, state,
                        displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
                        UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
                        mInsetsController.getRequestedVisibilities(),
                        getAttachedWindowFrame(), 1f /* compactScale */, mTmpFrames);
                setFrame(mTmpFrames.frame);
                ......
                if (res < WindowManagerGlobal.ADD_OKAY) {
                    ......// 对WMS调用后的结果判断是什么错误
                }
                ......
                view.assignParent(this); //这就是为什么decorView调用getParent返回的是ViewRootImpl的原因
                ......
            }
        }
    }

这个方法是核心方法,处理了很多事,都加载备注上了。为了有一个宏观的印象,这里将其触发的各个调用链整理出来。

arduino 复制代码
ViewRootImpl::setView
    ViewRootImpl::requestLayout
        ViewRootImpl::scheduleTraversals             
            ViewRootImpl.TraversalRunnable::run          --- Vsync相关--scheduleTraversals
                ViewRootImpl::doTraversal
                  ViewRootImpl::performTraversals 
                     ViewRootImpl::relayoutWindow        --- relayoutWindow
                     ViewRootImpl::performMeasure        --- View绘制三部曲
                     ViewRootImpl::performLayout
                     ViewRootImpl::performDraw        
                     ViewRootImpl::createSyncIfNeeded    --- 绘制完成finishDrawing
    WindowSession.addToDisplayAsUser                     --- addWindow
    WindowLayout::computeFrames                          --- 窗口大小计算

这里要注意:虽然看顺序好像 addWindow流程是在relayoutWindow执行前,但是因为 doTraversal是异步的,所以还是先执行addWindow流程的。

回到当前主题,继续跟踪addWindow流程,也就是addToDisplayAsUser,看来上面的setView方法还有2个点不清楚:

  1. mWindowSession是什么?
  2. 参数里的mWindow是什么?

2.3.1 ViewRootImpl的mWindowSession是什么

arduino 复制代码
# ViewRootImpl

    final W mWindow;
    final IWindowSession mWindowSession;

    public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),
                false /* useSfChoreographer */);
    }
    public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
        boolean useSfChoreographer) {
            mContext = context;
            mWindowSession = session;
            ......
            mWindow = new W(this);
            ......
        }

WindowManagerGlobal::addView下构造ViewRootImpl的通过2个参数的构造方法,所以他的session就是WindowManagerGlobal.getWindowSession()

这里提一嘴,既然会这么设计,那么说明在Framework中肯定不是这一种方式获取session,比如画中画就是另一种,以后会提到,当前留个印象。

java 复制代码
# WindowManagerGlobal

    @UnsupportedAppUsage
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    // Emulate the legacy behavior.  The global instance of InputMethodManager
                    // was instantiated here.
                    // TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
scala 复制代码
# WindowManagerService
    @Override
    public IWindowSession openSession(IWindowSessionCallback callback) {
        return new Session(this, callback);
    }
# Session

class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
    final WindowManagerService mService;
    ......
    @Override
    public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
                requestedVisibilities, outInputChannel, outInsetsState, outActiveControls);
    }
}

所以这里的WindowManagerGlobal::getWindowSession返回的就是一个Session对象。Session继承IWindowSession.Stub,并且内部持有WMS引用。

2.3.2 ViewRootImpl的mWindow是什么

调用addToDisplayAsUser这个方法传递的mWindow,他并不是前面分析的Activity的那个Window,上面mWindow也是在ViewRootImpl的构造方法里赋值的。那这个W是什么呢?

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

            W(ViewRootImpl viewAncestor) {
                mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
                mWindowSession = viewAncestor.mWindowSession;
            }
        }

所以这里的mWindow只是一个内部类W的对象,这个W继承了IWindow.Stub,那也是用例binder通信的,W内部有一个ViewRootImpl弱引用。

2.4 APP进程小结

addWindow的流程,在APP进程到此就结束了,后面的逻辑由WMS执行。

2.4.1 二级框图

应用进程启动后,会执行2个事务,分别触发到Activity的 onCreate和onResume2个常见的生命周期,所以这里分为了2个分支。

onCreate 分支

    1. 首先肯定是要创建Activity的
    1. 然后创建出Window,Window是抽象类,PhoneWindow是Window的唯一实现类。
    1. 执行到 onCreate 生命周期

onResume 分支

    1. 先触发了 onResume 的执行流程
    1. 执行WindowManagerImpl::addView
    1. 创建核心类 ViewRootImpl
    1. 执行关键函数 ViewRootImpl::setView ,跨进程通信后续流程就由WMS执行了

2.4.2 知识点小结:

一个进程内有个Window的总管家:WindowManagerGlobal。当Activity创建并初始化PhoneWindow后,WindowManagerImpl会调用WindowManagerGlobal的addView方法,将后续流程交给其处理。

WindowManagerGlobal会创建一个ViewRootImpl,WindowManagerGlobal内部还有3个集合,将Window的DecorView,参数LayoutParams和新创建的ViewRootImpl一一添加到对应的集合中。

后续流程由ViewRootImpl进行,ViewRootImpl的addView方式会通过Session最终调用到WMS的addWindow方法。

    1. Activity里的window其实是PhoneWindow,因为Window是抽象类,而PhoneWindow是其唯一子类
    1. Window的windowManager是WindowManagerImpl,内部有2个重要成员,DevordViw和WindowManagerImpl,Window本身并没有内容所以DevordViw才是UI的实际载体
    1. WindowManagerGlobal是单例类,一个进程只有一个,内部维护了3个集合

还有3个重要的方法:

Activity::attach

    1. 创建了PhoneWindow
    1. 设置了WindowManagerImpl作为WindowManager

WindowManagerGlobal::addView

    1. 创建ViewRootImpl
    1. 执行ViewRootImpl::setView

ViewRootImpl::setView

    1. IWindowSession::addToDisplayAsUser : 代表着addWindow流程在App进程结束,后面由WMS进行
    1. requestLayout:请求更新布局,触发relayoutWindow流程
    1. computeFrames : 计算窗口大小

ViewRootImpl::setView 这个方法比较长,这也是我目前比较熟悉的几个事情,也许还有重要分支被我忽略了。

其实有一个疑问,明明addWindo流程,但是到了WindowManagerImpl就变成了addView,传递的也是DecoreView,再到和WMS同信的时候,参数里连DecoreView都不剩了,这怎么能叫addWindow流程呢? 带着这个疑问下一篇将介绍WMS到底是怎么做的。

【Android 13源码分析】WMS-添加窗口(addWindow)流程-1-应用进程处理

【Android 13源码分析】WMS-添加窗口(addWindow)流程-2-SystemService进程处理

相关推荐
子非衣2 小时前
MySQL修改JSON格式数据示例
android·mysql·json
openinstall全渠道统计5 小时前
免填邀请码工具:赋能六大核心场景,重构App增长新模型
android·ios·harmonyos
双鱼大猫5 小时前
一句话说透Android里面的ServiceManager的注册服务
android
双鱼大猫5 小时前
一句话说透Android里面的查找服务
android
双鱼大猫5 小时前
一句话说透Android里面的SystemServer进程的作用
android
双鱼大猫5 小时前
一句话说透Android里面的View的绘制流程和实现原理
android
双鱼大猫6 小时前
一句话说透Android里面的Window的内部机制
android
双鱼大猫7 小时前
一句话说透Android里面的为什么要设计Window?
android
双鱼大猫7 小时前
一句话说透Android里面的主线程创建时机,frameworks层面分析
android
苏金标7 小时前
android 快速定位当前页面
android