Android 4.0 Launcher2源码分析——启动过程分析

本文来自http://blog.csdn.net/chenshaoyang0011 转载请申明文章出处!

文中如有纰漏之处,望不吝指教~~~欢迎讨论,共同学习~~~

Android的应用程序的入口定义在AndroidManifest.xml文件中可以找出:

[html] view plain copy
  1. <manifest  
  2. xmlns:android="http://schemas.android.com/apk/res/android"  
  3. package="com.android.launcher">  
  4.   
  5. <original-package android:name="com.android.launcher2" />  
  6. ...  
  7. <application  
  8.     android:name="com.android.launcher2.LauncherApplication"  
  9.     ...  
  10.     >  
  11.     <activity  
  12.         android:name="com.android.launcher2.Launcher"  
  13.         ...  
  14.         >  
  15.         <intent-filter>  
  16.             <action android:name="android.intent.action.MAIN" />  
  17.             <category android:name="android.intent.category.HOME" />  
  18.             <category android:name="android.intent.category.DEFAULT" />  
  19.             <category android:name="android.intent.category.MONKEY"/>  
  20.         </intent-filter>  
  21.     </activity>  
  22.     ...  
  23. </application>  
  24. </manifest>  

从中我们可以知道启动过程需要先后初始化LauncherApplication和Launcher的对象。更加简洁的说,启动过程可以分成两步,第一步在

LauncherApplication.onCreate()方法中,第二部在Launcher.onCreate()方法中。

先看第一步,代码片段如下:

[java] view plain copy
  1. public void onCreate() {  
  2.         super.onCreate();  
  3.         // 在创建icon cache之前,我们需要判断屏幕的大小和屏幕的像素密度,以便创建合适大小的icon  
  4.         final int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;  
  5.         sIsScreenLarge = screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE ||  
  6.             screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE;  
  7.         sScreenDensity = getResources().getDisplayMetrics().density;  
  8.   
  9.         mIconCache = new IconCache(this);  
  10.         mModel = new LauncherModel(this, mIconCache);  
  11.   
  12.         // 注册广播接收器  
  13.         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);  
  14.         ......  
  15.         registerReceiver(mModel, filter);  
  16.   
  17.   
  18.         //注册ContentObserver,监听LauncherSettings.Favorites.CONTENT_URI数据的变化  
  19.         ContentResolver resolver = getContentResolver();  
  20.         resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,  
  21.                 mFavoritesObserver);  
  22.     }  

LauncherApplication是Application的子类,是整个程序的入口。因此,一些全局信息的初始化和保存工作就放到这里执行。包括屏幕大小,像素密度信息的获取,以及

BroadcastReceiver和ContentObserver的注册都在整个程序的开始就完成。LauncherApplication的工作结束之后,下面就开始初始化Launcher了。Launcher是一个Activity,

而Activity的生命周期中,有几个重要的回调方法,而onCreate()方法是最先被执行的用于进行初始化操作的。那下面就来看看Launcher.onCreate()中具体做了哪些操作:

[java] view plain copy
  1. protected void onCreate(Bundle savedInstanceState) {  
  2.     ...  
  3.     mModel = app.setLauncher(this);  
  4.     mIconCache = app.getIconCache();  
  5.     ...  
  6.     mAppWidgetManager = AppWidgetManager.getInstance(this);  
  7.     mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);  
  8.     mAppWidgetHost.startListening();  
  9.     ...  
  10.     //检查本地保存的配置是否需要更新  
  11.     checkForLocaleChange();  
  12.     setContentView(R.layout.launcher);  
  13.     //对UI控件进行初始化和配置  
  14.     setupViews();  
  15.     //向用户展示指导的页面  
  16.     showFirstRunWorkspaceCling();  
  17.     registerContentObservers();  
  18.     ...  
  19.     if (!mRestoring) {  
  20.     //为Launcher加载数据  
  21.         mModel.startLoader(thistrue);  
  22.     }  
  23.     ...  
  24. }  

可以通过时序图,直观的认识下,onCreate()中主要进行了哪些操作:

Android 4.0 Launcher2源码分析——启动过程分析_第1张图片

可以将Launcher.onCreate()所执行的操作大概分为七步:

1、LauncherAppliaction.setLauncher()。

2、AppWidgetHost.startListening(),对widget事件进行监听

3、checkForLocaleChange(),检查更新本地保存的配置文件

4、setupViews(),配置UI控件

5、showFirstRunWorkspaceCling(),第一次启动时显示的指导画面

6、registerContentObservers(),设置内容监听器

7、LauncherModel.startLoader(),为Launcher加载Workspace和AllApps中的内容

那么,下面就一步一步的顺着执行的过程来看Launcher启动过程中都做了些什么。

Step1:LauncherApplication.setLauncher()

调用LauncherAppliction对象的setLauncher()方法,得到一个LauncherModel对象的引用,setLauncher内容如下:

[java] view plain copy
  1. LauncherModel setLauncher(Launcher launcher) {  
  2.         mModel.initialize(launcher);  
  3.         return mModel;  
  4.     }  
在setLauncher中继续执行了mModel对象的initialize方法,在initialize中只有小段代码:
[java] view plain copy
  1. public void initialize(Callbacks callbacks) {  
  2.         synchronized (mLock) {  
  3.             mCallbacks = new WeakReference<Callbacks>(callbacks);  
  4.         }  
  5.     }  

由于Launcher实现了Callback接口。在mModel中,将传入的Launcher对象向下转型为Callback赋值给mCallbacks变量。并在LauncherModel中获得了一个Callbacks的软引

通过这一过程,将Launcher对象作为Callback与mModel进行绑定,当mModel后续进行操作时,Launcher可以通过回调得到结果。

Step2:mAppWidgetHost.startListening()

LauncherAppWidgetHost继承自AppWidgetHost,它的作用就是帮助Launcher管理AppWidget,并且能够捕获长按事件,使得应用可以正常的删除、添加

AppWidget。通过调用mAppWidgetHost.startListening()方法,开启监听。

Step3:checkForLocaleChange()

接下来执行checkForLocaleChange(),方法内容如下:

[java] view plain copy
  1. private void checkForLocaleChange() {  
  2.         if (sLocaleConfiguration == null) {  
  3.   
  4.             //从本地存储文件中加载配置信息,包括locale地理位置、mcc移动国家代码  
  5.             //mnc移动网络代码  
  6.             new AsyncTask<Void, Void, LocaleConfiguration>() {  
  7.                 @Override  
  8.                 protected LocaleConfiguration doInBackground(Void... unused) {  
  9.                     LocaleConfiguration localeConfiguration = new LocaleConfiguration();  
  10.                     readConfiguration(Launcher.this, localeConfiguration);  
  11.                     return localeConfiguration;  
  12.                 }  
  13.   
  14.                 @Override  
  15.                 protected void onPostExecute(LocaleConfiguration result) {  
  16.                     sLocaleConfiguration = result;  
  17.                     //从本地取出信息后,再次调用  
  18.                     checkForLocaleChange();    
  19.                 }  
  20.             }.execute();  
  21.             return;  
  22.         }  
  23.   
  24.         //得到设备当前的配置信息  
  25.         final Configuration configuration = getResources().getConfiguration();  
  26.   
  27.         final String previousLocale = sLocaleConfiguration.locale;  
  28.         final String locale = configuration.locale.toString();  
  29.   
  30.         final int previousMcc = sLocaleConfiguration.mcc;  
  31.         final int mcc = configuration.mcc;  
  32.   
  33.         final int previousMnc = sLocaleConfiguration.mnc;  
  34.         final int mnc = configuration.mnc;  
  35.   
  36.         boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;  
  37.   
  38.   
  39.         if (localeChanged) {  
  40.             sLocaleConfiguration.locale = locale;  
  41.             sLocaleConfiguration.mcc = mcc;  
  42.             sLocaleConfiguration.mnc = mnc;  
  43.   
  44.             //清空Icon  
  45.             mIconCache.flush();  
  46.   
  47.             final LocaleConfiguration localeConfiguration = sLocaleConfiguration;  
  48.               
  49.             //将更新后的数据重新写入本地文件保存  
  50.             new Thread("WriteLocaleConfiguration") {  
  51.                 @Override  
  52.                 public void run() {  
  53.                     writeConfiguration(Launcher.this, localeConfiguration);  
  54.                 }  
  55.             }.start();  
  56.         }  
  57.     }  

