基于Android9.0的WindowManager源码解析

本文主题

关于WindowManager这个复杂的系统,本文会基于Android9.0源码,把其中的关键代码截取出来进行分析,并通过问答的形式来进行叙述,最终回答以下几个问题:

  1. WindowManager是什么?它的作用是什么?
  2. Window和WindowManager如何关联?
  3. Window、WindowManager和WindowManagerService三者有什么关系?
  4. Window有哪些类型?
  5. Window在Activity启动过程中的作用?
  6. Window如何处理View的添加、移除和更新?

WindowManager是什么

摘自 Android开发者官网:

The interface that apps use to talk to the window manager.
Each window manager instance is bound to a particular Display. To obtain a WindowManager for a different display, use Context#createDisplayContext to obtain a Context for that display, then use Context.getSystemService(Context.WINDOW_SERVICE) to get the WindowManager.
The simplest way to show a window on another display is to create a Presentation. The presentation will automatically obtain a WindowManager and Context for that display.

用通俗一点的话来讲就是:

WindowManager是一个实现了ViewManager的接口,至于它是干嘛用的,官方文档并没有详细说明,从名字上我们可以知道它和显示有关,用于管理Window,具体作用还是直接看源码吧。

如何获取WindowManager

正如官方文档所说,我们可以直接通过 Context.getSystemService(Context.WINDOW_SERVICE) 来获取WindowManager

继承ViewManager接口

WindowManager继承ViewManager接口,而ViewManager接口很简单,只有三个方法

public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    // 添加View
    public void addView(View view, ViewGroup.LayoutParams params);
    // 更新View
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    // 移除View
    public void removeView(View view);
}

定义了许多Flags

在WindowManager.LayoutParams里面有许多Flags,这些Flag的作用就是在创建Window的时候用于区分这个Window到底是什么类型的,关于Window类型的问题我们会在后面再详细说明。

下面列举了一小部分Flags

        public static final int TYPE_BASE_APPLICATION   = 1;

        public static final int TYPE_APPLICATION        = 2;

        public static final int TYPE_APPLICATION_STARTING = 3;

        public static final int TYPE_DRAWN_APPLICATION = 4;

        public static final int LAST_APPLICATION_WINDOW = 99;

        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;
        // 后面省略N个Flag

通过int类型的Type,我们可以区分不同的Window类型。

大家也可以直接跳到第四个问题查看:Window有哪些类型?

回答:WindowManager是什么

WindowManager负责的事情其实并不多,主要完成一些配置工作(定义Window类型的Flags,以及LayoutParams静态内部类),具体的跨进程通信还是要看WindowManagerService,而对View的操作则是通过WindowManagerGlobal来进行。

Window和WindowManager如何关联

说了这么多,分析了一通WindowManager这个类的源码,我们只知道它是一个接口,但是还不知道它具体是怎么使用的。别急,接下来就轮到Window登场了。

关联的关键:Window.java

我们在Window.java找到以下代码:

 /**
     * Set the window manager for use by this Window to, for example,
     * display panels.  This is <em>not</em> used for displaying the
     * Window itself -- that must be done by the client.
     *
     * @param wm The window manager for adding new windows.
     */
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
        setWindowManager(wm, appToken, appName, false);
    }

    /**
     * Set the window manager for use by this Window to, for example,
     * display panels.  This is <em>not</em> used for displaying the
     * Window itself -- that must be done by the client.
     *
     * @param wm The window manager for adding new windows.
     */
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        // 通过WindowManagerImpl创建WindowManager对象,并赋值给mWindowManager
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

这两个setWindowManager()方法分别在Activity和Dialog中被调用了。说明在Activity创建,Toast显示的过程中都需要用到WindowManager。

注意:mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);

这一行代码就是Window和WindowManager产生化学反应的关键!

首先需要看看WindowManagerImpl是什么

WM的实现类:WindowManagerImpl.java

WindowManagerImpl.java

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

其实Window.java里面调用的这个方法,就是创建一个Impl对象。

在WindowManagerImpl中,我们会看到有个成员变量

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

