Android之WMS分析

1 简述

WindowManagerService是有SystemServer进程启动的用于管理窗口的服务。
主要包含功能:窗口的添加更新删除、窗口启动、窗口动画、窗口大小和层级等等。

2 WMS启动流程

SystemServer进程启动后,会去启动一系列服务,其中就包括WMS。

/**
 * 进程的入口点
 */
public static void main(String[] args) {
    new SystemServer().run();
}


private void run() {

    ...

    // 启动 AMS PowerManagerService PackageManagerService 等服务
    startBootstrapServices(t);
    // 启动了BatteryService UsageStatsService WebViewUpdateService
    startCoreServices(t);
    // CameraService AlarmManagerService VrManagerService
    startOtherServices(t);    
    ...

    }

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {

    ...
        
    t.traceBegin("StartWindowManagerService");
    // WMS needs sensor service ready
    ConcurrentUtils.waitForFutureNoInterrupt(mSensorServiceStart, START_SENSOR_SERVICE);
    mSensorServiceStart = null;
    // 创建WMS对象,与AMS关联
    wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
                                   new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
    // 将WMS加入SM,使用的时候去里面拿就行
    ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
                              DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
    ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
                              /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
    t.traceEnd();

    t.traceBegin("SetWindowManagerService");
    // WMS与AMS关联
    mActivityManagerService.setWindowManager(wm);
    t.traceEnd();

    t.traceBegin("WindowManagerServiceOnInitReady");
    // 初始化监视器等等
    wm.onInitReady();
    t.traceEnd();
    
	...

}
复制代码

3 核心方法

3.1 addWindow

// 代码过多,就摘取关键性的
public int addWindow(Session session, IWindow client, int seq,
                     LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
                     Rect outContentInsets, Rect outStableInsets,
                     DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
                     InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
                     int requestUserId) {
    
    // 检查权限,没有权限不能添加窗口(manifest中注册)
    int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
                                         appOp);
    if (res != WindowManagerGlobal.ADD_OKAY) {
        return res;
    }

    WindowState parentWindow = null;
    // ...
    synchronized (mGlobalLock) {

        // 寻找窗口需要添加到哪个displayContent
        final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);

		// 避免重复添加
        if (mWindowMap.containsKey(client.asBinder())) {
            ProtoLog.w(WM_ERROR, "Window %s is already added", client);
            return WindowManagerGlobal.ADD_DUPLICATE_ADD;
        }

        // 判断是否为子窗口,是子窗口就去找父窗口
        if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
            parentWindow = windowForClientLocked(null, attrs.token, false);
        }

        ActivityRecord activity = null;
        final boolean hasParent = parentWindow != null;
        // Use existing parent window token for child windows since they go in the same token
        // as there parent window so we can apply the same policy on them.
        WindowToken token = displayContent.getWindowToken(
            hasParent ? parentWindow.mAttrs.token : attrs.token);
        // If this is a child window, we want to apply the same type checking rules as the
        // parent window type.
        final int rootType = hasParent ? parentWindow.mAttrs.type : type;

        // 没有token就创建
        // 是Toast等类型分别处理
        if (token == null) {
            token = new WindowToken(this, binder, type, false, displayContent,
                                    session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
        } 
        // ...

        // 创建WindowState
        // 类似ActivityRecord用来保存创建窗口状态
        final WindowState win = new WindowState(this, session, client, token, parentWindow,
                                                appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
                                                session.mCanAddInternalSystemWindow);
        // 判断添加窗口的客户端是否死亡
        if (win.mDeathRecipient == null) {
            // Client has apparently died, so there is no reason to
            // continue.
            ProtoLog.w(WM_ERROR, "Adding window client %s"
                       + " that is dead, aborting.", client.asBinder());
            return WindowManagerGlobal.ADD_APP_EXITING;
        }

        // displayContent是否为null
        if (win.getDisplayContent() == null) {
            ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");
            return WindowManagerGlobal.ADD_INVALID_DISPLAY;
        }

        win.attach();
        // windowmap中添加窗口
        mWindowMap.put(client.asBinder(), win);
        win.initAppOpsState();

        // 将windowstate添加到该windowstate对应的windowtoken中
        win.mToken.addWindow(win);
        displayPolicy.addWindowLw(win, attrs);

		// 窗口动画
        final WindowStateAnimator winAnimator = win.mWinAnimator;
        winAnimator.mEnterAnimationPending = true;
        winAnimator.mEnteringAnimation = true;
        // Check if we need to prepare a transition for replacing window first.
        if (activity != null && activity.isVisible()
            && !prepareWindowReplacementTransition(activity)) {
            // If not, check if need to set up a dummy transition during display freeze
            // so that the unfreeze wait for the apps to draw. This might be needed if
            // the app is relaunching.
            prepareNoneTransitionForRelaunching(activity);
        }

        if (displayPolicy.getLayoutHint(win.mAttrs, token, outFrame, outContentInsets,
                                        outStableInsets, outDisplayCutout)) {
            res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
        }
        outInsetsState.set(win.getInsetsState(), win.isClientLocal());

        if (mInTouchMode) {
            res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
        }
        if (win.mActivityRecord == null || win.mActivityRecord.isClientVisible()) {
            res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
        }

        displayContent.getInputMonitor().setUpdateInputWindowsNeededLw();
    }

    Binder.restoreCallingIdentity(origId);

    return res;
}
复制代码

