Window、WindowManager和WindowManagerService

前言

Window表示一个窗口,日常开发时接触的不多。但是在一些场景下会使用到window,比如在桌面显示一个悬浮窗。

Window时一个抽象类,具体实现是PhoneWindow。创建一个Window,通过windowManager即可完成

WindowManager是外界访问Window的入口,Window的具体实现在WindowsManagerService中。

WindowManager和 WindowManagerService 的交互是一个IPC过程

Android 中所有的视图都是通过Window来呈现的,不管是 Activity、Dialog 还是 Toast,它们的视图实际上都是附加在 Window 上的,因此Window 实际是 View 的直接管理者。

Window与WindowManager的简单使用

查看代码
Button extraButton = new Button(this);
extraButton.setText("extraButton");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.WRAP_CONTENT,0,0, PixelFormat.TRANSPARENT);
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
layoutParams.gravity = Gravity.LEFT|Gravity.TOP;
layoutParams.type = 30;
layoutParams.x = 100;
layoutParams.y = 300;
getWindowManager().addView(extraButton,layoutParams);;

LayoutParams中的flags和type这两个参数比较重要,下面对其进行说明。

Flags 参数表示 Window 的属性,它有很多选项,通过这些选项可以控制 Window 的显示特性,这里主要介绍几个比较常用的选项

FLAG_NOT_FOCUSABLE

表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的Window。

FLAG_NOT_TOUCH_MODAL

在此模式下,系统会将当前 Window 区域以外的单击事件传递给底层的 Window,当前Window 区域以内的单击事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他Window 将无法收到单击事件。

FLAG_SHOW_WHEN_LOCKED

开启此模式可以让Window显示在锁屏的界面上。

Type参数表示Window的类型,Window有三种类型,分别是应用Window、子Window和系统Window。应用类Window对应着一个Activity。子Window不能单独存在,它需要附属在特定的父Window之中,比如常见的一些Dialog就是一个子Window。系统Window是需要声明权限在能创建的Window,比如Toast和系统状态栏这些都是系统Window.

Window 是分层的,每个 Window 都有对应的 z-ordered,层级大的会覆盖在层级小的Window的上面。

应用层级为1~99,子Window层级1000~1999,系统Window层级2000~2999。

如果想要Window位于所有Window最顶层,那么采用较大层级即可。

需要声明权限∶<uses-permission android∶name="android.permission.SYSTEM_ALERT_WINDOW">。

WindowManager 所提供的功能很简单,常用的只有三个方法,即添加 View、更新 View和删除View.这三个方法定义在ViewManager 中,而WindowManager 继承了ViewManager。

Window内部机制

Window是一个抽象的概念,每一个Window都对应着一个View 和一个ViewRootImpl,是以View 的形式存在。从 WindowManager 的定义也可以看出,它提供的三个接口方法addView、updateViewLayout 以及 removeView都是针对 View的。

Window的添加删除更新过程需要通过 WindowManager 的 addView来实现,WindowManager是一个接口,它的真正实现是WindowManagerImpl类。而WindowManagerImpl的代码中,真正调用的是WindowManagerGlobal的代码。WindowManagerGlobal以工厂模式向外提供自己的实例。

Window的添加过程

WindowManagerGlobal

1、检查参数是否合法,如果是子window那么还需要调整一些布局参数