这个WindowManagerGlobal 正是用于对View进行操作的实际对象。

无论是addView(), updateView(), removeView(),都是通过这个对象进行操作的。

 @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        // 调用WindowManagerGlobal 的addView()方法
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

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

    @Override
    public void removeViewImmediate(View view) {
        // 同理
        mGlobal.removeView(view, true);
    }

关于WindowManagerGlobal 怎么处理View的逻辑,我们在后面会继续解析。这里还是先回到Window和WindowManager这两者是如何关联这个问题上来。

如果大家有细心留意的话就能看到,WindowManagerImpl的构造方法里面有一个parentWindow 的参数,这个参数的类型是Window,也就是说,当我们在Activity或者Dialog调用setWindowManager()的时候,就会把当前Window作为参数传递过来,在创建WindowManager的同时把这两者给关联起来。

回答:Window和WindowManager如何关联

在创建Activity或者Dialog的时候会调用Window.setWindowManager()方法,然后把当前Window作为参数传递到WindowManagerImpl的createLocalWindowManager()方法中,在创建WindowManager对象的时候把Window关联起来。

Window、WindowManager和WindowManagerService三者有什么关系

通过前面的分析,我们知道Window和WindowManager是如何关联起来的,然鹅到目前为止,我们还是不知道在Framework层的Window是如何同Native层的WindowManagerService进行通信的,让我们继续看源码。

上面说到,具体对View的操作实际是WindowManagerGlobal这个类来做的,那我们看看addView()方法里做了什么:

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        
		// 省略代码
        synchronized (mLock) {
           // 继续省略代码

            // 构建ViewRootImpl对象
            root = new ViewRootImpl(view.getContext(), display);

            // 设置参数
            view.setLayoutParams(wparams);

            // 把view,root, param添加到对应的list中
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

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

这里主要做了几件事:

  • 构建ViewRootImpl对象
  • 设置参数
  • 添加到list中
  • 把View显示出来

我们一个个来看,首先是构建ViewRootImpl对象

  public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
        mDisplay = display;
        mBasePackageName = context.getBasePackageName();
        mThread = Thread.currentThread();
        mLocation = new WindowLeaked(null);
        mLocation.fillInStackTrace();
        
      // 省略代码
      
    }

这里初始化了许多的成员变量,其中有一个是mWindowSession,我们进去看看

    @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();
                    // 获取WindowManagerService对象
                    IWindowManager windowManager = getWindowManagerService();
                    // 建立一个Session
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

看一下WindowManager是如何获取WMS对象的:

    @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());
                    }
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }

通过ServiceManager.getService(“window”)获取到WMS,然后再转为IWindowManager,那么在getService()方法中做了什么呢?

