戰略性瞭解WindowManager

什麼是戰略性瞭解:知其然,無需知其所以然,也就是知道整體結構框架即可,不追根內部具體實現。
注:文章前面會探索API 14的源碼,後面會探索API 28的源碼


WindowManager直譯過來叫做窗口管理者,用了這麼多年Window的我們,應該對窗口這個概念並不陌生,打開應用,彈出應用窗口,使用應用。電腦屏幕大,所以我們可以在一個電腦屏幕上看到多個應用,手機屏幕小,只看一個應用的體驗比較好。

如果是我們要寫一個類叫做WindowManager,我們會寫哪些方法呢?每個應用都給他分配一個WindowManager,然後添加界面,刪除界面,更新界面,差不多就這些方法吧,等有需求了再拓展嘛,嗯嗯,大概是這個邏輯。看看實際是怎麼實現的吧。

ViewManager探索

根據源碼,我們很輕易的發現WindowManager其實是一個接口,並且繼承ViewManager,ViewManager也是個接口。

public interface WindowManager extends ViewManager

ViewManager聽着像是管理View的,感覺添加View刪除View什麼的都在這裏,別猜了,先看看源碼:

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

添加View,更新View佈局,移除View,方法名字倒是一目瞭然的,但是爲啥沒有deleteView,難道Android窗口這裏不會刪除View嗎,感覺移除操作很簡單,只需要把View設置爲不可見就行了,也不知道內部怎麼實現的,根據方法名字猜想一波吧。

WindowManager探索

我們還是來看看繼承了ViewManager的WindowManager,看看他又實現了啥,最開始猜想的添加刪除view居然是
ViewManager的方法,現在都不知道WindowManager這個類是幹嘛的了,看波源碼再做打算。

public interface WindowManager extends ViewManager {
    
    public static class BadTokenException extends RuntimeException {
        public BadTokenException() {}
        public BadTokenException(String name) {
            super(name);
        }
    }

    public Display getDefaultDisplay();

    public void removeViewImmediate(View view);

    public boolean isHardwareAccelerated();
    
    public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable{
            // 各種屬性
    }
}

內部居然有個異常類,倒是挺簡單;還有個靜態公共類,是我們熟悉的LayoutParams,原來是在這裏定義的。

接着是三個待實現的接口方法,我們來猜猜是幹嘛的:

    // 獲取默認Display實例
    public Display getDefaultDisplay();
    // 立刻移除View
    public void removeViewImmediate(View view);
    // 是否使用硬件加速
    public boolean isHardwareAccelerated();

猜測完畢,源碼看到這裏,下面兩個方法倒是還是好理解,第一個方法不知道Display是啥東西,感覺是跟屏幕有點關係,看看源碼大部分都是一些get的方法,聽着都像是獲取屏幕都信息:

// 隨便摘要一些方法
public class Display {
    ...
    public void getSize(Point outSize){...}
    public int getDisplayId(){...}
    public int getWidth(){...}
    public int getHeight(){...}
    public void getRealSize(Point outSize){...}
    ...
}

大概瞭解一些方法是幹嘛的,不求甚解。確實,主要的一些方法還是跟屏幕有關係。

WindowManagerImpl探索

既然大概知道了WindowManager的結構,那麼來了解一下WindowManager的子類吧。

WindowManager的子類有這麼幾個,除去Test旗下的類,在剩下的類中,有一個類帶着主角臉,那就是WindowManagerImpl,既然這個類自帶主角臉,那麼我們主要就來探索一下這個類。

進去這個類之後,發現這個類只有500多行的代碼,難得源碼才500多行,好好研究一下。既然繼承了WindowManager和ViewManager接口,不然先看看WindowManagerImpl對這幾個方法具體這麼實現的。

ViewManager方法的具體實現

首先看看ViewManager的那三個方法:

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