public void addView(View view, ViewGroup.LayoutParams params,
                    Display display, Window parentWindow, int userId) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (display == null) {
        throw new IllegalArgumentException("display must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        // If there's no parent, then hardware acceleration for this view is
        // set from the application's hardware acceleration setting.
        final Context context = view.getContext();
        if (context != null
            && (context.getApplicationInfo().flags
                & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
            wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
        }
    }

2、创建ViewRootImpl并将View添加到列表中

@UnsupportedAppUsage
private final ArrayList<View> mViews = new ArrayList<View>();
@UnsupportedAppUsage
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
@UnsupportedAppUsage
private final ArrayList<WindowManager.LayoutParams> mParams =
    new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();

mViews存储的是所有Window 所对应的View,

mRoots 存储的是所有Window 所对应的 ViewRootlmpl,

mParams 存储的是所有Window 所对应的布局参数,

mDyingViews则存储了那些正在被删除的View对象,或者说是那些已经调用removeView方法但是删除操作还未完成的 Window 对象

3、通过VIewRootImpl来更新界面并完成window添加过程

由ViewRootImpl的setView方法来完成。

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

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

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }

setView内部通过requestLayout完成异步刷新请求,在通过WindowSession完成Window添加过程

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                mAttachInfo.mDisplayState = mDisplay.getState();
                mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

                mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
                mFallbackEventHandler.setView(view);
                mWindowAttributes.copyFrom(attrs);
                if (mWindowAttributes.packageName == null) {
                    mWindowAttributes.packageName = mBasePackageName;
                }
                mWindowAttributes.privateFlags |=
                        WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;

                attrs = mWindowAttributes;
                setTag();

                if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
                        & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
                        && (attrs.flags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) {
                    Slog.d(mTag, "setView: FLAG_KEEP_SCREEN_ON changed from true to false!");
                }
                // Keep track of the actual window flags supplied by the client.
                mClientWindowLayoutFlags = attrs.flags;

                setAccessibilityFocus(null, null);

                if (view instanceof RootViewSurfaceTaker) {
                    mSurfaceHolderCallback =
                            ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
                    if (mSurfaceHolderCallback != null) {
                        mSurfaceHolder = new TakenSurfaceHolder();
                        mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
                        mSurfaceHolder.addCallback(mSurfaceHolderCallback);
                    }
                }

                // Compute surface insets required to draw at specified Z value.
                // TODO: Use real shadow insets for a constant max Z.
                if (!attrs.hasManualSurfaceInsets) {
                    attrs.setSurfaceInsets(view, false /*manual*/, true /*preservePrevious*/);
                }

                CompatibilityInfo compatibilityInfo =
                        mDisplay.getDisplayAdjustments().getCompatibilityInfo();
                mTranslator = compatibilityInfo.getTranslator();

                // If the application owns the surface, don't enable hardware acceleration
                if (mSurfaceHolder == null) {
                    // While this is supposed to enable only, it can effectively disable
                    // the acceleration too.
                    enableHardwareAcceleration(attrs);
                    final boolean useMTRenderer = MT_RENDERER_AVAILABLE
                            && mAttachInfo.mThreadedRenderer != null;
                    if (mUseMTRenderer != useMTRenderer) {
                        // Shouldn't be resizing, as it's done only in window setup,
                        // but end just in case.
                        endDragResizing();
                        mUseMTRenderer = useMTRenderer;
                    }
                }

                boolean restore = false;
                if (mTranslator != null) {
                    mSurface.setCompatibilityTranslator(mTranslator);
                    restore = true;
                    attrs.backup();
                    mTranslator.translateWindowLayout(attrs);
                }
                if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs);

                if (!compatibilityInfo.supportsScreen()) {
                    attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
                    mLastInCompatMode = true;
                }

                mSoftInputMode = attrs.softInputMode;
                mWindowAttributesChanged = true;
                mAttachInfo.mRootView = view;
                mAttachInfo.mScalingRequired = mTranslator != null;
                mAttachInfo.mApplicationScale =
                        mTranslator == null ? 1.0f : mTranslator.applicationScale;
                if (panelParentView != null) {
                    mAttachInfo.mPanelParentWindowToken
                            = panelParentView.getApplicationWindowToken();
                }
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    inputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;

                if (mView instanceof RootViewSurfaceTaker) {
                    PendingInsetsController pendingInsetsController =
                            ((RootViewSurfaceTaker) mView).providePendingInsetsController();
                    if (pendingInsetsController != null) {
                        pendingInsetsController.replayAndAttach(mInsetsController);
                    }
                }

                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    adjustLayoutParamsForCompatibility(mWindowAttributes);
                    controlInsetsForCompatibility(mWindowAttributes);
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
                            mTempControls);
                    if (mTranslator != null) {
                        mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
                        mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
                    }
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

                mAttachInfo.mAlwaysConsumeSystemBars =
                        (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0;
                mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
                mInsetsController.onStateChanged(mTempInsets);
                mInsetsController.onControlsChanged(mTempControls);
                computeWindowBounds(mWindowAttributes, mInsetsController.getState(),
                        getConfiguration().windowConfiguration.getBounds(), mTmpFrames.frame);
                setFrame(mTmpFrames.frame);
                if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
                if (res < WindowManagerGlobal.ADD_OKAY) {
                    mAttachInfo.mRootView = null;
                    mAdded = false;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    switch (res) {
                        case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                        case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not valid; is your activity running?");
                        case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not for an application");
                        case WindowManagerGlobal.ADD_APP_EXITING:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- app for token " + attrs.token
                                    + " is exiting");
                        case WindowManagerGlobal.ADD_DUPLICATE_ADD:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- window " + mWindow
                                    + " has already been added");
                        case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
                            // Silently ignore -- we would have just removed it
                            // right away, anyway.
                            return;
                        case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                    + mWindow + " -- another window of type "
                                    + mWindowAttributes.type + " already exists");
                        case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                    + mWindow + " -- permission denied for window type "
                                    + mWindowAttributes.type);
                        case WindowManagerGlobal.ADD_INVALID_DISPLAY:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                    + mWindow + " -- the specified display can not be found");
                        case WindowManagerGlobal.ADD_INVALID_TYPE:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                    + mWindow + " -- the specified window type "
                                    + mWindowAttributes.type + " is not valid");
                        case WindowManagerGlobal.ADD_INVALID_USER:
                            throw new WindowManager.BadTokenException("Unable to add Window "
                                    + mWindow + " -- requested userId is not valid");
                    }
                    throw new RuntimeException(
                            "Unable to add window -- unknown error code " + res);
                }

                if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
                    mUseBLASTAdapter = true;
                }

                if (view instanceof RootViewSurfaceTaker) {
                    mInputQueueCallback =
                        ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
                }
                if (inputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                            Looper.myLooper());

                    if (ENABLE_INPUT_LATENCY_TRACKING && mAttachInfo.mThreadedRenderer != null) {
                        InputMetricsListener listener = new InputMetricsListener();
                        mHardwareRendererObserver = new HardwareRendererObserver(
                                listener, listener.data, mHandler, true /*waitForPresentTime*/);
                        mAttachInfo.mThreadedRenderer.addObserver(mHardwareRendererObserver);
                    }
                }

                view.assignParent(this);
                mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
                mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;

                if (mAccessibilityManager.isEnabled()) {
                    mAccessibilityInteractionConnectionManager.ensureConnection();
                }

                if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                    view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
                }

                // Set up the input pipeline.
                CharSequence counterSuffix = attrs.getTitle();
                mSyntheticInputStage = new SyntheticInputStage();
                InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
                InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                        "aq:native-post-ime:" + counterSuffix);
                InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
                InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                        "aq:ime:" + counterSuffix);
                InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
                InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                        "aq:native-pre-ime:" + counterSuffix);

                mFirstInputStage = nativePreImeStage;
                mFirstPostImeInputStage = earlyPostImeStage;
                mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
            }
        }
    }

