理解WindowManager

QuibblerQuibbler 2020-07-20 约 43 分钟 2328 次阅读

理解WindowManager

WindowManager类顾名思义就是用来管理Window类。Window在另外一篇有探究理解Window。

1、WindowManager

WindowManager也是接口,继承自ViewManager接口,它的实现类是WindowManagerImpl。都在android.view包中。

1.1、ViewManager

ViewManager接口中只定义了三个接口方法,用来添加、删除、更新View。

/** Interface to let you add and remove child views to an Activity. To get an instance
      */
    public interface ViewManager
    {
        public void addView(View view, ViewGroup.LayoutParams params);
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
        public void removeView(View view);
    }

WindowManager为什么又要继承ViewManager多此一举呢?ViewManager是更为通用的抽象,而WindowManager则在此基础之上添加定义了一些方法,还有很多常量。

1.2、WindowManagerImpl

WindowManager接口的实现类是WindowManagerImpl。从理解Window一文中得知获取到的WindowManager其实就是WindowManagerImpl类型实例(以参数wm不为空为例):

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

2、WindowManager.LayoutParams

WindowManager用来管理Window,该类定义了一个静态内部类WindowManager.LayoutParams,其中定义了很多和Window相关的Flag,用来控制Window的显示。

2.1、窗口类型Type

理解Window文中提到有三种类型的Window,它们对应的Type类型都有各自的范围

①Application windows(1~99)

/**
         * 代表普通应用程序窗口的窗口类型的开始。
         */
        public static final int FIRST_APPLICATION_WINDOW = 1;
        
        /**
         * Window type: an application window that serves as the "base" window
         * of the overall application; all other application windows will
         * appear on top of it.
         * In multiuser systems shows only on the owning user's window.
         */
        public static final int TYPE_BASE_APPLICATION   = 1;
        
        /**
         * Window type: a normal application window.  The {@link #token} must be
         * an Activity token identifying who the window belongs to.
         * In multiuser systems shows only on the owning user's window.
         */
        public static final int TYPE_APPLICATION        = 2;
        
        /**
         * Window type: special application window that is displayed while the
         * application is starting.  Not for use by applications themselves;
         * this is used by the system to display something until the
         * application can show its own windows.
         * In multiuser systems shows on all users' windows.
         */
        public static final int TYPE_APPLICATION_STARTING = 3;
        
        /**
         * Window type: a variation on TYPE_APPLICATION that ensures the window
         * manager will wait for this window to be drawn before the app is shown.
         * In multiuser systems shows only on the owning user's window.
         */
        public static final int TYPE_DRAWN_APPLICATION = 4;
        
        /**
         * 应用程序窗口类型的结尾。
         */
        public static final int LAST_APPLICATION_WINDOW = 99;

②Sub-windows(1000~1999)

/**
         * Start of types of sub-windows.  The {@link #token} of these windows
         * must be set to the window they are attached to.  These types of
         * windows are kept next to their attached window in Z-order, and their
         * coordinate space is relative to their attached window.
         */
        public static final int FIRST_SUB_WINDOW = 1000;
        
        public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
        
        public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
        
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
        
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
        
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;
        
        public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
        
        /**
         * End of types of sub-windows.
         */
        public static final int LAST_SUB_WINDOW = 1999;

③System windows(2000~2999)

/**
         * Start of system-specific window types.  These are not normally
         * created by applications.
         */
        public static final int FIRST_SYSTEM_WINDOW     = 2000;
        
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
        ...
        public static final int TYPE_PRESENTATION = FIRST_SYSTEM_WINDOW + 37;
        public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
        /**
         * End of types of system windows.
         */
        public static final int LAST_SYSTEM_WINDOW      = 2999;

此外Window类型还有一个隐藏的INVALID_WINDOW_TYPE,系统内部使用。

/**
         * Used internally when there is no suitable type available.
         */
        public static final int INVALID_WINDOW_TYPE = -1;

2.2、Window相关Flag

WindowManager.LayoutParams类中目前还定义了25种控制Window显示的Flag:

