源码阅读分析 - Window底层原理与系统架构

做了一段的时间的 android 我们就开始听到有人说 AMS、WMS、Window、WindowManager、WindowManagerService等等这些词汇,可能了解但是脑海里未必有架构图, 这次我们就从源码的角度来了解一下。在阅读本文之前希望你可以花点时间了解下面几篇文章:

1. 插件式换肤框架搭建 - setContentView源码阅读
2. Android进程间的通信 - IPC(机制)Binder的原理和源码阅读
2. Android插件化架构 - Activity的启动流程分析
3. 源码解析 - View的绘制流程

如何阅读源码

上面几篇文章和我今天的这篇文章有着千丝万缕的联系,我为什么可以把他们分开来?有很重要的一点,我们是需要用到才会去看源码,打个比方我想做一个皮肤切换的功能,那么我肯定需要去了解布局资源的加载过程,而这个你仅仅从网上看看别人写好的文章或者 copy 几行代码相信你应该很难做到,当然去 github 上面选个 demo 用用也行,但你心里不觉得少了点什么吗?还有我们最好能够点到即止,带着疑问,只需要清楚我们想要了解什么,今天的很多源码或多或少都会涉及到上面几篇文章的知识点但我们不用管。相信我,只要你能够多对着别人的文章自己打开源码看看,随着时间的推移当我们再看系统源码的时候就会得心应手,解决问题再也不是靠蒙和试(如果你在开发过程中有时候靠蒙可以在文章末尾刷个赞),而且如果心中有整个 android 应用层的源码架构图,对于开发和解决问题方面屡试不爽(如果你能看懂 native 层的源码更好)

我们的疑问

我们只是知道 setContentView 方法可以设置显示我们的布局,这篇 插件式换肤框架搭建 - setContentView源码阅读 文章只是带大家了解了布局的层次结构,但你了解 Window 和 WindowManager 都干了些什么吗?又或者当我们触摸一个 EditText 的时候会弹出一个系统的键盘,为什么弹出键盘我们的 Activity 布局会自动做调整?我们弹一个 Toast 但就算我们退出 Activity 或是整个应用 Toast 还是会存在?

PhoneWindow的创建过程

我们以 Activity 的 setContentView 作为入口可以看到这么两行代码:

     /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

getWindow().setContentView(layoutResID) 这行代码只是去解析我们的布局,没干其他任何事情,这行代码我就不再分析源码了,今天的重点不在这里如果想了解请看这篇插件式换肤框架搭建 - setContentView源码阅读,getWindow() 返回的是 mWindow 而 mWindow 是在 attach 方法中实例化的:

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        // 创建一个 PhoneWindow 实例
        mWindow = new PhoneWindow(this, window);
        // 设置一个 ControllerCallback
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        // 这个你看方法名就知道是什么意思了
        mWindow.setOnWindowDismissedCallback(this);
        // 给 Window 设置一个 WindowManager 
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        // Activity 自己也有一个 WindowManager
        mWindowManager = mWindow.getWindowManager();
    }

那么 attach 方法到底是在什么时候调用的呢?在 ActivityThread 中的 performLaunchActivity 方法中调用的,这里涉及到 Activity 的启动流程分析想了解请看这篇Android插件化架构 - Activity的启动流程分析

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

        Activity activity = null;
        try {
            // 利用反射创建 Activity 实例对象
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            if (activity != null) {
                // 我们要找的方法在这里
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window);

                // 设置主题
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }
            }
        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to start activity " + component
                    + ": " + e.toString(), e);
            }
        }
        // 返回 Activity 实例
        return activity;
    }

目前我们只知道了通过 setContentView 方法会调用 mWindow 的 setContentView 方法,这个方法只是去解析我们的布局而已什么时都没做,而 mWindow 的实例实在 activity 的 attach 方法中调用的,而这个方法是由 ActivityThread 调用的然后就没有了,那么布局到底是怎么显示的?

布局的测量和绘制过程

Android插件化架构 - Activity的启动流程分析中我们能够在 ActivityThread 中找到 handleResumeActivity 这个方法:

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        // 先执行 Activity 的 onResume 生命周期方法
        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);
       
        if (r != null) {
            final Activity a = r.activity;
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                // 获取 Activity 也就是 PhoneWindow 的根布局
                View decor = r.window.getDecorView();
                // 设置为显示
                decor.setVisibility(View.INVISIBLE);
                // 获取 Activity 的 WindowManager 这个对象在上面的 Activity 的 attach 方法中赋值的
                ViewManager wm = a.getWindowManager();
                // 获取PhoneWindow的 WindowManager.LayoutParams 
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                // 指定 type ,这是一个比较重要的概念,待会下面会介绍到 指定窗口的类型
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    // 调用 ViewManager 的 addView 方法
                    wm.addView(decor, l);
                }
            }
        } else {
            // If an exception was thrown when trying to resume, then
            // just end this activity.
            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }

最终调用了 ViewManager 的 addView 方法,但是我们发现 ViewManager 其实是个接口,所以我们得去找实现方法,是在上面 Activity 的 attach 方法通过 context.getSystemService(Context.WINDOW_SERVICE) 获取的,找到 context 的实例类 ContextImpl 的 getSystemService 方法其实调用了 SystemServiceRegistry.getSystemService 方法:

    /**
    * Gets a system service from a given context.
    */
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

SYSTEM_SERVICE_FETCHERS 是一个静态的 HashMap 对象,是通过静态代码块赋值的。这里其实我们可以总结一下,通过 Context 获取的系统服务其实早就被注册和添加到 SYSTEM_SERVICE_FETCHERS 集合中了,这是典型的单例设计模式。至此我们总算找到了这个类 WindowManagerImpl 的 addView 方法。

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

WindowManagerImpl 是 ViewManager 的实现类却把活交给了 WindowManagerGlobal 方法,而且我们发现 mGlobal 对象尽然是个单例,为什么要这么设计在文章中我就不做过多的讲解了。我们看下 mGlobal 的 addView 方法:

   public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {

        // 一些参数的校验
        // ......
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            // 如果是子 View 需要调整一些布局的参数
            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;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // 开始观看系统属性的变化。
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }
            // 通过View 去集合中查找位置,第一次添加肯定是 0
            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.
            }

            // 如果是一个子窗口,我们去找到它的父窗口的 View 
            // 窗口的类型,下面会介绍到
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }
            // 构建ViewRootImpl,暂时不管ViewRootImpl是什么东西
            root = new ViewRootImpl(view.getContext(), display);
            // 给 View 设置 LayoutParams
            view.setLayoutParams(wparams);
            // 下面是一些保存工作添加到集合,一一对应起来
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // 最后一行代码,用来触发做事
        // 将View显示到手机上。setView方法比较复杂,走了这么就最重要的方法在这里面。
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

走了这么久最重要的方法其实就是 root.setView(view, wparams, panelParentView); 这行代码,很复杂偏偏这个方法又是最主要的,希望我讲得并不太深入而且通俗易懂。在分析这个之前我们先讲一下上面反复出现的 type 属性。

Window的三种类型

Window(窗口)是有类型的,而且上面我们也看到了不同的 type 会做不同的处理,Window 分为三种类型:系统 Window,应用程序 Window,子 Window

  • 常见的系统Window,比如在手机电量低的时候,会有一个提示电量低的Window,我们输入文字的时候,会弹出输入法Window,还有搜索条Window,来电显示Window,Toast对应的Window,可以总结出来,系统Window是独立与我们的应用程序的,对于应用程序而言,我们理论上是无法创建系统Window,因为没有权限,这个权限只有系统进程有。所对应的层级区间是 2000 以上

  • 应用程序Window,比如 Activity 就是一个应用程序 Window 从上面源码可以看到 type 给的是 TYPE_BASE_APPLICATION 。所对应的层级区间是 1 - 99

  • 子Window,所谓的子Window,是说这个Window必须要有一个父窗体,比如PopWindow,Dialog 等等 。所对应的层级区间是 1000 - 1999