public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return Binder.allowBlocking(rawGetService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

可以看到getService()方法返回的是IBinder对象。到这里我们就真正拿到了WMS了,也知道了Framework层与Native底层其实都是通过Binder机制进行通信。

这里比较绕,我们按照流程来捋一遍:首先在ViewRootImpl构建过程中,我们需要初始化IWindowSession对象,因此在getWindowSession() -> getWindowManagerService() -> getService()中从缓存列表(HashMap)获取WMS,然后通过asInterface函数转为WindowManager对象,最后通过openSession()与WMS建立会话,也就是在Framework层和Native层之间建立了连接。

回答:Window,WM和WMS有什么关系

经过前面三个问题的分析,我们应该有比较清晰的脉络了,对于这三者,Window和WM有关联(通过Activity和Dialog,忘记了可以回头看),WM和WMS有关联(通过WM的实现类WindowManagerImp的小弟WindowManagerGlobal)

因此,在Activity或者Dialog创建的时候,其实这三者就已经创建并且相互关联起来了。

我们还没说View到底是怎么显示出来的,这个问题留到最后一步再来解决。

Window有哪些类型

我们现在来填第一个问题时候埋下的坑,关于Window有哪些类型,其实就三种

  • System Window
  • Sub Window
  • Application Window

System Window(系统窗口)

常见的例如Toast,输入法,系统弹出框等等,这部分窗口我们是没有权限创建的。

还记得上面我们列举了一小部分的Flags吗?System Window的type范围是2000以上,下面列举一部分

WindowManager.java:

 
		//系统窗口
        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;

		// 来电窗口
        @Deprecated
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;

        // 系统提示
        @Deprecated
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;

Sub Window(子窗口)

所谓子窗口,则是指这个窗口还要有一个父窗口,例如PopupWindow

Sub Window的范围是是1000~1999,由于Sub Window比较少,我就全部列出来了

WindowManager.java

		// 子窗口
        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;
child of its container.
        // 对话框
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;

		// 媒体信息
        @UnsupportedAppUsage
        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;
		// 结束子窗口	
        public static final int LAST_SUB_WINDOW = 1999;

Application Window(应用程序窗口)

常见的例如Activity,由于比较少,我也全部列举出来了:

WindowManager.java

		// 开始应用程序窗口 
		public static final int FIRST_APPLICATION_WINDOW = 1;
		// 程序窗口的base窗口,其他窗口都在它之上
        public static final int TYPE_BASE_APPLICATION   = 1;
     	// 普通应用程序窗口
        public static final int TYPE_APPLICATION        = 2;
		// 程序启动窗口
        public static final int TYPE_APPLICATION_STARTING = 3;
		// 普通应用程序窗口的一种变体,显示应用程序之前等待时的窗口
        public static final int TYPE_DRAWN_APPLICATION = 4;
		// 结束程序窗口
        public static final int LAST_APPLICATION_WINDOW = 99;

回答:Window有哪些类型

三种,分别是系统窗口,子窗口,应用程序窗口,根据Type大小,系统窗口>子窗口>应用窗口,因此系统窗口在最上层,优先级最高。

WindowManager在Activity启动过程中的作用

Activity的attach()方法

关于Activity启动过程我们先忽略,只了解与Window/WindowManager相关的源码

在Activity.attach() 方法中,我们找到了WindowManager的身影

 @UnsupportedAppUsage
    final void attach(Context context, ActivityThread aThread,
        // 忽略代码...
       
        // 创建Window
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        
        // 创建WindowManager
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);

        setAutofillOptions(application.getAutofillOptions());
        setContentCaptureOptions(application.getContentCaptureOptions());
    }

在attach()方法中,Activity会创建一个Window,然后setWindowManager() 我们在上面已经分析过了,其实就是创建了一个WindowManagerImpl类,并把Window和WM关联了起来。

接下来在Activity的onCreate()方法中,我们会调用setContentView()方法:

AppCompatActivity.java

   @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

继续进入AppCompatDelegate.java,这是一个抽象类,没有实现具体方法,我们直接看他的子类AppCompatDelegateImpl.java

@Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

	// 主要关注这个方法
    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

    @Override
    public void setContentView(View v, ViewGroup.LayoutParams lp) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v, lp);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

这里提供了三个setContentView的重载方法,我们只看第二个,也就是传入layoutId的这个方法:

ensureSubDecor();

private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();  // 创建Decor

            // If a title was set before we installed the decor, propagate it now
            CharSequence title = getTitle();
            if (!TextUtils.isEmpty(title)) {
                if (mDecorContentParent != null) {
                    mDecorContentParent.setWindowTitle(title);
                } else if (peekSupportActionBar() != null) {
                    peekSupportActionBar().setWindowTitle(title);
                } else if (mTitleView != null) {
                    mTitleView.setText(title);
                }
            }

            applyFixedSizeWindow();

            onSubDecorInstalled(mSubDecor);

            mSubDecorInstalled = true;

            // Invalidate if the panel menu hasn't been created before this.
            // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
            // being called in the middle of onCreate or similar.
            // A pending invalidation will typically be resolved before the posted message
            // would run normally in order to satisfy instance state restoration.
            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!mIsDestroyed && (st == null || st.menu == null)) {
                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
            }
        }
    }