/**
   * 各种行为Flags,默认为无。
   *
   * @see #FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
   * @see #FLAG_DIM_BEHIND
   * @see #FLAG_NOT_FOCUSABLE
   * @see #FLAG_NOT_TOUCHABLE
   * @see #FLAG_NOT_TOUCH_MODAL
   * @see #FLAG_TOUCHABLE_WHEN_WAKING
   * @see #FLAG_KEEP_SCREEN_ON
   * @see #FLAG_LAYOUT_IN_SCREEN
   * @see #FLAG_LAYOUT_NO_LIMITS
   * @see #FLAG_FULLSCREEN
   * @see #FLAG_FORCE_NOT_FULLSCREEN
   * @see #FLAG_SECURE
   * @see #FLAG_SCALED
   * @see #FLAG_IGNORE_CHEEK_PRESSES
   * @see #FLAG_LAYOUT_INSET_DECOR
   * @see #FLAG_ALT_FOCUSABLE_IM
   * @see #FLAG_WATCH_OUTSIDE_TOUCH
   * @see #FLAG_SHOW_WHEN_LOCKED
   * @see #FLAG_SHOW_WALLPAPER
   * @see #FLAG_TURN_SCREEN_ON
   * @see #FLAG_DISMISS_KEYGUARD
   * @see #FLAG_SPLIT_TOUCH
   * @see #FLAG_HARDWARE_ACCELERATED
   * @see #FLAG_LOCAL_FOCUS_MODE
   * @see #FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
   */

对源码比较了解的会发现,Android系统相关的类中经常会出现一大堆定义常量的参数,各种Flag或者Type。这些完全不用记住,用的时候会查看API,即拿即用(所以一定要学会看源码,看API;比起背书,更重要的是如何“渔”)。举几个其中比较熟悉的Flag:

①FLAG_KEEP_SCREEN_ON

/** 只要该窗口对用户可见,请保持设备的屏幕处于打开状态并保持明亮*/
     public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;

②FLAG_LAYOUT_NO_LIMITS

/** 窗口标志:允许窗口扩展到屏幕之外 */
     public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;

③FLAG_FULLSCREEN:设置Activity全屏,但是对于异形屏、水滴屏等需要额外设置,参考《Activity页面全屏探究》。

/**
      * Window flag: hide all screen decorations (such as the status bar) while
      * this window is displayed.  This allows the window to use the entire
      * display space for itself -- the status bar will be hidden when
      * an app window with this flag set is on the top layer. A fullscreen window
      * will ignore a value of {@link #SOFT_INPUT_ADJUST_RESIZE} for the window's
      * {@link #softInputMode} field; the window will stay fullscreen
      * and will not resize.
      *
      * <p>This flag can be controlled in your theme through the
      * {@link android.R.attr#windowFullscreen} attribute; this attribute
      * is automatically set for you in the standard fullscreen themes
      * such as {@link android.R.style#Theme_NoTitleBar_Fullscreen},
      * {@link android.R.style#Theme_Black_NoTitleBar_Fullscreen},
      * {@link android.R.style#Theme_Light_NoTitleBar_Fullscreen},
      * {@link android.R.style#Theme_Holo_NoActionBar_Fullscreen},
      * {@link android.R.style#Theme_Holo_Light_NoActionBar_Fullscreen},
      * {@link android.R.style#Theme_DeviceDefault_NoActionBar_Fullscreen}, and
      * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_Fullscreen}.</p>
      */
     public static final int FLAG_FULLSCREEN      = 0x00000400;

3、布局是如何被添加到Window中的?

在DecorView添加到Window中的流程一文中有详细的源码分析。

4、Window操作流程

前面讲到,WindowManager继承自ViewManager接口,ViewManager中定义了三种接口方法。这几个方法基本流程相似,来看看其中一个updateViewLayout()方法的实现流程。

4.1、updateViewLayout()

直接从WindowManager的实现类WindowManagerImpl看:

@Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

通过WindowManagerGlobal类型的变量mGlobal的同名方法

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        view.setLayoutParams(wparams);
        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }

调用ViewRootImpl的setLayoutParams()方法

