Android 4.0 Launcher源碼分析系列(二)

這一節我們看看整個Laucher的入口點,同時Laucher在加載瞭它的佈局文件Laucher.xml時都幹瞭些什麼。
我們在源代碼中可以找到LauncherApplication, 它繼承瞭Application類,當整個Launcher啟動時,它就是整個程序的入口。我們先來看它們在AndroidManifest.xml中是怎麼配置的。
1. <application
2.     android:name="com.android.launcher2.LauncherApplication"
3.     android:label="@string/application_name"
4.     android:icon="@drawable/ic_launcher_home"
5.     android:hardwareAccelerated="@bool/config_hardwareAccelerated"
6.     android:largeHeap="@bool/config_largeHeap">    
首先通過android:name指定瞭整個Launcher的Application也就是入口是在 com.android.launcher2.LauncherApplication這個路徑下,android:lable指定瞭桌面的名字是叫 Launcher,如果要改名字就改values文件夾的string.xml中的相應屬性就可以瞭。android:icon指定瞭Laucher的圖標,這個圖標可以在應用程序管理器中看見,如下圖所示,是個可愛機器人住在一個小房子裡面,如果需要更改Laucher的圖片,重新設置這個屬性就可以瞭。
 
android:hardwareAccelerated="@bool/config_hardwareAccelerated" 指定瞭整個應用程序是啟用硬件加速的,這樣整個應用程序的運行速度會更快。
android:largeHeap="@bool/config_largeHeap" 指定瞭應用程序使用瞭大的堆內存,能在一定程度上避免,對內存out of memory錯誤的出現。我們可以在values文件夾的config.xml中看到對是否啟用硬件加速和大內存的配置。如下所示:
1. <bool name="config_hardwareAccelerated">true</bool>
2. <bool name="config_largeHeap">false</bool>
在Application中onCreate()方法通過:sIsScreenLarge = screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE || screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE; 和sScreenDensity = getResources().getDisplayMetrics().density;來判斷是否是大屏幕,同時得到它的屏幕密度。同時通過mIconCache = new IconCache(this); 來設置瞭應用程序的圖標的cache,然後申明瞭LauncherModel,mModel = new LauncherModel(this, mIconCache); LauncherModel主要用於加載桌面的圖標、插件和文件夾,同時LaucherModel是一個廣播接收器,在程序包發生改變、區域、或者配置文件發生改變時,都會發送廣播給LaucherModel,LaucherModel會根據不同的廣播來做相應加載操作,此部分會在後面做詳細介紹。
在LauncherApplication完成初始化工作之後,我們就來到瞭Launcher.java的onCreate()方法,同樣是啟動桌面時的一系列初始化工作。
首先需要註意的是在加載launcher佈局文件時的一個TraceView的調試方法,它能夠對在他們之間的方法進行圖形化的性能分析,並且能夠具體到method 代碼如下:
1. if (PROFILE_STARTUP) { 
2.      android.os.Debug.startMethodTracing( 
3.              Environment.getDataDirectory() + "/data/com.android.launcher/launcher"); 
4.  } 
5.  if (PROFILE_STARTUP) { 
6.      android.os.Debug.stopMethodTracing(); 
7.  } 
我指定的生成性能分析的路徑是:/data/data/com.android.launcher/launcher,啟動launcher後我們會發現在指定的目錄下生成瞭launcher.trace文件,如下圖所示:
 
把launcher.trace文件通過DDMS pull到電腦上,在SDK的tools目錄裡,執行traceview工具來打開launcher.trace .如下圖所示:
 
點擊查看大圖
可以看到setContentView使用瞭448.623ms,占整個跟蹤代碼時間的62%,所以說在加載佈局文件時,肯定經過瞭一系列的加載運算,我們接著分析。
當加載launcher佈局文件的過程時,最為關鍵的時對整個workspace的加載,workspace是一個自定義組件,它的繼承關系如下所示,可以看到Workspace實際上也是一個ViewGroup,可以加入其他控件。
 