createSubDecor()

    private ViewGroup createSubDecor() {
     
        // 忽略代码...

        // 获取Window
        ensureWindow();
        mWindow.getDecorView();

        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;


       // 忽略代码...

 

        // 调用PhoneWindow的setContentView方法
        mWindow.setContentView(subDecor);

        contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
            @Override
            public void onAttachedFromWindow() {}

            @Override
            public void onDetachedFromWindow() {
                dismissPopups();
            }
        });

        return subDecor;
    

这个方法主要做了两个事情

  • 获取Window,保证mWindow对象不为空
  • 获取到Window对象后再调用setContentView()方法

我们先看第一步:

private void ensureWindow() {
        // We lazily fetch the Window for Activities, to allow DayNight to apply in
        // attachBaseContext
        if (mWindow == null && mHost instanceof Activity) {
            // 调用Activity的getWindow()方法
            attachToWindow(((Activity) mHost).getWindow());
        }
        if (mWindow == null) {
            throw new IllegalStateException("We have not been given a Window");
        }
    }

	// 把上面获取到的window对象赋值给mWindow
    private void attachToWindow(@NonNull Window window) {
        if (mWindow != null) {
            throw new IllegalStateException(
                    "AppCompat has already installed itself into the Window");
        }

        final Window.Callback callback = window.getCallback();
        if (callback instanceof AppCompatWindowCallback) {
            throw new IllegalStateException(
                    "AppCompat has already installed itself into the Window");
        }
        mAppCompatWindowCallback = new AppCompatWindowCallback(callback);
        // Now install the new callback
        window.setCallback(mAppCompatWindowCallback);

        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
                mContext, null, sWindowBackgroundStyleable);
        final Drawable winBg = a.getDrawableIfKnown(0);
        if (winBg != null) {
            // Now set the background drawable
            window.setBackgroundDrawable(winBg);
        }
        a.recycle();

        mWindow = window;
    }

还记得在这个问题的开头,Activity的attach()方法中做了什么吗?

没错,我们初始化了一个PhoneWindow对象,并赋值为了mWindow!因此在这里拿到的Window对象自然也是PhoneWindow了!

因此第二步调用的setContentView()方法,也就是:PhoneWindow.setContentView()了。我们继续往下看

PhoneWindow的setContentView()方法

PhoneWindow.java

@Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
        //1.创建DecorView,以及DecorView中的mContentParent 布局
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {  
          //2,将layoutResID布局加载到mContentParent和上
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
        //3通知视图改变回调
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

我们看一下installDecor()做了什么

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            // 创建Decor
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            // 生成一个Layout
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());
                if (mDecorContentParent.getTitle() == null) {
                    mDecorContentParent.setWindowTitle(mTitle);
                }

                final int localFeatures = getLocalFeatures();
                for (int i = 0; i < FEATURE_MAX; i++) {
                    if ((localFeatures & (1 << i)) != 0) {
                        mDecorContentParent.initFeature(i);
                    }
                }
 }

具体的源码就不再仔细分析了,在这里系统主要做了:创建Decor,然后根据Window的Flag, Theme等配置创建一个布局,并且添加到Decor中。

至此,Activity已经成功的创建了WindowManager, 创建了Decor,创建了相应的布局,还差最后一步:把这个布局添加到Window中并显示出来。

让我们回到最初的起点,也就是ActivityThread,在这里我们能看到有个方法:handleResumeActivity()并找到其中一段

// 设置activity为可见状态
if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();
            }

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        // 注意这个调用,正是因为它用户才能看到activity的界面内容
        mDecor.setVisibility(View.VISIBLE); 
    }

在这里系统又做了三件事情:

  1. 获取WindowManager
  2. 把刚刚创建的Decor对象添加到WM中
  3. 设置Decor为可见状态

最终Activity才能正常显示在用户的面前。

回答:WindowManager在Activity启动过程中的作用

首先ActivityThread.attach()方法会创建Window和WindowManager

然后在onCreate()方法中会调用创建的PhoneWindow的setContentView()方法,接着在里面创建Decor以及一个根布局,创建完毕后把layoutResId加载到布局中,然后通知回调

最后调用ActivityThead.handleResumeActivity()方法,在这里把WindowManager和Decor关联起来,并且调用setVisibility让Decor可见,最终把UI呈现在用户面前。

WindowManager如何处理View的添加、移除和更新

这一个问题其实是上一个问题的延伸和扩展,虽然在上面我们说到了ActivityThread()调用handleResumeActivity()方法,然后把Decor通过addView()的方式加入到WindowManager中,但是我们有没有想过,这个addView()的过程有涉及到哪些模块呢?

正是因为这一过程比较复杂,因此也值得单独提出来探究。

addView() 过程

还记得WindowManager.addView() 实际调用的是哪个类的方法吗?忘了的请回去重新看一遍第二问~

WindowManagerGlobal.java

 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        
     		// 省略N行代码...
 
            try {
                // 调用ViewRootImpl.setView方法
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

RootViewImpl.setView()

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

                // 1
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    // 2
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
                    setFrame(mTmpFrame);
                } 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();
                    }
                }

            }
    }

RootViewImpl.setView()方法很复杂,其中我们需要关注的是两点:

  • requestLayout()
  • mWindowSession.addToDisplay()

我们先看一下requestLayout()方法:

// 定义 TraversalRunnable    
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
// 初始化 mTraversalRunnable
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();   

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();   // 检查锁
            mLayoutRequested = true;
            scheduleTraversals();   // 发送 CALLBACK_TRAVERSAL 消息
        }
    }

	@UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 向 mTraversalRunnable 发送一条 CALLBACK_TRAVERSAL 消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }


往Handler里面发送一条CALLBACK_TRAVERSAL消息,这条消息的意思就是刷新界面。

最终会调用 doTraversal() 方法

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();  //1

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

然后到 performTraversals() 方法, 这个方法非常复杂,整个方法加起来大概有800多行,主要工作就是

  • 测量各个View的大小(performMeasure)
  • 布局(performLayout)
  • 绘制(performDraw)

最终把整个视图树展示出来

updateViewLayout() 过程

update过程比较简单,直接上源码:

 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);  // 1
            mParams.remove(index);    // 2
            mParams.add(index, wparams);  // 3
            root.setLayoutParams(wparams, false);  // 4
        }
    }
  1. 首先获取要update的View的ViewRootImpl
  2. 把这个View的LayoutParam移除掉
  3. 重新添加LayoutParam
  4. 刷新根布局

removeView() 过程

@UnsupportedAppUsage
    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);   // 1
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }


    // 移除对应的View
    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);   // 2
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }


 boolean die(boolean immediate) {
        if (immediate && !mIsInTraversal) {
            doDie();    // 3
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);   // 5
        return true;
    }

    void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            // 省略代码...
            
        WindowManagerGlobal.getInstance().doRemoveView(this);  //4
    
  1. 首先通过removeView()方法来移除View,在这个方法里面调用了removeViewLocked()
  2. 在removeViewLocked()又调用了 root.die(immediate)
  3. 在die() 又调用了 doDie()
  4. 在doDie()中调用了 doRemoveView()
  5. 如果需要延迟,则再发送一条MSG_DIE,重新调用doDie()方法

doRemoveView()

    void doRemoveView(ViewRootImpl root) {
        synchronized (mLock) {
            final int index = mRoots.indexOf(root);
            if (index >= 0) {
                mRoots.remove(index);
                mParams.remove(index);
                final View view = mViews.remove(index);
                mDyingViews.remove(view);
            }
        }
        if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
            doTrimForeground();
        }
    

这个方法和上面的update方法类似,从缓存列表中找到对应的view,然后移除掉。

总结

在这篇文章中,我们从Acitivty的创建过程说起,涉及到ActivityThread, Window, WindowManager, WindowManagerService, RootViewImpl, PhoneWindow, WindowManagerImpl, WindowManagerGlobal 这么多类的相关源码分析,希望大家看完之后能对WindowManager相关的知识点有所了解。

如果文章有不对的地方也欢迎大家批评指正,感谢阅读!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值