void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
        synchronized (this) {
            final int oldInsetLeft = mWindowAttributes.surfaceInsets.left;
            final int oldInsetTop = mWindowAttributes.surfaceInsets.top;
            final int oldInsetRight = mWindowAttributes.surfaceInsets.right;
            final int oldInsetBottom = mWindowAttributes.surfaceInsets.bottom;
            final int oldSoftInputMode = mWindowAttributes.softInputMode;
            final boolean oldHasManualSurfaceInsets = mWindowAttributes.hasManualSurfaceInsets;
            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, "setLayoutParams: FLAG_KEEP_SCREEN_ON from true to false!");
            }
            // Keep track of the actual window flags supplied by the client.
            mClientWindowLayoutFlags = attrs.flags;
            // Preserve compatible window flag if exists.
            final int compatibleWindowFlag = mWindowAttributes.privateFlags
                    & WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
            // Transfer over system UI visibility values as they carry current state.
            attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
            attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
            mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
            if ((mWindowAttributesChangesFlag
                    & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
                // Recompute system ui visibility.
                mAttachInfo.mRecomputeGlobalAttributes = true;
            }
            if ((mWindowAttributesChangesFlag
                    & WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) {
                // Request to update light center.
                mAttachInfo.mNeedsUpdateLightCenter = true;
            }
            if (mWindowAttributes.packageName == null) {
                mWindowAttributes.packageName = mBasePackageName;
            }
            mWindowAttributes.privateFlags |= compatibleWindowFlag;
            if (mWindowAttributes.preservePreviousSurfaceInsets) {
                // Restore old surface insets.
                mWindowAttributes.surfaceInsets.set(
                        oldInsetLeft, oldInsetTop, oldInsetRight, oldInsetBottom);
                mWindowAttributes.hasManualSurfaceInsets = oldHasManualSurfaceInsets;
            } else if (mWindowAttributes.surfaceInsets.left != oldInsetLeft
                    || mWindowAttributes.surfaceInsets.top != oldInsetTop
                    || mWindowAttributes.surfaceInsets.right != oldInsetRight
                    || mWindowAttributes.surfaceInsets.bottom != oldInsetBottom) {
                mNeedsRendererSetup = true;
            }
            applyKeepScreenOnFlag(mWindowAttributes);
            if (newView) {
                mSoftInputMode = attrs.softInputMode;
                requestLayout();
            }
            // Don't lose the mode we last auto-computed.
            if ((attrs.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
                    == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
                mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
                        & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
                        | (oldSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);
            }
            mWindowAttributesChanged = true;
            scheduleTraversals();
        }
    }

方法虽然很长,但是只需要看最后的scheduleTraversals()方法进入下一步,该方法在ViewRootImpl中被调用的次数非常多。

@UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

其中调用了mChoreographer的postCallback()方法,mChoreographer就是View中大名鼎鼎的Choreographer类,俗称“舞蹈指导”。

/**
     * Posts a callback to run on the next frame.
     * <p>
     * The callback runs once then is automatically removed.
     * </p>
     *
     * @param callbackType The callback type.
     * @param action The callback action to run during the next frame.
     * @param token The callback token, or null if none.
     *
     * @see #removeCallbacks
     * @hide
     */
    @TestApi
    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }

接着调用postCallbackDelayed()方法

/**
     * Posts a callback to run on the next frame after the specified delay.
     * <p>
     * The callback runs once then is automatically removed.
     * </p>
     *
     * @param callbackType The callback type.
     * @param action The callback action to run during the next frame after the specified delay.
     * @param token The callback token, or null if none.
     * @param delayMillis The delay time in milliseconds.
     *
     * @see #removeCallback
     * @hide
     */
    @TestApi
    public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        if (action == null) {
            throw new IllegalArgumentException("action must not be null");
        }
        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
            throw new IllegalArgumentException("callbackType is invalid");
        }
        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }

通过Handler回到主线程更新UI

private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

再回过头来看看当时执行的Runnable对象定义如下

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

TraversalRunnable类也很简单,run()执行doTraversal()方法

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

在ViewRootImpl中doTraversal()定义如下

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            performTraversals();
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

调用performTraversals()方法,源码太长久不贴了。内部会调用relayoutWindow通过IWindowSession更新视图。该方法还会调用performMeasuer()、performLayout()、performDraw()方法,分别执行View的measure、layout和draw,最终完成View的绘制。

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w