第一個是addView方法是吧。那就來吧:

    public void addView(View view) {
        addView(view, new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE));
    }

    public void addView(View view, ViewGroup.LayoutParams params) {
        addView(view, params, null, false);
    }

    public void addView(View view, ViewGroup.LayoutParams params, CompatibilityInfoHolder cih) {
        addView(view, params, cih, false);
    }

    private void addView(View view, ViewGroup.LayoutParams params,
                         CompatibilityInfoHolder cih, boolean nest) {
        ...
    }

沒想到居然有這麼多addView方法,而且又是以這種形式調用的,最終還是會調用最下面的這個方法,那麼我們就來具體看看最下面的這個方法是怎麼實現的吧。

爲了便於閱讀,我將addView方法的內部劃分成了三個部分,我們一個一個的來看,大事化小。(注:這三個部分合起來並非addView所有源碼,我只是挑出了主要代碼)

    private void addView(View view, ViewGroup.LayoutParams params,
                         CompatibilityInfoHolder cih, boolean nest) {
        // 第一部分
        // 第二部分
        // 第三部分
    }

我們首先來看第一部分的源碼:

    private View[] mViews;
    private ViewRootImpl[] mRoots;
    
    private void addView(View view, ViewGroup.LayoutParams params,
                         CompatibilityInfoHolder cih, boolean nest) {

        final WindowManager.LayoutParams wparams
                = (WindowManager.LayoutParams) params;

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (this) {
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (!nest) {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                root = mRoots[index];
                root.mAddNesting++;
                // Update layout parameters.
                view.setLayoutParams(wparams);
                root.setLayoutParams(wparams, true);
                return;
            }

           // 第二部分
           // 第三部分

        }
        
        root.setView(view, wparams, panelParentView);
    }

這些代碼看着並非那麼晦澀難懂,雖然不知道findViewLocked這個方法是幹嘛的,但是我們可以繼續看下去,畢竟就兩個if而已,我們看到第二個if拋了個異常,條件是!nest,但是根據前面看到的addView方法的一系列的引用,好像此處的nest變量爲false,那麼這個!nest就一定爲true了,所以這個異常一定會拋出來,我們還是看看第一個if的index>=0在什麼情況下進去吧,再往上看就是findViewLocked方法了,姑且進去一探究竟。

    private View[] mViews;
    
    private int findViewLocked(View view, boolean required) {
        synchronized (this) {
            final int count = mViews != null ? mViews.length : 0;
            for (int i = 0; i < count; i++) {
                if (mViews[i] == view) {
                    return i;
                }
            }
            if (required) {
                throw new IllegalArgumentException(
                        "View not attached to window manager");
            }
            return -1;
        }
    }

看來WindowManagerImpl是用View[]來儲存view的,這個方法是用來檢查WindowManagerImpl是否已經擁有這個view了。
我們再回到addView方法:

    private void addView(View view, ViewGroup.LayoutParams params,
                         CompatibilityInfoHolder cih, boolean nest) {

        synchronized (this) {
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (!nest) {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
            }

           // 第二部分
           // 第三部分

        }
        
    }

再看這個if判斷好像就很合理了,如果該view已經被添加了,就拋出異常。

我們接着來看第二部分的代碼:

    private void addView(View view, ViewGroup.LayoutParams params,
                         CompatibilityInfoHolder cih, boolean nest) {
        if (false) Log.v("WindowManager", "addView view=" + view);

        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException(
                    "Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams
                = (WindowManager.LayoutParams) params;

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (this) {
            // 第一部分
            
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews != null ? mViews.length : 0;
                for (int i = 0; i < count; i++) {
                    if (mRoots[i].mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews[i];
                    }
                }
            }

            root = new ViewRootImpl(view.getContext());
            root.mAddNesting = 1;
            if (cih == null) {
                root.mCompatibilityInfo = new CompatibilityInfoHolder();
            } else {
                root.mCompatibilityInfo = cih;
            }

            view.setLayoutParams(wparams);
            
            // 第三部分
        }
        root.setView(view, wparams, panelParentView);
    }

