Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 我眼中的Window創建/添加/刪除/更新過程

我眼中的Window創建/添加/刪除/更新過程

編輯:關於Android編程

在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 mViews = new ArrayList();
    private final ArrayList mRoots = new ArrayList();
    private final ArrayList mParams =
            new ArrayList();
就是三個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);
意思是removeViewImmediate是removeView的特殊版本,使用它會在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列表呀,其實就是把原來的刪了,把新的填進去罷了;最後呢,執行了ViewRootImpl的setLayoutParams方法,怎麼樣,又看到了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之間的關系的話,感覺能理解的更好;

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

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved