基於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相關的知識點有所瞭解。

如果文章有不對的地方也歡迎大家批評指正,感謝閱讀!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章