第二部分的代碼,第一個判斷涉及binder,不在我們的討論範圍,我們就暫且忽視。(一旦討論會涉及更多其他類的源碼,還有AIDL類,涉及源碼太多,就此打住)

接下來就涉及ViewRootImpl,所以第二部分的代碼就此跳過吧,我們來看看第三部分。

    private void addView(View view, ViewGroup.LayoutParams params,
                         CompatibilityInfoHolder cih, boolean nest) {
        if (false) Log.v("WindowManager", "addView view=" + view);

        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException(
                    "Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams
                = (WindowManager.LayoutParams) params;

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (this) {
        
            // 第一部分
            // 第二部分        

            if (mViews == null) {
                index = 1;
                mViews = new View[1];
                mRoots = new ViewRootImpl[1];
                mParams = new WindowManager.LayoutParams[1];
            } else {
                index = mViews.length + 1;
                Object[] old = mViews;
                mViews = new View[index];
                
                System.arraycopy(old, 0, mViews, 0, index - 1);
                old = mRoots;
                mRoots = new ViewRootImpl[index];
                System.arraycopy(old, 0, mRoots, 0, index - 1);
                old = mParams;
                mParams = new WindowManager.LayoutParams[index];
                System.arraycopy(old, 0, mParams, 0, index - 1);
            }
            index--;

            mViews[index] = view;
            mRoots[index] = root;
            mParams[index] = wparams;
        }
        root.setView(view, wparams, panelParentView);
    }

第三部分的代碼顯的中規中矩,如果被添加的view是第一個,就新建一個數據放到第一個位置,如果是第N個,就將原來的數組複製一份,並新增一個位置,將要添加的view放到新增的位置上。然後調用ViewRootImpl的setView方法,將View加載進去。(setView方法的內容很多,你甚至可以直接通過追蹤源碼找到view的事件分發那裏去)

到此爲止,我們就算了解了addView的方法。從整個結構上來看,addView的結構很清晰,將view添加進去。不過記錄了添加的view罷了。

接着我們來看具體首先的第二個方法:updateViewLayout

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

源碼很短:

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams
                = (WindowManager.LayoutParams) params;

        view.setLayoutParams(wparams);

        synchronized (this) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots[index];
            mParams[index] = wparams;
            root.setLayoutParams(wparams, false);
        }
    }

找到View對應的ViewRootImpl類,然後調用setLayoutParams方法,間接刷新界面,也算對得起這個方法的名字。