那么每个层级具体有那些,请看 WindowManager.LayoutParams中的 type 值,这里我贴出来但是不做翻译,因为我英语不好:

        /**
         * Start of window types that represent normal application windows.
         */
        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;

        /**
         * End of types of application windows.
         */
        public static final int LAST_APPLICATION_WINDOW = 99;

        /**
         * 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;

        /**
         * Window type: a panel on top of an application window.  These windows
         * appear on top of their attached window.
         */
        public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;

        /**
         * Window type: window for showing media (such as video).  These windows
         * are displayed behind their attached window.
         */
        public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;

        /**
         * Window type: a sub-panel on top of an application window.  These
         * windows are displayed on top their attached window and any
         * {@link #TYPE_APPLICATION_PANEL} panels.
         */
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;

        /** Window type: like {@link #TYPE_APPLICATION_PANEL}, but layout
         * of the window happens as that of a top-level window, <em>not</em>
         * as a child of its container.
         */
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;

        /**
         * Window type: window for showing overlays on top of media windows.
         * These windows are displayed between TYPE_APPLICATION_MEDIA and the
         * application window.  They should be translucent to be useful.  This
         * is a big ugly hack so:
         * @hide
         */
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;

        /**
         * Window type: a above sub-panel on top of an application window and it's
         * sub-panel windows. These windows are displayed on top of their attached window
         * and any {@link #TYPE_APPLICATION_SUB_PANEL} panels.
         * @hide
         */
        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;

        /**
         * Start of system-specific window types.  These are not normally
         * created by applications.
         */
        public static final int FIRST_SYSTEM_WINDOW     = 2000;

        /**
         * Window type: the status bar.  There can be only one status bar
         * window; it is placed at the top of the screen, and all other
         * windows are shifted down so they are below it.
         * In multiuser systems shows on all users' windows.
         */
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;

        /**
         * Window type: the search bar.  There can be only one search bar
         * window; it is placed at the top of the screen.
         * In multiuser systems shows on all users' windows.
         */
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;

        /**
         * Window type: phone.  These are non-application windows providing
         * user interaction with the phone (in particular incoming calls).
         * These windows are normally placed above all applications, but behind
         * the status bar.
         * In multiuser systems shows on all users' windows.
         */
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;

        /**
         * Window type: system window, such as low power alert. These windows
         * are always on top of application windows.
         * In multiuser systems shows only on the owning user's window.
         */
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;

        /**
         * Window type: keyguard window.
         * In multiuser systems shows on all users' windows.
         * @removed
         */
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;

        /**
         * Window type: transient notifications.
         * In multiuser systems shows only on the owning user's window.
         */
        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;

        /**
         * Window type: system overlay windows, which need to be displayed
         * on top of everything else.  These windows must not take input
         * focus, or they will interfere with the keyguard.
         * In multiuser systems shows only on the owning user's window.
         */
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;

        /**
         * Window type: priority phone UI, which needs to be displayed even if
         * the keyguard is active.  These windows must not take input
         * focus, or they will interfere with the keyguard.
         * In multiuser systems shows on all users' windows.
         */
        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;

        /**
         * Window type: panel that slides out from the status bar
         * In multiuser systems shows on all users' windows.
         */
        public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;

        /**
         * Window type: dialogs that the keyguard shows
         * In multiuser systems shows on all users' windows.
         */
        public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;

        /**
         * Window type: internal system error windows, appear on top of
         * everything they can.
         * In multiuser systems shows only on the owning user's window.
         */
        public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;

        /**
         * Window type: internal input methods windows, which appear above
         * the normal UI.  Application windows may be resized or panned to keep
         * the input focus visible while this window is displayed.
         * In multiuser systems shows only on the owning user's window.
         */
        public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;

        /**
         * Window type: internal input methods dialog windows, which appear above
         * the current input method window.
         * In multiuser systems shows only on the owning user's window.
         */
        public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;

        /**
         * Window type: wallpaper window, placed behind any window that wants
         * to sit on top of the wallpaper.
         * In multiuser systems shows only on the owning user's window.
         */
        public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;

        /**
         * Window type: panel that slides out from over the status bar
         * In multiuser systems shows on all users' windows.
         */
        public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;

        /**
         * Window type: secure system overlay windows, which need to be displayed
         * on top of everything else.  These windows must not take input
         * focus, or they will interfere with the keyguard.
         *
         * This is exactly like {@link #TYPE_SYSTEM_OVERLAY} except that only the
         * system itself is allowed to create these overlays.  Applications cannot
         * obtain permission to create secure system overlays.
         *
         * In multiuser systems shows only on the owning user's window.
         * @hide
         */
        public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;

        /**
         * Window type: the drag-and-drop pseudowindow.  There is only one
         * drag layer (at most), and it is placed on top of all other windows.
         * In multiuser systems shows only on the owning user's window.
         * @hide
         */
        public static final int TYPE_DRAG               = FIRST_SYSTEM_WINDOW+16;

        /**
         * Window type: panel that slides out from under the status bar
         * In multiuser systems shows on all users' windows.
         * @hide
         */
        public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;

        /**
         * Window type: (mouse) pointer
         * In multiuser systems shows on all users' windows.
         * @hide
         */
        public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;

        /**
         * Window type: Navigation bar (when distinct from status bar)
         * In multiuser systems shows on all users' windows.
         * @hide
         */
        public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;

        /**
         * Window type: The volume level overlay/dialog shown when the user
         * changes the system volume.
         * In multiuser systems shows on all users' windows.
         * @hide
         */
        public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;

        /**
         * Window type: The boot progress dialog, goes on top of everything
         * in the world.
         * In multiuser systems shows on all users' windows.
         * @hide
         */
        public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;

        /**
         * Window type to consume input events when the systemUI bars are hidden.
         * In multiuser systems shows on all users' windows.
         * @hide
         */
        public static final int TYPE_INPUT_CONSUMER = FIRST_SYSTEM_WINDOW+22;

        /**
         * Window type: Dreams (screen saver) window, just above keyguard.
         * In multiuser systems shows only on the owning user's window.
         * @hide
         */
        public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;

        /**
         * Window type: Navigation bar panel (when navigation bar is distinct from status bar)
         * In multiuser systems shows on all users' windows.
         * @hide
         */
        public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;

        /**
         * Window type: Display overlay window.  Used to simulate secondary display devices.
         * In multiuser systems shows on all users' windows.
         * @hide
         */
        public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;

        /**
         * Window type: Magnification overlay window. Used to highlight the magnified
         * portion of a display when accessibility magnification is enabled.
         * In multiuser systems shows on all users' windows.
         * @hide
         */
        public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;

        /**
         * Window type: keyguard scrim window. Shows if keyguard needs to be restarted.
         * In multiuser systems shows on all users' windows.
         * @hide
         */
        public static final int TYPE_KEYGUARD_SCRIM           = FIRST_SYSTEM_WINDOW+29;

        /**
         * Window type: Window for Presentation on top of private
         * virtual display.
         */
        public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;

        /**
         * Window type: Windows in the voice interaction layer.
         * @hide
         */
        public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;

        /**
         * Window type: Windows that are overlaid <em>only</em> by a connected {@link
         * android.accessibilityservice.AccessibilityService} for interception of
         * user interactions without changing the windows an accessibility service
         * can introspect. In particular, an accessibility service can introspect
         * only windows that a sighted user can interact with which is they can touch
         * these windows or can type into these windows. For example, if there
         * is a full screen accessibility overlay that is touchable, the windows
         * below it will be introspectable by an accessibility service even though
         * they are covered by a touchable window.
         */
        public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;

        /**
         * Window type: Starting window for voice interaction layer.
         * @hide
         */
        public static final int TYPE_VOICE_INTERACTION_STARTING = FIRST_SYSTEM_WINDOW+33;

        /**
         * Window for displaying a handle used for resizing docked stacks. This window is owned
         * by the system process.
         * @hide
         */
        public static final int TYPE_DOCK_DIVIDER = FIRST_SYSTEM_WINDOW+34;

        /**
         * Window type: like {@link #TYPE_APPLICATION_ATTACHED_DIALOG}, but used
         * by Quick Settings Tiles.
         * @hide
         */
        public static final int TYPE_QS_DIALOG = FIRST_SYSTEM_WINDOW+35;

        /**
         * Window type: shares similar characteristics with {@link #TYPE_DREAM}. The layer is
         * reserved for screenshot region selection. These windows must not take input focus.
         * @hide
         */
        public static final int TYPE_SCREENSHOT = FIRST_SYSTEM_WINDOW + 36;

        /**
         * End of types of system windows.
         */
        public static final int LAST_SYSTEM_WINDOW      = 2999;

