【22】Android高级知识之Window(三) -WMS

一、概述

这次开始到了WindowManagerService(WMS),你可以把它看做一个WindowManager,只不过呢,属于系统服务进程(system_server)中的一员,和应用不在同一进程,所以涉及了一些跨进程通信的内容,如果不清楚的可以去补一下Binder通信机制。这些不是重点,这次重点接着之前没讲完的addToDisplayAsUser。

二、WMS

关于WMS启动的过程,之后会和AMS(ATMS)文章中一起讲解。如果已经有了解Android特有的跨进程通信方式Binder的基础,不难知道WMS是一个Binder对象,这对之后的讲解有点帮助。

2.1 addToDisplay

上篇Window的文章不知道大家还有没有印象mWindowSession#addToDisplayAsUser,这里的Session是通过WindowManagerGlobal#getWindowSession提供的binder对象,帮助我们完成跨进程通信。

	//WindowManagerGlobal.java
    @UnsupportedAppUsage
    public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                try {
                    if (sWindowManagerService != null) {
                        ValueAnimator.setDurationScale(
                                sWindowManagerService.getCurrentAnimatorScale());
                        sUseBLASTAdapter = sWindowManagerService.useBLAST();
                    }
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }

    @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;
        }
    }

这段代码很长,但是很简单,目的是请求服务端的WindowSession对象,即服务端的binder对象。我们看它是怎么做的。首先先通过IPC通信方式获取WMS,之前说过这也是一个binder对象。然后借助WMS的openSession拿到服务端的WindowSession对象。这样做的好处是什么呢?将两次binder通信简化成了一次binder通信,优化了跨进程通信的效率。

服务端的WindowSession对象是一个叫做Session的类。

既然已经进入Session这个类,我们直接来看一下addToDisplayAsUser做了什么。

    //Session.java
    @Override
    public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, @InsetsType int requestedVisibleTypes,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
            float[] outSizeCompatScale) {
        return mService.addWindow(this, window, attrs, viewVisibility, displayId,
                UserHandle.getUserId(mUid), requestedVisibleTypes, outInputChannel, outInsetsState,
                outActiveControls, outAttachedFrame, outSizeCompatScale);
    }

    @Override
    public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, int userId, @InsetsType int requestedVisibleTypes,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
            float[] outSizeCompatScale) {
        return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
                requestedVisibleTypes, outInputChannel, outInsetsState, outActiveControls,
                outAttachedFrame, outSizeCompatScale);
    }

它调用了内部的addToDisplay方法,一般我会把这种行为相同,但是命名有些许不同的方法叫做衍生方法。这里面的mService就是WMS,因为它们属于同一个进程,所以这里的调用已经不是跨进程通信了。并且调用了WMS#addWindow方法。

上面跟了一大段,我们发现现在才是真正进入了WMS,可见系统源码对于职责的封装不可谓是不严格啊,我们继续分析。

//WindowManagerService.java
    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
            float[] outSizeCompatScale) {

	...
	//获取屏幕内容对象,根据屏幕id(一屏多显可能也会有多个id)
	final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
	
	...
	//一个window集合,保存的key是ViewRootImpl$w的binder对象(也可以理解为是window的binder对象)
	if (mWindowMap.containsKey(client.asBinder())) {
                ProtoLog.w(WM_ERROR, "Window %s is already added", client);
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }

	...
	//创建WindowState,服务端管理的window,记录了window全部信息
final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], attrs, viewVisibility, session.mUid, userId,
                    session.mCanAddInternalSystemWindow);

	...
	win.attach();
	//添加到map集合中,key是客户端w的binder对象,value是WindwoState
    mWindowMap.put(client.asBinder(), win);
    win.initAppOpsState();
	
	...
	//WindowState拿到token添加当前的WindowState进去,双向绑定
	win.mToken.addWindow(win);

由于WMS#addWindow篇幅很多,省略了很多细节之后看上去就清楚多了。重要的事情注解都有说明:

  • 通过DisplayContent获取token
  • 根据客户端window创建WindowState对象,保存Window有关的所有信息
  • 把WindowState缓存到WindowMap集合中,key是客户端的w的binder对象
  • 将WindowState和token双向绑定

关于ViewRootImpl#setView中的关于WMS#addToDisplayAsUser的逻辑我们就分析完了。这样一趟分析下来,发现其实也没有做多少事,但是有一点好奇的不知道大家有没有注意到,方法是setView,而addToDisplayAsUser只是在WMS层创建了一个WindowState对象进行管控,并没有对window或者view的操作和绘制。