當ViewGroup組件進行加載的時候首先會讀取本控件對應的XML文件,然後Framework層會執行它的onMeasure()方法,根據它所包含的子控件大小來計算出整個控件要在屏幕上占的大小。Workspace重寫瞭ViewGroup的onMeasure方法(在PagedView中),在workspace中是對5個子CellLayout進行測量,的方法如下, 具體含義請看註釋:
1. @Override
2.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3.         if (!mIsDataReady) {
4.             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
5.             return;
6.         }
7.         //得到寬度的模式(在配置文件中對應的是match_parent 或者 wrap_content)和其大小
8.         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
9.         final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
10.         //寬度必須是match_parent,否則會拋出異常。
11.         if (widthMode != MeasureSpec.EXACTLY) {
12.             throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
13.         }
14. 
15.         /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
16.          * of the All apps view on XLarge displays to not take up more space then it needs. Width
17.          * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
18.          * each page to have the same width.
19.          */
20.         //高度允許是wrap_content,因為在大屏幕的情況下,會占瞭多餘的位置
21.         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
22.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
23.         int maxChildHeight = 0;
24.         //得到在豎值方向上和水平方向上的Padding 
25.         final int verticalPadding = mPaddingTop + mPaddingBottom;
26.         final int horizontalPadding = mPaddingLeft + mPaddingRight;
27. 
28. 
29.         // The children are given the same width and height as the workspace
30.         // unless they were set to WRAP_CONTENT
31.         if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize  + " mPaddingTop="+mPaddingTop + " mPaddingBottom="+mPaddingBottom);
32.         final int childCount = getChildCount();
33.         //對workspace的子View進行遍歷,從而對它的幾個子view進行測量。
34.         for (int i = 0; i < childCount; i++) {
35.             // disallowing padding in paged view (just pass 0)
36.             final View child = getPageAt(i);
37.             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
38. 
39.             int childWidthMode;
40.             if (lp.width == LayoutParams.WRAP_CONTENT) {
41.                 childWidthMode = MeasureSpec.AT_MOST;
42.             } else {
43.                 childWidthMode = MeasureSpec.EXACTLY;
44.             }
45. 
46.             int childHeightMode;
47.             if (lp.height == LayoutParams.WRAP_CONTENT) {
48.                 childHeightMode = MeasureSpec.AT_MOST;
49.             } else {
50.                 childHeightMode = MeasureSpec.EXACTLY;
51.             }
52. 
53.             final int childWidthMeasureSpec =
54.                 MeasureSpec.makeMeasureSpec(widthSize – horizontalPadding, childWidthMode);
55.             final int childHeightMeasureSpec =
56.                 MeasureSpec.makeMeasureSpec(heightSize – verticalPadding, childHeightMode);
57.             //對子View的大小進行設置,傳入width和height參數
58.             child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
59.             maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
60.             if (DEBUG) Log.d(TAG, "\tmeasure-child" + i + ": " + child.getMeasuredWidth() + ", "
61.                     + child.getMeasuredHeight());
62.         }
63. 
64.         if (heightMode == MeasureSpec.AT_MOST) {
65.             heightSize = maxChildHeight + verticalPadding;
66.         }
67.         //存儲測量後的寬度和高度
68.         setMeasuredDimension(widthSize, heightSize);
69. 
70.         // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions.
71.         // We also wait until we set the measured dimensions before flushing the cache as well, to
72.         // ensure that the cache is filled with good values.
73.         invalidateCachedOffsets();
74.         updateScrollingIndicatorPosition();
75. 
76.         if (childCount > 0) {
77.             mMaxScrollX = getChildOffset(childCount – 1) – getRelativeChildOffset(childCount – 1);
78.         } else {
79.             mMaxScrollX = 0;
80.         }
81.     }
測量完畢之後就可以對子控件進行佈局瞭,這時候Framework層會調用PagedView中重寫的onLayout方法。
1. @Override
2.    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
3.        if (!mIsDataReady) {
4.            return;
5.        }
6. 
7.        if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
8.        //豎值方向的Padding
9.        final int verticalPadding = mPaddingTop + mPaddingBottom;
10.        final int childCount = getChildCount();
11.        int childLeft = 0;
12.        if (childCount > 0) {
13.            if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", "
14.                    + getChildWidth(0));
15.            childLeft = getRelativeChildOffset(0);
16.            //偏移量為0
17.            if (DEBUG) Log.d(TAG, "childLeft:"+childLeft);  
18. 
19.            // Calculate the variable page spacing if necessary
20.            // 如果mPageSpacing小於0的話,就重新計算mPageSpacing,並且給它賦值。
21.            if (mPageSpacing < 0) {
22.                setPageSpacing(((right – left) – getChildAt(0).getMeasuredWidth()) / 2);
23.            }
24.        }
25. 
26.        for (int i = 0; i < childCount; i++) {
27.            final View child = getPageAt(i);
28.            if (child.getVisibility() != View.GONE) {
29.                final int childWidth = getScaledMeasuredWidth(child);
30.                final int childchildHeight = child.getMeasuredHeight();
31.                int childTop = mPaddingTop;
32.                if (mCenterPagesVertically) {
33.                    childTop += ((getMeasuredHeight() – verticalPadding) – childHeight) / 2;
34.                }
35.                
36.                if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
37.                //把5個CellLayout佈局到相應的位置,layout的4個參數分別是 左、上、右、下。
38.                child.layout(childLeft, childTop,
39.                        childLeft + child.getMeasuredWidth(), childTop + childHeight);
40.                childLeft += childWidth + mPageSpacing;
41.            }
42.        }
43.        //第一次佈局完畢之後,就根據當前頁偏移量(當前頁距離Workspace最左邊的距離)滾動到默認的頁面去,第一次佈局時
44.        //默認的當前頁是3,則它的便宜量就是兩個CellLayout的寬度。
45.        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
46.            setHorizontalScrollBarEnabled(false);
47.            int newX = getChildOffset(mCurrentPage) – getRelativeChildOffset(mCurrentPage);
48.            //滾動到指定的位置
49.            scrollTo(newX, 0);
50.            mScroller.setFinalX(newX);
51.            if (DEBUG) Log.d(TAG, "newX is "+newX);
52.            setHorizontalScrollBarEnabled(true);
53.            mFirstLayout = false;
54.        }
55. 
56.        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
57.            mFirstLayout = false;
58.        }
59.    }

 作者:LuoXianXion
 

發佈留言