這一節我們看看整個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