1. 程式人生 > >我眼中的Window建立/新增/刪除/更新過程

我眼中的Window建立/新增/刪除/更新過程

        在Android中和我們打交道最多的就是Activity,因為我們會頻繁的與介面進行互動,而Activity內部關於介面方面的操作都是由Window來實現的,因此我們有必要了解下Window的實現機制了;網上有挺多關於Window建立/新增/刪除/更新方面的原始碼分析了,我這篇部落格不會去大篇幅的貼出程式碼分析那些原始碼機制,取而代之的是以語言描述的方式展現出Window機制中的一些知識點;

        個人認為想要學習Window機制的實現原理,需要弄懂以下幾個問題,這篇文章主要講解的是應用級Window--->Activity的相關內容:

        (1):Android中Window的分類有哪些呢?

        (2):與Window實現機制有關的是哪些類或者介面呢?

        (3):一個Window是怎麼創建出來的呢?

        (4):新增Window的過程中做了些什麼事情?

        (5):刪除Window的時候發生了什麼?

        (6):更新Window的時候發生了什麼?

        接下來,我一個一個的解答上面的問題:

        首先是Android中Window的分類

        Android中的Window分為三類:

        <1>:應用Window,我們通常見到的就是Activity了;

        <2>:子Window

,比如Dialog;

        <3>:系統Window,比如Toast;

        那麼與Window機制實現有關的類和介面有哪些呢?

        ViewManager(介面)、WindowManager(介面)、WindowManagerImpl(final類)、WindowManagerGlobal(final類)

        具體他們的關係見下圖:

                                              

        接著便是一個Window是怎麼創建出來的呢?

        因為Window是對Activity操作的真正執行者嘛,我們平常呼叫的setContentView實際上呼叫的也是Window的setContentView,那麼很自然找Window是怎麼建立的就該先了解下Activity的建立流程了,在

這篇文章中,我有講到過Activity的啟動過程最終會執行到ApplicationThread的scheduleLaunchActivity方法上面,在這個方法的最後會發送一條訊息來讓H這個Handler進行處理,具體訊息中攜帶的標誌資訊是LAUNCH_ACTIVITY,真正的處理就應該是在H這個Handler裡面的handleMessage了,在他裡面找到case為LAUNCH_ACTIVITY的語句塊,執行handleLaunchActivity方法,而在handleLaunchActivity裡面是會通過performLaunchActivity建立一個Activity物件出來的,具體Activity是怎麼被創建出來的呢?我們稍微看下ActivityThread$performLaunchActivity原始碼就知道了:

java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
 public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }
        很明顯的看出來是利用Instrumentation物件通過反射建立的;

        接著會建立一個Application物件出來,通過LoadedApk的makeApplication方法,這個方法裡面建立Application的方式實際上也是通過Instrumentation物件反射建立的,在makeApplication中建立完Application之後會通過Instrumentation的callApplicationOnCreate方法回撥Application的onCreate方法,這個就是我們Application的生命週期方法啦;

        現在我們僅僅只是建立了Activity物件了,但是我們知道Android程式的執行是需要上下文環境支援的,因此繼續檢視ActivityThread$performLaunchActivity原始碼你會看到有關建立應用上下文的程式碼:

if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity);
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config);
}
        注意我僅僅截取了與我們分析有關的原始碼,在這段原始碼中首先就是建立一個ContextImpl物件了,至於為什麼是ContextImpl型別,你檢視createBaseContextForActivity方法就知道了,有了ContextImpl物件之後,會通過Activity的attach方法,將ContextImpl與我們的Activity繫結起來;

        到此,我們還沒看到有關Window的任何相關程式碼,只看到了Activity的有關部分,可想而知,在Activity的attach裡面必定存在有關Window的部分,簡單把有用的原始碼copy如下:

        Activity$attach

mWindow = PolicyManager.makeNewWindow(this);
............
mWindow.setCallback(this);
.............
mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
............
 mWindowManager = mWindow.getWindowManager();