接着我們來看removeView方法:

    public void removeView(View view) {
        synchronized (this) {
            int index = findViewLocked(view, true);
            View curView = removeViewLocked(index);
            if (curView == view) {
                return;
            }

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

首先查看要被移除的view的位置是哪一個,然後調用removeViewLocked,我們來看看removeViewLocked做了啥:

    View removeViewLocked(int index) {
        ViewRootImpl root = mRoots[index];
        View view = root.getView();

        // Don't really remove until we have matched all calls to add().
        root.mAddNesting--;
        if (root.mAddNesting > 0) {
            return view;
        }

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance(view.getContext());
            if (imm != null) {
                imm.windowDismissed(mViews[index].getWindowToken());
            }
        }
        root.die(false);
        finishRemoveViewLocked(view, index);
        return view;
    }

裏面的代碼涉及了很多位置的方法,不過我們依然可以根據方法名來判斷這個方法具體幹了什麼,不過裏面有行註釋能夠給我們提供不少幫助,翻譯過來:在調用add()方法之前,並沒有真正移除view。
不過這個方法總體就是移除指定的view了。(不求甚解)

WindowManager方法的具體實現

現在我們來看看關於WindowManager接口的具體實現過程:

public interface WindowManager extends ViewManager { 
    public Display getDefaultDisplay();
    public void removeViewImmediate(View view);
    public boolean isHardwareAccelerated();
}

WindowManager接口的具體實現就要簡單的多了。

    public Display getDefaultDisplay() {
        return new Display(Display.DEFAULT_DISPLAY, null);
    }

獲取默認Display原來就是獲取Display的默認實例。

    public void removeViewImmediate(View view) {
        synchronized (this) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots[index];
            View curView = root.getView();

            root.mAddNesting = 0;
            root.die(true);
            finishRemoveViewLocked(curView, index);
            if (curView == view) {
                return;
            }

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

這個方法倒是和removeView方法很類似。

public boolean isHardwareAccelerated() {
        return false;
}

這個方法就顯的異常簡單了。

到目前爲止,我們就算是把WindowManager的這6個方法的具體實現給看完了,那我們又能得出什麼呢,總不能白看吧。

我們發現,WindowManager確實主要是用來管理view的,主要就是view的那幾個操作,增加刪除和更新操作,但這裏的刪除更新,並不是由WindowManager本身來實現的,而是藉助ViewRootImpl類來進行真正具體的操作。本身的主要任務是通過數組的方式記錄View和對應的ViewRootImpl,不愧是管理者,指揮其他人幫你幹事就行了,然後我們指揮管理者。

基於API28的探索

前面的代碼都是基於API14的代碼,爲什麼我要用API14的代碼講解之後再用API28的代碼呢,因爲低級的API往往比高級的API源碼要簡單很多,但是Android的源碼,這些代碼的主要架構是不會變的,現在我們已經瞭解了API14的源碼,那麼現在我們來了解API28的源碼就會快很多,試試?

API28的ViewManager類倒是沒有變化,不過WindowManager倒是變了不少,現在來看看這個接口變成什麼樣子了:

public interface WindowManager extends ViewManager {

    // 一堆屬性
    // 兩個異常類

    public Display getDefaultDisplay();

    public void removeViewImmediate(View view);

    public interface KeyboardShortcutsReceiver {
        void onKeyboardShortcutsReceived(List<KeyboardShortcutGroup> result);
    }

    public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);

    public Region getCurrentImeTouchRegion();

    public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable{
            // 各種屬性
    }

}

我們可以看到,還是有不少變化,新增了2個方法,去掉了isHardwareAccelerated這個方法。

子類也由多個變成了一個:

WindowManagerImpl的變化更大,從原來的500多行,變成了100來行,難得源碼變少。

大概看看裏面長什麼樣:

public final class WindowManagerImpl implements WindowManager{
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        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);
    }
    ...
}

原來裏面的方法都被mGlobal給承包了,以前是通過ViewRootImpl實現的東西,現在由WindowManagerGlobal實現,所以這到底是個啥。

進來WindowManagerGlobal這個類之後,發現這個類,跟API14的WindowManagerImpl特別像,以前WindowManagerImpl有的方法,現在在WindowManagerGlobal這個類裏都能找到,而且還做了拓展,並且實現方式也很類似,看來Google將原來WindowManagerImpl的方法都讓WindowManagerGlobal去管理了。而且儲存view的形式也從數組變成了ArrayList,想必是要管理更多的View了。

總結

通過對WindowManager類的探索,我們大致瞭解到了,其實這個類主要是對view進行管理,添加移除什麼的,但是這只是個管理類,所以具體的實現不可能寫在這個類裏面,若要了解具體如何實現的,還需進入ViewRootImpl類裏面細細探索,這個類纔是真正的大頭,我們只是站在了一個頂端的位置俯瞰整個結構,還需深入每一個模塊才能知其所以然。

我們也知道了老代碼和新代碼雖然有差異,但是核心的思想還是不會變的,如果要想了解某些模塊的具體實現流程,在啃不動源碼的時候,不妨啃啃老代碼,老代碼沒有花裏胡哨(各種各樣)的類去裝飾,側重核心,可以很快了解內部機制。(其實本篇博客的目的是這個😂)

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