2.2 relayoutWindow

在上一篇文章中Android基础知识之Window(二)我们有提到过performTraversals这个方法,是准备遍历开始绘制view的,在执行perfromMeasure之前,也就是最早的测量阶段前,还会调用一个方法relayoutWindow,我们在来看一下。

//ViewRootImpl.java
    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {

	...
	relayoutResult = mWindowSession.relayout(mWindow, params,
                    requestedWidth, requestedHeight, viewVisibility,
                    insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
                    mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
                    mTempInsets, mTempControls, mRelayoutBundle);
	...

其实这个有一个relayout和relayoutAsync的方法,两个在Session中都会调用到同一个方法,这里就不过多展示代码了。像之前讲的一样,Session也是借助WMS#relayoutWindow来处理的。

//WMS
public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
            int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
            int lastSyncSeqId, ClientWindowFrames outFrames,
            MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
            InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,
            Bundle outSyncIdBundle) {
        if (outActiveControls != null) {
            outActiveControls.set(null);
        }
        ...
        //根据之前管理的WindowMap取出对应的WindowState
        final WindowState win = windowForClientLocked(session, client, false);
        
        ...
        //这个值是ViewRootImpl传下来的,根据内部mView是可见,设置请求的大小
        if (viewVisibility != View.GONE) {
            win.setRequestedSize(requestedWidth, requestedHeight);
        }
        
        ...
		flagChanges = win.mAttrs.flags ^ attrs.flags;
        privateFlagChanges = win.mAttrs.privateFlags ^ attrs.privateFlags;
        //更新窗口的LayoutParams
        attrChanges = win.mAttrs.copyFrom(attrs);

		...
		//给WMS管理的WindowState设置是否可见状态
		win.setViewVisibility(viewVisibility)
		
		...
        // Create surfaceControl before surface placement otherwise layout will be skipped
        // (because WS.isGoneForLayout() is true when there is no surface.
        if (shouldRelayout && outSurfaceControl != null) {
                try {
                	//根据ViewRootImpl的SurfaceControl创建WMS的SurfaceControl
                    result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
                } catch (Exception e) {
                    displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);

                    ProtoLog.w(WM_ERROR,
                            "Exception thrown when creating surface for client %s (%s). %s",
                            client, win.mAttrs.getTitle(), e);
                    Binder.restoreCallingIdentity(origId);
                    return 0;
                }
       }
       
       ...
       //刷新界面和更新焦点
       // We may be deferring layout passes at the moment, but since the client is interested
       // in the new out values right now we need to force a layout.
       mWindowPlacerLocked.performSurfacePlacement(true /* force */);
       
       ...
       if (focusMayChange) {
           if (updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/)) {
                    imMayMove = false;
           }
       }

}

有负责取出之前创建WindowState、DisplayContent等对象;有设置可见状态;有更新窗口参数,请求窗口大小;然后创建WindowSurfaceControl对象。

createSurfaceControl

//WMS
    private int createSurfaceControl(SurfaceControl outSurfaceControl, int result,
            WindowState win, WindowStateAnimator winAnimator) {
        if (!win.mHasSurface) {
            result |= RELAYOUT_RES_SURFACE_CHANGED;
        }

        WindowSurfaceController surfaceController;
        try {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
            surfaceController = winAnimator.createSurfaceLocked();
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        if (surfaceController != null) {
            surfaceController.getSurfaceControl(outSurfaceControl);
            ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);

        } else {
            // For some reason there isn't a surface.  Clear the
            // caller's object so they see the same state.
            ProtoLog.w(WM_ERROR, "Failed to create surface control for %s", win);
            outSurfaceControl.release();
        }

        return result;
    }

可以看到,应用上层传下来的是自己创建的outSurfaceControl,但是在WMS中,会创建一个SurfaceController,叫做WindowSurfaceController,内部也会创建一个SurfaceControl,然后把SurfaceControl的一些状态拷贝给outSurfaceControl。