小结
addwindow方法主要是在添加窗口前会进行一系列检查,处理windowToken(有的窗口类型如Toast)需要隐式创建token,完成WindowState和displayContent的处理。

3.2 removeWindow

void removeWindow(Session session, IWindow client) {
    synchronized (mGlobalLock) {
        WindowState win = windowForClientLocked(session, client, false);
        if (win != null) {
            win.removeIfPossible();
            return;
        }

        // Remove embedded window map if the token belongs to an embedded window
        mEmbeddedWindowController.remove(client);
    }
}
复制代码

4 Window、WindowManager与WMS

  • WMS管理窗口
  • Window是一个抽象类,它的实现类是PhoneWindow,主要用来管理view
  • WindowManager是一个接口类,实现类WindowManagerImpl,顾名思义,它是用来管理Window的,会与WMS通过binder进行交互
@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;
    }
}
复制代码

未命名文件.png

4.1 addView

WindowManagerImpl -> addView
WindowManagerGlobal -> addView
ViewRootImpl -> setView

// AIDL跨进程调用 Session.java的addToDisplayAsUser方法
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                                        getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                        mAttachInfo.mDisplayCutout, inputChannel,
                                        mTempInsets, mTempControls);


@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
                        int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
                        Rect outStableInsets,
                        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
                        InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                              outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
                              outInsetsState, outActiveControls, UserHandle.getUserId(mUid));
}


@Override
public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
                              int viewVisibility, int displayId, int userId, Rect outFrame,
                              Rect outContentInsets, Rect outStableInsets,
                              DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
                              InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                              outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
                              outInsetsState, outActiveControls, userId);
}

mService就是WMS,即调用WMS的addWindow方法

复制代码

4.2 removeView

WindowManagerImpl -> removeView
WindowManagerGlobal -> removeView
WindowManagerGlobal -> removeViewLocked
ViewRootImpl -> die
ViewRootImpl -> doDie
ViewRootImpl -> dispatchDetachedFromWindow

try {
    mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}

@Override
public void remove(IWindow window) {
    mService.removeWindow(this, window);
}
复制代码

4.3 updateViewLayout

WindowManagerImpl -> updateViewLayout
WindowManagerGlobal -> updateViewLayout
ViewRootImpl -> setLayoutParams
ViewRootImpl -> scheduleTraversals
ViewRootImpl -> TraversalRunnable
ViewRootImpl -> performTraversals
ViewRootImpl -> relayoutWindow
Seesion -> relayout
WMS -> relayoutWindow
复制代码
  • setLayoutParams会对View进行重新布局包括测量、布局、重绘等。
  • 通过 WindowSession 调用 WindowManagerServicerelayoutWindow更新window窗口。

5 总结

WMS是在SystemServer进程整体管理系统Window添加 更新 删除等流程的服务。并且,出于安全的考虑,应用程序App只能通过binder与WMS进程交互,来管理自身的窗口。

Activity启动过程中,WMS是什么时候添加window窗口的?

onResume中。

@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
                                 String reason) {
    // ...
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        // ...
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                // WMS添加窗口
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    }   
}
复制代码

为什么dialog的创建不能使用Application context?

dialog是TYPE_APPLICATION类型窗口,要求必需是Activity的Token,不是的话系统会抛出BadTokenException异常。
为什么是Activity的token,主要是为了确保dialog弹出的合法,试下一下,如果是Application Context,那么某些应用就可能在后台弹出一个dialog,让用户误以为是当前App弹出的,是不是很危险?!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享