【專家專欄】Android 4.0 Launcher源碼分析系列(二)
原創(chuàng)【51CTO.com 2月2日獨家特稿】上一節(jié)我們研究了Launcher的整體結構,這一節(jié)我們看看整個Laucher的入口點,同時Laucher在加載了它的布局文件Laucher.xml時都干了些什么。
我們在源代碼中可以找到LauncherApplication, 它繼承了Application類,當整個Launcher啟動時,它就是整個程序的入口。我們先來看它們在AndroidManifest.xml中是怎么配置的。
- <application
- android:name="com.android.launcher2.LauncherApplication"
- android:label="@string/application_name"
- android:icon="@drawable/ic_launcher_home"
- android:hardwareAccelerated="@bool/config_hardwareAccelerated"
- 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" 指定了應用程序使用了大的堆內(nèi)存,能在一定程度上避免,對內(nèi)存out of memory錯誤的出現(xiàn)。我們可以在values文件夾的config.xml中看到對是否啟用硬件加速和大內(nèi)存的配置。如下所示:
- <bool name="config_hardwareAccelerated">true</bool>
- <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是一個廣播接收器,在程序包發(fā)生改變、區(qū)域、或者配置文件發(fā)生改變時,都會發(fā)送廣播給LaucherModel,LaucherModel會根據(jù)不同的廣播來做相應加載操作,此部分會在后面做詳細介紹。
在LauncherApplication完成初始化工作之后,我們就來到了Launcher.java的onCreate()方法,同樣是啟動桌面時的一系列初始化工作。
首先需要注意的是在加載launcher布局文件時的一個TraceView的調(diào)試方法,它能夠?qū)υ谒麄冎g的方法進行圖形化的性能分析,并且能夠具體到method 代碼如下:
- if (PROFILE_STARTUP) {
- android.os.Debug.startMethodTracing(
- Environment.getDataDirectory() + "/data/com.android.launcher/launcher");
- }
- if (PROFILE_STARTUP) {
- android.os.Debug.stopMethodTracing();
- }
我指定的生成性能分析的路徑是:/data/data/com.android.launcher/launcher,啟動launcher后我們會發(fā)現(xiàn)在指定的目錄下生成了launcher.trace文件,如下圖所示:

把launcher.trace文件通過DDMS pull到電腦上,在SDK的tools目錄里,執(zhí)行traceview工具來打開launcher.trace .如下圖所示:

可以看到setContentView使用了448.623ms,占整個跟蹤代碼時間的62%,所以說在加載布局文件時,肯定經(jīng)過了一系列的加載運算,我們接著分析。
當加載launcher布局文件的過程時,最為關鍵的時對整個workspace的加載,workspace是一個自定義組件,它的繼承關系如下所示,可以看到Workspace實際上也是一個ViewGroup,可以加入其他控件。