requestLayout内部通过scheduleTraversals绘制View,WindowSession是一个Binder对象,真正的实现类是Session,也就是Window添加过程是一次IPC调用,Seesion内部通过WindowManagerService实现WIndow添加,通过调用addWindow。

Window的删除过程

Window的更新过程

WindowManagerService

交互图

要想实现完整的窗口管理器,我们首先要知道与WMS相关联的元素。这里先初略的介绍一下

1、WindowManagerService

充当导演,考虑画面的效果与视觉美感,根据实际安排演员站位。

系统级服务,由SystemServer启动,在这之前的画面由BootAnimation提供

异常可重启,关机才退出。

2、SurfaceFlinger

充当摄像机,负责客观地捕捉当前画面,然后真实地呈现给观众

3、有UI的程序

窗口一般分为三类:系统窗口,应用窗口,子窗口,不同窗口层级不同。

4、InputManagerService

IMS负责接收一切输入,当IMS收到一个按键或触摸,他就需要分发给WMS,WMS是派发系统按键和触摸信息的最佳人选。

5、ActivityManagerService

当AC1启动了AC2,UI上的变化就是AC1逐渐消失,AC2慢慢占据屏幕

6、Binder通信

AMS、SurfaceFlinger等,与WMS通讯都需要Binder。

WindowsManagerService的功能

1、窗口添加和删除

2、启动窗口?

3、窗口动画

4、窗口大小

5、窗口层级

6、事件派发

WMS的启动

WMS是SystemServer.java中众多系统服务的一员

t.traceBegin("StartWindowManagerService");
// WMS needs sensor service ready
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_WAIT_FOR_SENSOR_SERVICE);
wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
                               new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
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();

WMS基础功能

WMS使用AIDL描述接口

WMS工作方式

在Android4.1之前,是使用WMThread作为线程的。之后是使用wmHandlerThread,使得其他服务可以往线程里投递事件。

调用者使用WM有两种方式,一种是事件投递,一种是直接调用

当wmHandlerThread创建一个WindowManagerService对象后,后者的mH成员变量就可以调用sendMessage方法传递消息给wmHandlerThread所属的消息队列中,完成事件投递。

直接调用略

WMS、AMS、Activity的联系

Activity运行在应用程序进程中,而AMS,WMS在系统进程中,他们之间通信需要Binder支持。

Activity

 

未完待续

posted @ 2022-07-05 14:30  Miraculous_B  阅读(436)  评论(0)    收藏  举报