【.com2月2日独家专题】上一节我们研究了Launcher的整体结构,这一节我们看下Launcher的入口点整个Launcher,还有Laucher在加载布局文件Laucher.xml时做了什么。我们在源码中可以找到LauncherApplication,它继承了Application类,当整个Launcher启动时,它就是整个程序的入口。我们先看看它们在AndroidManifest.xml中是如何配置的。首先,整个Launcher的应用是通过android:name来指定的,即入口在com.android.launcher2.LauncherApplication这个路径下,桌面的名字是通过android来指定的:标签。Launcher,如果要改名字,改成values文件夹的string.xml中对应的属性即可。android:icon指定Laucher的图标,可以在应用管理器中看到。如下图所示,它是一个住在小房子里的可爱机器人。如果您需要更改Laucher的图片,您可以重新设置该属性。android:hardwareAccelerated="@bool/config_hardwareAccelerated"指定对整个应用进行硬件加速,这样整个应用运行起来会更快。android:largeHeap="@bool/config_largeHeap"指定应用程序使用大堆内存,可以在一定程度上避免内存溢出错误的发生。我们可以在values文件夹下的config.xml中看到是否开启硬件加速和大内存的配置。如下图:truefalse在Application中的onCreate()方法中传入:sIsScreenLarge=screenSize==Configuration.SCREENLAYOUT_SIZE_LARGE||screenSize==Configuration.SCREENLAYOUT_SIZE_XLARGE;和sScreenDensity=getResources().getDisplayMetrics().density;判断是否为大屏,获取其屏幕密度。同时通过mIconCache=newIconCache(this);设置应用图标的缓存。然后声明LauncherModel,mModel=newLauncherModel(this,mIconCache);LauncherModel主要用于加载桌面图标、插件和文件夹,同时LaucherModel是一个广播接收器。当package改变,region,或者配置文件改变时,它会向LaucherModel发送广播。LaucherModel会根据不同的广播进行相应的加载操作。这部分将在后面详细描述。LauncherApplication完成初始化工作后,我们来到Launcher.java的onCreate()方法,这也是启动桌面时的一系列初始化工作。首先要注意的是加载launcher布局文件时的一个TraceView调试方法,可以对它们之间的方法进行图形化的性能分析,可以具体到方法代码如下: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后,我们会发现在指定目录下生成了launcher.trace文件,如下图:通过DDMS将launcher.trace文件拉到电脑上,在SDK的tools目录下,执行traceview工具打开launcher.trace。如下图:点击查看大图可以看到setContentView用了448.623ms,占了整个跟踪代码时间的62%,所以在加载布局文件的时候,肯定经过了一系列的加载操作,接下来我们来分析一下。加载启动器布局文件时,最关键的是加载整个工作区。工作区是一个自定义组件。其继承关系如下图。可以看到Workspace其实就是一个ViewGroup,可以添加到其他控件中。ViewGroup组件加载时,首先会读取控件对应的XML文件,然后Framework层会执行它的onMeasure()方法,根据子控件的大小计算出整个控件在屏幕上的大小控制它包含。.Workspace重写了ViewGroup(PagedView中)的onMeasure方法。在工作区中,它测量五个子CellLayout。方法如下。具体含义请参考注释:@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){if(!mIsDataReady){super.onMeasure(widthMeasureSpec,heightMeasureSpec);return;}//获取width模式(对应match_parent或wrap_content中的配置文件)及其大小//宽度必须是match_parent,否则会抛出异常。if(widthMode!=MeasureSpec.EXACTLY){thrownewIllegalStateException("WorkspacecanonlybeusedinEXACTLYmode.");}/*AllowtheheighttobesetasWRAP_CONTENT.Thisallowstheparticularcase*oftheAllappsviewonXLargedisplaystonottakeupmorespacethenitneeds.Width*isstillnotallowedtobesetasWRAP_CONTENTsincemanypartsofthecodeexpect*eachpagetohavethesamewidth.*///高度允许是wrap_content,因为在大屏幕的情况下,会占用多余的位置finalintheightMode=MeasureSpec.getMode(heightMeasureSpec);intheightSize=MeasureSpec.getSize(heightMeasureSpec);intmaxChildHeight=0;//获取垂直方向和水平方向的PaddingfinalintverticalPadding=mPaddingTop+mPaddingBottom;finalinthorizo??ntalPadding=mPaddingLe+mPaddingRight;//孩子们被赋予与工作空间相同的宽度和高度//除非他们被设置为WRAP_CONTENTif(DEBUG)Log.d(TAG,"PagedView.onMeasure():"+widthSize+","+heightSize+"mPaddingTop="+mPaddingTop+"mPaddingBottom="+mPaddingBottom="+mPaddingintBottom=Child);();//进入子视图的世界空间行遍历来衡量它的几个子视图for(inti=0;i0){mMaxScrollX=getChildOffset(childCount-1)-getRelativeChildOffset(childCount-1);}else{mMaxScrollX=0;}}测量完成后,就可以对子控件进行布局了。这时Framework层会调用PagedView中重写的onLayout方法@OverrideprotectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom){if(!mIsDataReady){return;}if(DEBUG)Log.d(TAG,"PagedView.onLayout()");//垂直方向填充finalintverticalPadding=mPaddingTop+mPaddingBottom;finalintchildCount=getChildCount();intchildLeft=0;if(childCount>0){if(DEBUG)Log.d(TAG,"getRelativeChildOffset():"+getMeasuredWidth()+","+getChildWidth(0));childLeft=getRelativeChildOffset(0);//Offset为0if(DEBUG)Log.d(TAG,"childLeft:"+childLeft);//计算变量pagespacingifnecessary//如果mPageSpacing小于0,重新计算mPageSpacing,赋值一个价值。if(mPageSpacing<0){setPageSpacing(((right-left)-getChildAt(0).getMeasuredWidth())/2);}}for(inti=0;i=0&&mCurrentPage=0&&mCurrentPage