//WindowStateAnimator.java
    WindowSurfaceController createSurfaceLocked() {
        final WindowState w = mWin;

        if (mSurfaceController != null) {
            return mSurfaceController;
        }
		//设置是否有surface的标志为false
        w.setHasSurface(false);

        ProtoLog.i(WM_DEBUG_ANIM, "createSurface %s: mDrawState=DRAW_PENDING", this);
		//重置绘制状态
        resetDrawState();
       
        ...
        //创建WindowSurfaceController
		mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
                    flags, this, attrs.type);
        mSurfaceController.setColorSpaceAgnostic(w.getPendingTransaction(),
                    (attrs.privateFlags & LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
		//SurfaceController创建好了,设置是否有surface的标志为true
        w.setHasSurface(true);
		...

这里我们先忽略一下WindowStateAnimator的创建过程,只需要知道,在创建上一篇文章中讲到的创建WindowState的过程中,会判断是否有Animator,没有则直接创建一个WindowStateAnimator对象,并让WindowState持有。而这段代码中,重点是当创建SurfaceController之后,才会把hasSurface设置为true。

我们再来看看WindowPlacerLocked#performSurfacePlacement方法,执行surface位置摆放,即Window显示位置和大小。

//WindowSurfacePlacer.java
private void performSurfacePlacementLoop() {
	...
	try {
    	mService.mRoot.performSurfacePlacement();
    	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;
    	}

    	if (mService.mWindowsChanged && !mService.mWindowChangeListeners.isEmpty()) {
        	mService.mH.removeMessages(REPORT_WINDOWS_CHANGE);
        	mService.mH.sendEmptyMessage(REPORT_WINDOWS_CHANGE);
    	}
	} catch (RuntimeException e) {
    	mInLayout = false;
    	Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
	}
	...
}

核心代码就是这一段,mService.mRoot是WMS中的RootWindowContainer,调用它的performSurfacePlacement执行表面放置操作。

void performSurfacePlacementNoTrace() {
		...
		//获取WindowSurfacePlacer,是一个单例
        final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;

        if (SHOW_LIGHT_TRANSACTIONS) {
            Slog.i(TAG,
                    ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
        }
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
        //开启surface处理
        mWmService.openSurfaceTransaction();
        try {
        	//开始处理surface变化的处理
            applySurfaceChangesTransaction();
        } catch (RuntimeException e) {
            Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
        } finally {
        	//关闭surface处理
            mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
            if (SHOW_LIGHT_TRANSACTIONS) {
                Slog.i(TAG,
                        "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
            }
        }
        
        ...
}

我们继续看看applySurfaceChangesTransaction,对surface变化处理做了什么。

//RootWindowContainer.java
    private void applySurfaceChangesTransaction() {
        // TODO(multi-display): Support these features on secondary screens.
        final DisplayContent defaultDc = mDefaultDisplay;
        final DisplayInfo defaultInfo = defaultDc.getDisplayInfo();
        ...
		// 也就是这个WindowContainer的一系列子windowContainer的集合
        final int count = mChildren.size();
        for (int j = 0; j < count; ++j) {
        	//获取到这个WindowContainer对应的DisplayContent
            final DisplayContent dc = mChildren.get(j);
            //displaycontent执行surface变化的处理
            dc.applySurfaceChangesTransaction();
        }

        // Give the display manager a chance to adjust properties like display rotation if it needs to.
        mWmService.mDisplayManagerInternal.performTraversal(t);
        if (t != defaultDc.mSyncTransaction) {
            SurfaceControl.mergeToGlobalTransaction(t);
        }
    }

通过遍历WindowContainer来获取DisplayContent,对每个DisplayContent执行surface变化的任务。之前有说过,DisplayContent这个对象创建是通过Display模块提供的DisplayId,而这个id一般情况是一个屏幕对应一个,除非有创建虚拟屏幕。

//DisplayContent.java
// TODO: Super unexpected long method that should be broken down...
void applySurfaceChangesTransaction() {

		...
        // Perform a layout, if needed.
        performLayout(true /* initial */, false /* updateInputWindows */);
        pendingLayoutChanges = 0;

        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyPostLayoutPolicy");
        try {
            mDisplayPolicy.beginPostLayoutPolicyLw();
            forAllWindows(mApplyPostLayoutPolicy, true /* traverseTopToBottom */);
            mDisplayPolicy.finishPostLayoutPolicyLw();
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        mInsetsStateController.onPostLayout();

        mTmpApplySurfaceChangesTransactionState.reset();

        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyWindowSurfaceChanges");
        try {
            forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        prepareSurfaces();
		
		...
}

这里两个重要的方法一个performLayout,这个之后进入分析,还有一个forAllWindows,传入的是一个mApplyPostLayoutPolicy的回调方法,它是遍历所有window,并对每个window应用Surface变化的事务。

//DisplayContent.java
    void performLayout(boolean initial, boolean updateInputWindows) {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performLayout");
        try {
            performLayoutNoTrace(initial, updateInputWindows);
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
    }

    private void performLayoutNoTrace(boolean initial, boolean updateInputWindows) {
        if (!isLayoutNeeded()) {
            return;
        }
        clearLayoutNeeded();
        if (DEBUG_LAYOUT) {
            Slog.v(TAG, "-------------------------------------");
            Slog.v(TAG, "performLayout: dw=" + mDisplayInfo.logicalWidth
                    + " dh=" + mDisplayInfo.logicalHeight);
        }
        int seq = mLayoutSeq + 1;
        if (seq < 0) seq = 0;
        mLayoutSeq = seq;
        mTmpInitial = initial;
        
        // First perform layout of any root windows (not attached to another window).
        forAllWindows(mPerformLayout, true /* traverseTopToBottom */);

        // Now perform layout of attached windows, which usually depend on the position of the
        // window they are attached to. XXX does not deal with windows that are attached to windows
        // that are themselves attached.
        forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */);

        // Window frames may have changed. Tell the input dispatcher about it.
        mInputMonitor.setUpdateInputWindowsNeededLw();
        if (updateInputWindows) {
            mInputMonitor.updateInputWindowsLw(false /*force*/);
        }
    }

这里的两个forAllWindow,同样是遍历了所有的window,只不过它们的回调方法不同,一个处理遍历所有window,让它们应用Layout的事务,一个遍历所有window,让它们应用LayoutAttach的事务。这几个回调方法,我们就不继续跟了,想要了解的可以自行查看一下源码。
总结就是先遍历所有窗口,对每个窗口应用表面变化,接着对每个根窗口执行布局操作,再接着对每个附加窗口(依赖其他窗口位置的窗口)执行布局操作。最后根据updateInputWindows的状态,更新input window(输入窗口)。

updateFocusedWindowLocked
我们继续看WMS#relayoutWindow中的另一个方法updateFocusedWindowLocked,通过RootWindowContainer遍历所有的DisplayContent,执行它们的updateFocusedWindowLocked方法。

//RootWindowContainer.java
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
        mTopFocusedAppByProcess.clear();
        boolean changed = false;
        int topFocusedDisplayId = INVALID_DISPLAY;
        // Go through the children in z-order starting at the top-most
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final DisplayContent dc = mChildren.get(i);
            changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);
...
}

//DisplayContent.java
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,
            int topFocusedDisplayId) {
...
//计算新焦点窗口是哪个
        WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
        if (mCurrentFocus == newFocus) {
            return false;
        }
...
//为输入法更新输入窗口
        adjustForImeIfNeeded();
...
}

所以直接看DisplayContent#updateFocusedWindowLocked,就是计算新焦点窗口,并且为输入法更新之后需要输入的窗口。

补充

在这里插入图片描述
1、经过分析我们知道ViewRootImpl会创建SurfaceControl传递给WMS,WMS在创建WindowSurfaceController之后,会调用surfaceController.getSurfaceControl(outSurfaceControl)把上层的outSurfaceControl作为参数传递进去
2、WindowSurfaceController也会自己创建一个SurfaceControl,把它的状态信息拷贝给outSurfaceControl
2、创建WindowSurfaceController的过程中,SurfaceControl会和SurfaceFlinger(SF)进行IPC通信请求一个表面(surface),SF会创建一个Layer对象来表示这个表面
3、创建Layer对象后,会返回一个Handler(句柄)给SurfaceControl,这个句柄包含了新建Layer的相关信息
4、WindowSurfaceController持有这个SurfaceControl来管理窗口表面的属性和生命周期

三、总结

1、ViewRootImpl#setView通过WindwoSession来管理window
2、借助WMS的binder对象调用openSession来获取WindowSession对象,服务端叫Session
3、WMS#addWindow根据客户端window创建了对应的WindowState对象
4、用一个WindowMap集合缓存,key是客户端ViewRootImpl$W的binder对象
5、WindowState和WindowToken双向绑定
6、relayoutWindow对窗口设置了是否可见状态、窗口大小位置、WindowSurfaceController
7、WindowSurfaceController对ViewRootImpl#SurfaceControl状态进行了重新赋值
8、WindowSurfacePlacer对Window进行了布局
9、relayoutWindow更新了焦点窗口

文章讲了这么多,有对Window在WMS层管理进行了创建WindowState,和对window的重新布局,比如刷新大小位置以及焦点的更新。那么View又是怎么处理的呢?我们下一篇文章中继续探索Window的真相。

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值