在这个方法中,先是检查了本地文件的配置与当前设备的配置是否一致,如果不一致,则更新配置,并且清空IconCache,因为配置的改变可能会改变语言环境,

所以需要清空IconCache中的内容重新加载。

Step4:setupViews()

setupViews()方法调用,在这个方法中简单的对所有的UI控件进行加载和配置:

[java] view plain copy
  1. /** 
  2.      * Finds all the views we need and configure them properly. 
  3.      */  
  4.     private void setupViews() {  
  5.         final DragController dragController = mDragController;  
  6.         ...  
  7.         // Setup the drag layer  
  8.         mDragLayer.setup(this, dragController);  
  9.   
  10.         // Setup the hotseat  
  11.         mHotseat = (Hotseat) findViewById(R.id.hotseat);  
  12.         if (mHotseat != null) {  
  13.             mHotseat.setup(this);  
  14.         }  
  15.   
  16.         // Setup the workspace  
  17.         mWorkspace.setHapticFeedbackEnabled(false);  
  18.         mWorkspace.setOnLongClickListener(this);  
  19.         mWorkspace.setup(dragController);  
  20.         dragController.addDragListener(mWorkspace);  
  21.   
  22.         // Get the search/delete bar  
  23.         mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar);  
  24.   
  25.         // Setup AppsCustomize  
  26.         mAppsCustomizeTabHost = (AppsCustomizeTabHost)  
  27.                 findViewById(R.id.apps_customize_pane);  
  28.         mAppsCustomizeContent = (AppsCustomizePagedView)  
  29.                 mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);  
  30.         mAppsCustomizeContent.setup(this, dragController);  
  31.   
  32.         // Get the all apps button  
  33.         mAllAppsButton = findViewById(R.id.all_apps_button);  
  34.         if (mAllAppsButton != null) {  
  35.             mAllAppsButton.setOnTouchListener(new View.OnTouchListener() {  
  36.                 @Override  
  37.                 public boolean onTouch(View v, MotionEvent event) {  
  38.                     if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {  
  39.                         onTouchDownAllAppsButton(v);  
  40.                     }  
  41.                     return false;  
  42.                 }  
  43.             });  
  44.         }  
  45.         // Setup the drag controller (drop targets have to be added in reverse order in priority)  
  46.         dragController.setDragScoller(mWorkspace);  
  47.         dragController.setScrollView(mDragLayer);  
  48.         dragController.setMoveTarget(mWorkspace);  
  49.         dragController.addDropTarget(mWorkspace);  
  50.         if (mSearchDropTargetBar != null) {  
  51.             mSearchDropTargetBar.setup(this, dragController);  
  52.         }  
  53.     }  
由于UI组件较多,setupViews中所进行的操作也比较繁琐,先通过时序图来简单的理一下吧:

Android 4.0 Launcher2源码分析——启动过程分析_第2张图片

这里一共包括5个UI组件和一个DragController,那就一步一步地看都进行了哪些操作吧。

1、DragLayer

首先我们简单的认识下Draglayer。DragLayer继承自FrameLayout,是整个Launcher的根容器。当快捷图标或者AppWidget被拖拽时,事件的处理就在DragLayer

行操作的,DragLayer.setup()方法的内容如下:

[java] view plain copy
  1. public void setup(Launcher launcher, DragController controller) {  
  2.         mLauncher = launcher;  
  3.         mDragController = controller;  
  4.     }  
只是简单的做了赋值操作,使DragLayer持有Launcher和DragController对象的引用。DragController可以帮助其实现拖拽操作。

2、Hotseat

Hotseat也是FrameLayout的直接子类,代表主屏幕下方的dock栏,可以放置4个快捷图标和一个进入AllApps的按钮。代码如下:

[java] view plain copy
  1. public void setup(Launcher launcher) {  
  2.         mLauncher = launcher;  
  3.         setOnKeyListener(new HotseatIconKeyEventListener());  
  4.     }  
方法调用之后,Hotseat持有Launcher对象的引用,并且用HotseatIconKeyEvenListener对自身的按键进行监听,进入HotseatIconKeyEvenListener可以看到:

[java] view plain copy
  1. class HotseatIconKeyEventListener implements View.OnKeyListener {  
  2.     public boolean onKey(View v, int keyCode, KeyEvent event) {  
  3.         final Configuration configuration = v.getResources().getConfiguration();  
  4.         return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);  
  5.     }  
  6. }  

调用方法handleHotseatButtonKeyEvent()来处理相应的事件:

[java] view plain copy
  1. static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {  
  2.         ...  
  3.         switch (keyCode) {  
  4.             case KeyEvent.KEYCODE_DPAD_LEFT:  
  5.                 ...  
  6.                 break;  
  7.             case KeyEvent.KEYCODE_DPAD_RIGHT:  
  8.                 ...  
  9.                 break;  
  10.             case KeyEvent.KEYCODE_DPAD_UP:  
  11.                 ...  
  12.                 break;  
  13.             case KeyEvent.KEYCODE_DPAD_DOWN:  
  14.                 ...  
  15.                 break;  
  16.             defaultbreak;  
  17.         }  
  18.         return wasHandled;  
  19.     }  

handleHotseatButtonKeyEvent()方法中根据当前的方向,对KeyEvent.KEYCODE_DPAD_LEFT、KeyEvent.KEYCODE_DPAD_RIGHT、

KeyEvent.KEYCODE_DPAD_UPKeyEvent.KEYCODE_DPAD_DOWN即可能存在(如果手机有实体按键)的导航按钮上、下、左、右进行响应。这样Hotseat

的初始化工作就完成了。

3、Workspace的初始化

先调用setHapticFeedbackEnabled(false),使其在触摸的时候没有触感反馈。接着设置长按事件的监听setOnLongClickListener(this),Launcher实现了

OnLongClickListener接口,看看Launcher中是如何进行响应的:

[java] view plain copy
  1. public boolean onLongClick(View v) {  
  2.         ...  
  3.         if (!(v instanceof CellLayout)) {  
  4.             v = (View) v.getParent().getParent();  
  5.         }  
  6.         ...  
  7.         CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();  
  8.         ..  
  9.         // The hotseat touch handling does not go through Workspace, and we always allow long press  
  10.         // on hotseat items.  
  11.         final View itemUnderLongClick = longClickCellInfo.cell;  
  12.         boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();  
  13.         if (allowLongPress && !mDragController.isDragging()) {  
  14.             if (itemUnderLongClick == null) {  
  15.                 // 在空的空间上长按时  
  16.                 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,  
  17.                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);  
  18.                 startWallpaper();  
  19.             } else {  
  20.                 ...  
  21.             }  
  22.         }  
  23.         return true;  
  24.     }  
这里我们只关心与Workspace的长按事件相关的内容,当Workspace发生长按事件时,产生触感反馈,同时调用startWallpaper进行壁纸的设置:
[java] view plain copy
  1. private void startWallpaper() {  
  2.         showWorkspace(true);  
  3.         final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);  
  4.         Intent chooser = Intent.createChooser(pickWallpaper,  
  5.                 getText(R.string.chooser_wallpaper));  
  6.         startActivityForResult(chooser, REQUEST_PICK_WALLPAPER);  
  7.     }  

showWorkspace(true)的作用是不管当前的Launcher处于什么状态,都跳转到显示Workspace的状态,并且带有动画过渡。而后面几段代码的作用就是弹出

Dialog,包含了所有能够响应ACTOIN_SET_WALLPAPER的action的Activity。然后我们就可以选择一个来设置比壁纸了。接着就是调用Workspace.setup():