............

        可以看到首先就是通過PolicyManager的makeNewWindow方法建立了一個PhoneWindow物件出來,具體makeNewWindow的話是通過Policy類的makeNewWindow方法new出來一個PhoneWindow物件的;接著便是為建立的PhoneWindow物件設定回撥;隨後會通過setWindowManager方法為PhoneWindow物件設定WindowManager物件,具體WindowManager是怎麼建立的就是通過setWindowManager的第一個引數(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)建立的,那麼這時候你肯定就想什麼時候會建立Context.WINDOW_SERVICE型別的系統服務呢?其實就在我們建立ContextImpl物件的時候啦,上面已經講到說在建立Activity的時候會繫結ContextImpl物件,即ContextImpl物件會在Activity之前建立的,檢視ContextImpl原始碼,你會發現存在一個static型別的靜態塊程式碼區域,這個區域裡面找到與Context.WINDOW_SERVICE有關的程式碼如下:

registerService(WINDOW_SERVICE, new ServiceFetcher() {
                Display mDefaultDisplay;
                public Object getService(ContextImpl ctx) {
                    Display display = ctx.mDisplay;
                    if (display == null) {
                        if (mDefaultDisplay == null) {
                            DisplayManager dm = (DisplayManager)ctx.getOuterContext().
                                    getSystemService(Context.DISPLAY_SERVICE);
                            mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
                        }
                        display = mDefaultDisplay;
                    }
                    return new WindowManagerImpl(display);
                }});
        可以看到首先建立了一個Display物件出來,隨後呼叫了WindowManagerImpl建構函式,建立了一個WindowManagerImpl物件出來;

        如果你檢視setWindowManager原始碼的話,你會發現他最後會執行我們剛剛建立的WindowManagerImpl物件的createLocalWindowManager語句,並且返回這個建立的值,檢視createLocalWindowManager的原始碼會發現其實他也是建立一個WindowManagerImpl物件出來的,最初我對這個地方一直很費解,為什麼要分兩步來建立WindowManagerImpl物件呢?後來想明白是這樣子的,首先建立一個WindowManagerImpl物件,此時的WindowManagerImpl是沒有和具體的Window發生關聯的,隨後創建出來的WindowManagerImpl會和Window發生關聯操作;

        最後呢,將建立的WindowManager物件賦給我們的Activity類中屬性便可以啦,因為在Activity的別的地方可能會用到WindowManager嘛;

        這樣子,Activity中的Window就建立成功啦,我們來做個小結:

        (1):首先是建立一個Activity物件出來;

        (2):接著是建立一個ContextImpl物件出來,ContextImpl是實現了Context上下文介面的,在建立ContextImpl的同時,會在他的static語句塊中建立一個只包含有Display物件的WindowManager物件出來;

        (3):通過Activity的attach函式,將Activity與建立的ContextImpl物件繫結到一起,在attach裡面會建立PhoneWindow物件,同時為其設定回撥介面;

        (4):通過setWindowManager為當前建立的PhoneWindow物件繫結WindowManager物件,並且返回這個WindowManager物件物件,將這個WindowManager賦給Activity類中的變數;

       在建立了WindowManager物件之後,如果我們想要往Window裡面新增一個View的話,需要呼叫WindowManager的addView方法,而WindowManager是介面,WindowManagerImpl是對WindowManager的實現,如果你檢視WindowManagerImpl的實現的話,會發現它裡面的所有方法都是通過WindowManagerGlobal物件實現的,因此像Window裡面新增View實際上執行的是WindowManagerGlobal的addView,除了addView外,刪除View和更新View也是在WindowManagerGlobal中實現的;

        在WindowManagerGlobal裡的addView方法裡面的虛擬碼實現如下:

//進行一些引數檢查,不合法的話則丟擲異常
............
root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
root.setView(view, wparams, panelParentView);
        我們可以認為addView裡面做了一下幾件事情:

        (1):進行引數合法性的檢查,不合法的話丟擲相應的異常;

        (2):建立一個ViewRootImpl物件出來,為什麼要建立一個ViewRootImpl物件呢?這麼說吧,我們的Window實際上是一個抽象概念,他需要有View的存在而存在,而ViewRootImpl是Window與View之間進行互動的中介吧,瞭解View繪製過程的都清楚,View繪製的開始方法其實就是ViewRootImp裡面的performTraversals,在建立ViewRootImp裡面要注意下面這句程式碼:

mWindowSession = WindowManagerGlobal.getWindowSession();
他會為我們建立一個IWindowSession物件,這個IWindowSession物件具體來講的話是通過WindowManagerGlobal的getWindowSession建立的,這段原始碼不算長,我們來看看:
public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            imm.getClient(), imm.getInputContext());
                    float animatorScale = windowManager.getAnimationScale(2);
                    ValueAnimator.setDurationScale(animatorScale);
                } catch (RemoteException e) {
                    Log.e(TAG, "Failed to open window session", e);
                }
            }
            return sWindowSession;
        }
    }
第6行看到通過getWindowManagerService建立了一個WindowManagerService物件出來,檢視WindowManagerService原始碼會發現實際上是通過IPC的方式創建出來的,如下:
 public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
            }
            return sWindowManagerService;
        }
    }
有了WindowManagerService物件之後,會利用該物件呼叫openSession方法創建出來一個IWindowSession物件,有了這個Session物件之後,隨後的新增操作實際上就是ViewRootImpl通過這個Session來進行新增的;

        (3):繼續回到WindowManagerGlobal的addView方法,在建立完ViewRootImpl之後,為View設定佈局引數,接下來會執行3個add操作,我們來看看這三個物件的定義:

 private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
就是三個List嘛,其中mViews儲存的是所有Window所對應的View,mRoots儲存的是所有Window所對應的ViewRootImpl,mParams儲存的是所有Window對應的佈局引數,最後在所有的新增操作做完之後,執行了ViewRootImpl的setView方法,這裡也印證了我們前面提到的ViewRootImpl其實上是Window與View之間互動的橋樑這個觀點;

        (4):ViewRootImpl的setView方法比較長,我們僅說下和Window新增View有關聯的部分,首先呼叫requestLayout從根View也就是DecorView開始進行重繪,隨後會利用我們在建立ViewRootImpl的Session物件來將當前View新增到窗體中,呼叫的是addToDisplay方法,這個方法的內部會利用我們在建立ViewRootImpl的時候建立的WindowManagerService物件,呼叫WindowManagerService物件的addWindow方法;

        到此,一個View就被新增到Window上面啦,我們來對整個新增過程做個小結:

        <1>:首先利用之前建立Window的過程中建立的WindowManager物件,呼叫它的addView方法,實際上呼叫的是WindowManagerImpl的addView方法,而WindowManagerImpl裡面是由WindowManagerGlobal實現的,也就是呼叫了WindowManagerGlobal的addView方法;

        <2>:在addView中首先會進行引數合法性的檢查,不合法的話丟擲相應的異常資訊;

        <3>:接著會建立一個ViewRootImpl物件出來,他是Window與View進行互動的橋樑,在建立ViewRootImpl的時候會獲取到WindowManagerService物件,同時利用WindowManagerService物件建立一個Session物件,有了Session物件之後,後面的新增操作就可以通過該Session進行了,相當於建立了一個通話過程一樣了;

        <4>:接著便是將當前View、ViewRootImpl、佈局引數LayoutParams新增到各自佇列裡面的操作了;

        <5>:最後呼叫ViewRootImpl的setView方法將View新增到Window上面,其實在setView方法裡面真正的新增操作是通過Session來進行傳遞由WindowMangerService的addWindow方法實現的;

       接著我們看看刪除Window的操作是什麼樣子的了?

        和新增Window的過程有點類似,其實本質上整個過程還是會交割給WindowManager去刪除的,而WindowManagerImpl實現了WindowManager介面,因此任務相當於給了WindowManagerImpl,而WindowManagerImpl裡面所有的操作都是通過橋接模式轉交給WindowManagerGlobal來完成的,在WindowManagerImpl裡面是存在兩個與刪除Window有關的方法的,一個是removeView一個是removeViewImmediate,兩者最終都會執行WindowManagerGlobal的removeView,但是是有區別的,具體區別檢視原始碼註釋:

/**
     * Special variation of {@link #removeView} that immediately invokes
     * the given view hierarchy's {@link View#onDetachedFromWindow()
     * View.onDetachedFromWindow()} methods before returning.  This is not
     * for normal applications; using it correctly requires great care.
     * 
     * @param view The view to be removed.
     */
    public void removeViewImmediate(View view);
        意思是removeViewImmediateremoveView的特殊版本,使用它會在removeView返回之前觸發View樹中View的onDetachedFromWindow和onDetachedFromWindow方法,一般情況下我們的應用程式是不會用這個方法刪除Window的,使用的時候要格外的注意;

        因為上面的removeView和removeViewImmediate都是執行的WindowManagerGlobal的removeView,只不過就是第二個boolean引數值不同而已,因此只需要檢視WindowManagerGlobal的removeView方法就可以了:

        程式碼不長,我們看下原始碼:

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);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }
        第7行會通過findViewLocked方法獲取到要刪除View的index值,這個獲取過程是通過陣列遍歷實現的,接著就呼叫removeViewLocked來刪除這個View,那必須要知道removeViewLocked幹了些什麼啦,程式碼也不長,我貼出來了:
 private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }
        首先會獲取到要刪除View的index對應的ViewRootImpl,此時你應該就有預感了,既然你新增View到Window的時候用到了ViewRootImpl作為橋樑,那麼在刪除的時候,你就應該也用到ViewRootImpl作為橋樑了,後面你會發現確實是這樣子的,有了ViewRootImpl之後就呼叫他的die方法啦,是這樣吧;

        在die方法裡面會根據我們boolean型別的引數immediate來判斷到底是直接執行doDie()方法還是通過Handler傳送一條帶有MSG_DIE標誌的訊息,至於immediate引數值是什麼時候傳入的在一開始我們已經講了WindowManagerImpl裡面存在兩個刪除Window的方法removeView和removeViewImmediate,前者傳入的immediate值是false,後者傳入的immediate值是true,true跟false的區別其實就是同步和非同步刪除的區別了,但是不管用哪種方法,最終執行的都是ViewRootImpl的doDie()方法;

        這個ViewRootImpl的doDie()方法裡面會做一下幾件事情:

        (1):呼叫dispatchDetachedFromWindow方法,在dispatchDetachedFromWindow方法裡面你會見到我們很熟悉的Session操作身影了,在addView就已經說過新增操作是通過Session來相當於建立通訊渠道一樣,由WindowManagerService來真正執行新增操作,那麼這裡刪除操作情況也一樣,也是由Session來建立通訊渠道的,呼叫的是Session的remove方法來移除Window,而你檢視Session的remove原始碼的話會發現實際上執行的還是WindowManagerService的removeWindow方法的,情況和addView完全一致;

        (2):最後還會執行WindowManagerGlobal的doRemoveView方法,直接檢視doRemoveView原始碼你會發現其實就是將當前View從mViews中移出,將當前ViewRootImpl從當前mRoots移出,將當前LayoutParams從mParams移出而已了,因為你在建立的時候新增進去了嘛,刪除的時候就該移出出去啊!

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);
            }
        }
    }
        醬紫的話,刪除操作過程也結束啦,我們也來個小結吶:

        <1>:呼叫WindowManagerImpl的removeView或者removeViewImmediate來進行刪除,但是後者的話我們要慎用啦,官方都這麼建議了還是小心點啦,但是兩者最終執行的都是WindowManagerGlobal的removeView方法;

        <2>:在WindowManagerGlobal的removeView方法裡面,首先先找到我們要刪除View對應的index索引值,有了這個index值之後我們便可以得到該index值對應的是哪個ViewRootImpl了,因為Window上的View的刪除操作實際上還是由ViewRootImpl作為中介橋樑完成的;

        <3>:接下來的刪除操作就轉交給ViewRootImpl來完成,ViewRootImpl的刪除實際上是通過建立ViewRootImpl的時候創建出來的Session來完成的,他好像是建立了一個通訊通道一樣,具體最後的刪除操作是由WindowManagerService來完成的,這個過程中是有涉及到IPC通訊的;

        <4>:在最後刪除結束準備返回之前一定要記得將當前View從mViews列表中刪除,將當前ViewRootImpl從mRoots刪除,將當前LayoutParams從mParams中刪除;

       最後就是在更新Window的時候發生什麼啦?

        不用說,和前面一樣,更新Window操作方法最終執行到的是WindowManagerGlobal的updateViewLayout裡面的,這個原始碼也不長,我們貼出來看看:

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

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

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }
        其實我們可以想想什麼情況下我們會進行更新Window操作,就是在我們Window裡面的View的LayoutParams屬性發生變化的時候嘛,要是讓我們實現的話,肯定就是先把新屬性賦值給View,隨後重繪View就可以啦,其實原始碼裡就是這麼實現的,只不過做了點封裝而已了;看第11行,將新的LayoutParams引數賦值給當前View,隨後通過findViewLocked方法找到當前View的索引值,利用該索引值找到其對應的ViewRootImpl物件,然後更新我們的LayoutParams列表呀,其實就是把原來的刪了,把新的填進去罷了;最後呢,執行了ViewRootImplsetLayoutParams方法,怎麼樣,又看到了ViewRootImpl了吧,整個新增/刪除/更新操作都是拿他作為中間橋樑的,在ViewRootImpl的setLayoutParams方法裡面你會看到執行了scheduleTraversals方法,這個方法就會開啟我們從DecorView的檢視重繪工作,接著呢,還需要更新我們的Window檢視呀,具體是通過scheduleTraversals呼叫performTraversals方法之後,在performTraversals方法裡面執行的,在performTraversals方法裡面執行了ViewRootImpl的relayoutWindow方法,而在relayoutWindow裡面就會執行Session的relayout方法了,很可愛,我們又見到了Session啦,下一步不用想肯定就是執行的WindowManagerService的relayoutWindow方法來更新Window啦啦!

        至此,更新操作結束啦,我們也來小結一下:

        <1>:更新操作呼叫的是WindowManagerGlobal的updateViewLayout方法;

        <2>:這個方法裡面首先會更新我們當前View的LayoutParams屬性,接著會通過當前View找到片當前View所在的index索引值,有了這個索引值我們便可以找到當前View所處的ViewRootImpl啦,隨後更新我們的LayoutParams列表;

        <3>:通過ViewRootImpl來進行具體的更新操作,具體實現是首先先是更新View啦,其實上就是重繪View而已,接著便是重繪Window了,重繪Window的話,實際上是通過建立ViewRootImpl的時候建立的Session物件來完成的,而Session的話只不過是提供了一個通道而已啦,具體的實現還是通過WindowManagerService來進行的;

        好了,至此對Window的建立/新增/刪除/更新操作的原始碼機制分析完畢啦,當然我這裡主要分析的是Activity這個應用級Window的整個過程把,實際上Dialog和Toast這兩種Window的話個人感覺整個過程應該比較類似吧,以後有時間了再細細查看了,只要明白Window、ViewRootImpl、View、WindowManagerImpl、WindowManagerGlobal、Session、WindowManagerService之間的關係的話,感覺能理解的更好;

        本文只是簡單的分析而已,不免會有疏漏錯誤之處,歡迎指正!!!!!!