當ViewGroup組件進行加載的時候首先會讀取本控件對應的XML文件,然后Framework層會執(zhí)行它的onMeasure()方法,根據(jù)它所包含的子控件大小來計算出整個控件要在屏幕上占的大小。Workspace重寫了ViewGroup的onMeasure方法(在PagedView中),在workspace中是對5個子CellLayout進行測量,的方法如下, 具體含義請看注釋:
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (!mIsDataReady) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- return;
- }
- //得到寬度的模式(在配置文件中對應的是match_parent 或者 wrap_content)和其大小
- final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- //寬度必須是match_parent,否則會拋出異常。
- if (widthMode != MeasureSpec.EXACTLY) {
- throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
- }
- /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
- * of the All apps view on XLarge displays to not take up more space then it needs. Width
- * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
- * each page to have the same width.
- */
- //高度允許是wrap_content,因為在大屏幕的情況下,會占了多余的位置
- final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- int maxChildHeight = 0;
- //得到在豎值方向上和水平方向上的Padding
- final int verticalPadding = mPaddingTop + mPaddingBottom;
- final int horizontalPadding = mPaddingLeft + mPaddingRight;
- // The children are given the same width and height as the workspace
- // unless they were set to WRAP_CONTENT
- if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize + " mPaddingTop="+mPaddingTop + " mPaddingBottom="+mPaddingBottom);
- final int childCount = getChildCount();
- //對workspace的子View進行遍歷,從而對它的幾個子view進行測量。
- for (int i = 0; i < childCount; i++) {
- // disallowing padding in paged view (just pass 0)
- final View child = getPageAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- int childWidthMode;
- if (lp.width == LayoutParams.WRAP_CONTENT) {
- childWidthMode = MeasureSpec.AT_MOST;
- } else {
- childWidthMode = MeasureSpec.EXACTLY;
- }
- int childHeightMode;
- if (lp.height == LayoutParams.WRAP_CONTENT) {
- childHeightMode = MeasureSpec.AT_MOST;
- } else {
- childHeightMode = MeasureSpec.EXACTLY;
- }
- final int childWidthMeasureSpec =
- MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode);
- final int childHeightMeasureSpec =
- MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode);
- //對子View的大小進行設置,傳入width和height參數(shù)
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
- if (DEBUG) Log.d(TAG, "\tmeasure-child" + i + ": " + child.getMeasuredWidth() + ", "
- + child.getMeasuredHeight());
- }
- if (heightMode == MeasureSpec.AT_MOST) {
- heightSize = maxChildHeight + verticalPadding;
- }
- //存儲測量后的寬度和高度
- setMeasuredDimension(widthSize, heightSize);
- // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions.
- // We also wait until we set the measured dimensions before flushing the cache as well, to
- // ensure that the cache is filled with good values.
- invalidateCachedOffsets();
- updateScrollingIndicatorPosition();
- if (childCount > 0) {
- mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1);
- } else {
- mMaxScrollX = 0;
- }
- }
測量完畢之后就可以對子控件進行布局了,這時候Framework層會調(diào)用PagedView中重寫的onLayout方法。
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (!mIsDataReady) {
- return;
- }
- if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
- //豎值方向的Padding
- final int verticalPadding = mPaddingTop + mPaddingBottom;
- final int childCount = getChildCount();
- int childLeft = 0;
- if (childCount > 0) {
- if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", "
- + getChildWidth(0));
- childLeft = getRelativeChildOffset(0);
- //偏移量為0
- if (DEBUG) Log.d(TAG, "childLeft:"+childLeft);
- // Calculate the variable page spacing if necessary
- // 如果mPageSpacing小于0的話,就重新計算mPageSpacing,并且給它賦值。
- if (mPageSpacing < 0) {
- setPageSpacing(((right - left) - getChildAt(0).getMeasuredWidth()) / 2);
- }
- }
- for (int i = 0; i < childCount; i++) {
- final View child = getPageAt(i);
- if (child.getVisibility() != View.GONE) {
- final int childWidth = getScaledMeasuredWidth(child);
- final int childchildHeight = child.getMeasuredHeight();
- int childTop = mPaddingTop;
- if (mCenterPagesVertically) {
- childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2;
- }
- if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
- //把5個CellLayout布局到相應的位置,layout的4個參數(shù)分別是 左、上、右、下。
- child.layout(childLeft, childTop,
- childLeft + child.getMeasuredWidth(), childTop + childHeight);
- childLeft += childWidth + mPageSpacing;
- }
- }
- //***次布局完畢之后,就根據(jù)當前頁偏移量(當前頁距離Workspace最左邊的距離)滾動到默認的頁面去,***次布局時
- //默認的當前頁是3,則它的便宜量就是兩個CellLayout的寬度。
- if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
- setHorizontalScrollBarEnabled(false);
- int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage);
- //滾動到指定的位置
- scrollTo(newX, 0);
- mScroller.setFinalX(newX);
- if (DEBUG) Log.d(TAG, "newX is "+newX);
- setHorizontalScrollBarEnabled(true);
- mFirstLayout = false;
- }
- if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
- mFirstLayout = false;
- }
- }
【51CTO.com獨家特稿,非經(jīng)授權謝絕轉(zhuǎn)載,合作媒體轉(zhuǎn)載請注明原文作者及出處!】