[java] view plain copy
  1. void setup(DragController dragController) {  
  2.         mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);  
  3.         mDragController = dragController;  
  4.   
  5.         // hardware layers on children are enabled on startup, but should be disabled until  
  6.         // needed  
  7.         updateChildrenLayersEnabled();  
  8.         setWallpaperDimension();  
  9.     }  

代码中先创建了一个SpringLoadedDragController的对象,这个类的作用控制当Launcher处于State.APPS_CUSTOMIZE_SPRING_LOADED状态时,即处于缩小状

态时,提供控制Launcher进行滑动、放置item的操作。接着Workspace的成员变量mDragController获取了DragController对象的引用。随后,调用

updateChildrenLayersEnabled (),注释中的意思是当子view在创建的时候会开启硬件层,其它时候关闭。其中调用了内部的API这里就不过多追究了。最后,调用

setWallpaperDimension()设置Wallpaper的尺寸。下面还有一步操作调用dragController.addDragListener(mWorkspace)方法,Workspace实现了DragListener:

[java] view plain copy
  1. interface DragListener {  
  2.        void onDragStart(DragSource source, Object info, int dragAction);  
  3.        void onDragEnd();  
  4.    }  
这样mWorkspace就能够响应拖拽事件了,具体响应内容将在后面的文章中进行分析。

这样mWorkspace的初始化就算完成了,主要完成了两件事情:1、设置了对长按事件的处理,2、对拖拽事件的处理

4、AppsCustomizeTabHost、AppsCustomizePagedView

AppsCustomizePagedView是内嵌在AppsCustomizeTabHost中的组件,在当点击AllApp按钮是,会跳转到AppsCustomizeTabHost中,而在

AppsCustomizePagedView装载Icon。初始化时调用AppCustomizedPagedView.setup()方法:

[java] view plain copy
  1. public void setup(Launcher launcher, DragController dragController) {  
  2.         mLauncher = launcher;  
  3.         mDragController = dragController;  
  4.     }  
获取Launcher与DragController对象的引用。

5、DragController

DragController类主要的工作就是处理拖拽事件,对其进行初始化时分别调用了四个方法dragController.setDragScoller(mWorkspace);dragController.setScrollView(mDragLayer);

dragController.setMoveTarget(mWorkspace);dragController.addDropTarget(mWorkspace);那分别看看这四个方法中具体都做了什么:

[java] view plain copy
  1. public void setDragScoller(DragScroller scroller) {  
  2.         mDragScroller = scroller;  
  3.     }  

首先我想吐槽下,方法名应该时在敲代码的时候拼错了,正常情况应该是setDragScroller()~~~~~。Workspace实现了DragScroller接口,代表了Workspace

可以进行滑动操作。通过此方法获取到了DragScroller对象。接着又调用了DragController.setScrollView()

[java] view plain copy
  1. /** 
  2.  * Set which view scrolls for touch events near the edge of the screen. 
  3.  */  
  4. public void setScrollView(View v) {  
  5.     mScrollView = v;  
  6. }  

从提供的代码注释理解,这个方法设置了当屏幕的边缘触摸滑动时,所滚动的View。(目前还不清楚具体所指的对象)
[java] view plain copy
  1. /** 
  2.  * Sets the view that should handle move events. 
  3.  */  
  4. void setMoveTarget(View view) {  
  5.     mMoveTarget = view;  
  6. }      
设置应该处理移动事件的View,传入的对象是Workspace。
[java] view plain copy
  1. /** 
  2.  * Add a DropTarget to the list of potential places to receive drop events. 
  3.  */  
  4. public void addDropTarget(DropTarget target) {  
  5.     mDropTargets.add(target);  
  6. }  

将Workspace对象作为DropTarget对象添加到mDropTargets中。其中DropTarget接口的定义了一个能够接收拖曳对象的类。当桌面的item被拖拽后,需要找到下一

个容纳它的容器,而这个容器就一个DropTarget。

6、SearchDropTargetBar

SearchDropTargetBar管理着搜索框和删除框的转换,正常情况下它是一个searchBar,当图标被拖拽时,它就变成了deleteDropTargetBar,将图标拖放到上面松手就可以将其从Workspace中删除。

[java] view plain copy
  1. public void setup(Launcher launcher, DragController dragController) {  
  2.         dragController.addDragListener(this);  
  3.         dragController.addDragListener(mInfoDropTarget);  
  4.         dragController.addDragListener(mDeleteDropTarget);  
  5.         dragController.addDropTarget(mInfoDropTarget);  
  6.         dragController.addDropTarget(mDeleteDropTarget);  
  7.         mInfoDropTarget.setLauncher(launcher);  
  8.         mDeleteDropTarget.setLauncher(launcher);  
  9.     }  
setup中执行的内容比较繁杂,这里不作详细的分析。

这样setupViews()执行完毕。继续回到onCreate()方法中分析。

Step5:showFirstRunWorkspaceCling()

showFirstRunWorkspaceCling()方法调用,在应用第一次被启动的时候,此方法会被调用,用于向用户展示一个指导界面。以后都不会再出现。

Step6:registerContentObservers()

registerContentObservers()注册对指定URI所指定的数据的监听,及时对数据变化做出反应。

Step7:LauncherModel.startLoader()

在应用启动的时候需要加载数据,LauncherModel.startLoader()就完成了这个任务。加载过程的基本流程如下:

Android 4.0 Launcher2源码分析——启动过程分析_第3张图片

通过上面的时序图,对加载的流程基本有了认识。调用LauncherModel.startLoader()开始加载内容,内容加载完之后,通过LauncherModel.Callbacks接口定义的回

调方法,将数据返回给需要的对象。而Launcher实现了这个接口,数据将回传给Launcher。了解了基本过程之后,开始进入加载过程。

[java] view plain copy
  1. public void startLoader(Context context, boolean isLaunching) {  
  2.         synchronized (mLock) {  
  3.             ......  
  4.             // Don't bother to start the thread if we know it's not going to do anything  
  5.             if (mCallbacks != null && mCallbacks.get() != null) {  
  6.                 ......  
  7.                 mLoaderTask = new LoaderTask(context, isLaunching);  
  8.                 sWorkerThread.setPriority(Thread.NORM_PRIORITY);  
  9.                 sWorker.post(mLoaderTask);  
  10.             }  
  11.         }  
  12.     }  

方法中,创建了一个实现了Runnable接口的LoaderTask类的对象mLoaderTask,mWork是一个Handler,调用mWork.post()将mLoaderTask添加到消息队列中。最

后mLoaderTask中的run方法就会得到执行

[java] view plain copy
  1. public void run() {  
  2.             ......  
  3.             keep_running: {  
  4.                 ......  
  5.                 if (loadWorkspaceFirst) {  
  6.                     ......  
  7.                     loadAndBindWorkspace();  
  8.                 } else {  
  9.                     ......  
  10.                 }  
  11.   
  12.                 if (mStopped) {  
  13.                     break keep_running;  
  14.                 }  
  15.   
  16.                 ......  
  17.                 waitForIdle();  
  18.   
  19.                 // second step  
  20.                 if (loadWorkspaceFirst) {  
  21.                     ......  
  22.                     loadAndBindAllApps();  
  23.                 } else {  
  24.                     ......  
  25.                 }  
  26.                 ......  
  27.             }  
  28.             ......  
  29.         }  

如果是初次启动,则loadWorkspaceFirst=true,loadAndBindWorkspace被调用,此时Workspace中的内容项将被加载并且绑定显示到Workspace中。当

Workspace中的内容加载之后,调用waitForIdle方法,以等待加载结束。确认完成之后紧接着loadAndBindAllApps()方法执行,在这个方法中将加载AllApps页面的

内容。这样加载过程就分成了两个部分:1、loadAndBindWorkspace()加载Workspace内容。2、loadAndBindAllApps()加载AllApps中的内容。这部分内容本文暂

不作深入的分析。

随着startLoader()的过程执行完毕,Launcher的初始化过程就基本上结束了。启动过程是很繁琐的,因为所有应用中需要使用到的组件都可能在启动的时候

进行配置,等到从具体的功能入手的时候,就能够更加清楚启动过程所做的操作的意义。

你可能感兴趣的:(android)