窗口与WindowManagerService服务的连接过程

回到 ViewRootImpl 中的 setView 方法:

    /**
    * We have one child
    */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                // ......
                // 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();
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
                // ......
        }
    }

requestLayout() 这个方法很有含金量,如果想了解看下这篇 源码解析 - View的绘制流程 ,我们主要还是分析mWindowSession.addToDisplay 方法,mWindowSession 又是什么呢?看到 Session 其实还是知道什么意思,是不是真的和网络的 Session 差不多呢?mWindowSession 是通过 WindowManagerGlobal.getWindowSession(); 获取的,我们去看下:

    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

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

这又是一个典型的 IPC 的通信机制,和 AMS 非常的类似想了解看下这篇 Android进程间的通信 - IPC(机制)Binder的原理和源码阅读 ,现在我们去服务端 WindowManagerService 的 openSession 方法:

    @Override
    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
            IInputContext inputContext) {
        if (client == null) throw new IllegalArgumentException("null client");
        if (inputContext == null) throw new IllegalArgumentException("null inputContext");
        Session session = new Session(this, callback, client, inputContext);
        return session;
    }

Session我们总算是获取到了,服务端 WindowManagerService 创建了一个 Session 对象返回回来了,接下来我们发现 Session 的 addToDisplay 方法来到了 WindowManager 的 addWindow 方法,相信应该差不多快完了吧,这不是人干的事:

    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
        ......  
  
        WindowState attachedWindow = null;  
        final int type = attrs.type;
  
        synchronized(mWindowMap) {  
            ......  
  
            if (mWindowMap.containsKey(client.asBinder())) {  
                ......  
                return WindowManagerImpl.ADD_DUPLICATE_ADD;  
            } 

这段代码首先在WindowManagerService类的成员变量mWindowMap所描述的一个HashMap中检查是否存在一个与参数client所对应的WindowState对象,如果已经存在,那么就说明WindowManagerService服务已经为它创建过一个WindowState对象了,因此,这里就不会继续往前执行,而是直接返回一个错误码WindowManagerImpl.ADD_DUPLICATE_ADD。我们继续往前看代码:

if (attrs.type >= FIRST_SUB_WINDOW && attrs.type <= LAST_SUB_WINDOW) {  
    attachedWindow = windowForClientLocked(null, attrs.token, false);  
    if (attachedWindow == null) {  
        ......  
        return WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN;  
    }  
    if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW  
            && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {  
        ......  
        return WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN;  
    }  
}  

参数attrs指向的是一个WindowManager.LayoutParams对象,用来描述正在启动的Activity组件的UI布局,当它的成员变量type的值大于等于FIRST_SUB_WINDOW并且小于等于LAST_SUB_WINDOW的时候,就说明现在要增加的是一个子窗口。在这种情况下,就必须要指定一个父窗口,而这个父窗口是通过数attrs指向的是一个WindowManager.LayoutParams对象的成员变量token来指定的,因此,这段代码就会调用WindowManagerService类的另外一个成员函数windowForClientLocked来获得用来描述这个父窗口的一个WindowState对象,并且保存在变量attachedWindow。

如果得到变量attachedWindow的值等于null,那么就说明父窗口不存在,这是不允许的,因此,函数就不会继续向前执行,而是直接返回一个错误码WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN。另一方面,如果变量attachedWindow的值不等于null,但是它的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象的成员变量type的值也是大于等于FIRST_SUB_WINDOW并且小于等于LAST_SUB_WINDOW,那么也说明找到的父窗口也是一个子窗口,这种情况也是不允许的,因此,函数就不会继续向前执行,而是直接返回一个错误码WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN。

继续看代码:

// 一些检查合法的代码

win = new WindowState(session, client, token,  
        attachedWindow, attrs, viewVisibility);  
......  
mPolicy.adjustWindowParamsLw(win.mAttrs);  
  
res = mPolicy.prepareAddWindowLw(win, attrs);  
if (res != WindowManagerImpl.ADD_OKAY) {  
    return res;  
} 

通过上面的合法性检查之后,这里就可以为正在增加的窗口创建一个WindowState对象了。 WindowManagerService类的成员变量mPolicy指向的是一个实现了WindowManagerPolicy接口的窗口管理策略器。在Phone平台中,这个窗口管理策略器是由com.android.internal.policy.impl.PhoneWindowManager来实现的,它负责对系统中的窗口实现制定一些规则。这里主要是调用窗口管理策略器的成员函数adjustWindowParamsLw来调整当前正在增加的窗口的布局参数,以及调用成员函数prepareAddWindowLw来检查当前应用程序进程请求增加的窗口是否是合法的。如果不是合法的,即变量res的值不等于WindowManagerImpl.ADD_OKAY,那么函数就不会继续向前执行,而直接返回错误码res。

我们就先看到这里了,你甚至还可以在去了解 WindowState 的成员变量都有一些啥作用,又或者是那些合法的检查代码都有什么用等等,最后我再画一张草图:

Activity组件与WindowManagerService服务之间的连接模型.png

我花了大概半个多月的时间才勉强看懂一些源码,当然包括这篇文章中的所有源码链接,如果看不太懂需要多花些时间,如果文字让你受不了可以看看我的直播视频,同时这也是自定义View部分的最后一篇文章了。

所有分享大纲:Android进阶之旅 - 自定义View篇

视频讲解地址:https://pan.baidu.com/s/1pLBTwDX

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容