Android系統默認Home(Launcher)的啓動過程小結

http://blog.csdn.net/happy08god/article/details/24265167


      Android系統開機,各個應用是如何加載並被顯示到桌面上的呢?帶着這份好奇,閱讀了在

Android應用程序安裝過程源代碼分析 一文中,我們看到應用程序的apk歸檔文件中的配置文件

AndroidManifest.xml 會被解析,解析得到的application,service和activity等信息保存在

PackageManagerService中。


      但是我們進入HOME界面,是要看到各個android app的快捷圖標和名稱的。顯示app的這些信息,

就是我們的HOME,也就是Launcher乾的事情了。代碼流程是從SystemServer 開始的,調用棧爲:

ServerThread::run ( SystemServer.Java) ——>  ActivityManagerService::main (ActivityManagerService.java)

——> ActivityManagerService:: startRunning ——> ActivityManagerService::systemReady ——> 

ActivityStack::resumeTopActivityLocked ——> ActivityManagerService::startHomeActivityLocked

 其中startHomeActivityLocked函數首先創建一個CATEGORY_HOME類型的Intent,然後通過

Intent.resolveActivityInfo函數向PackageManagerService查詢Category類型爲HOME的Activity。

這裏只有系統自帶的Launcher應用程序註冊了HOME類型的Activity

(見packages/apps/Launcher2/AndroidManifest.xml文件):


[html] view plain copy
  1. <activity  
  2.             android:name="com.android.launcher2.Launcher"  
  3.             android:launchMode="singleTask"  
  4.             android:clearTaskOnLaunch="true"  
  5.             android:stateNotNeeded="true"  
  6.             android:theme="@style/Theme"  
  7.             android:configChanges="mcc|mnc"  
  8.             android:windowSoftInputMode="adjustPan"  
  9.             android:screenOrientation="nosensor">  
  10.             <intent-filter>  
  11.                 <action android:name="android.intent.action.MAIN" />  
  12.                 <category android:name="android.intent.category.HOME" />  
  13.                 <category android:name="android.intent.category.DEFAULT" />  
  14.                 <category android:name="android.intent.category.MONKEY"/>  
  15.             </intent-filter>  
  16. </activity>  

      最終,com.android.launcher2.Launcher被啓動起來,其onCreate函數被調用。具體可參考

Android應用程序啓動過程源代碼分析  一文。 在activity start流程中,performLaunchActivity

調用。裏面的mInstrumentation.callActivityOnCreate(activity, r.state); 調用的就是Instrumentation

類的callActivityOnCreate方法。調用堆棧爲:ActivityThread::performLaunchActivity ——> 

Instrumentation::callActivityOnCreate ——>  Activity::performCreate ——> onCreate(icicle); 

最後這個就是創建的Launcher 這個Activity覆蓋的onCreate方法。至此,Launcher.onCreate

被調用了。接下來的調用流程爲:Launcher.onCreate ——> LauncherModel.startLoader ——> 

LoaderTask.run ——> LoaderTask.loadAndBindAllApps ——> LoaderTask.loadAllAppsByBatch

 

      函數首先構造一個CATEGORY_LAUNCHER類型的Intent:

[java] view plain copy
  1. final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);  
  2. mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);  

      接着從mContext變量中獲得PackageManagerService的接口:

[java] view plain copy
  1. final PackageManager packageManager = mContext.getPackageManager();  

       下一步就是通過這個PackageManagerService.queryIntentActivities接口來查詢所有Action

類型爲Intent.ACTION_MAIN,並且Category類型爲Intent.CATEGORY_LAUNCHER的Activity了。

[java] view plain copy
  1. final PackageManager packageManager = mContext.getPackageManager();  
  2.   
  3. List<ResolveInfo> apps = null;  
  4.   
  5. ...  
  6. ...  
  7. apps = packageManager.queryIntentActivities(mainIntent, 0);  

      PackageManagerService會把系統中的應用程序都解析一遍,然後把解析得到的Activity都保存在

mActivities變量中,這裏通過這個mActivities變量的queryIntent函數返回符合條件intent的Activity,這裏

返回的便是Action類型爲Intent.ACTION_MAIN,並且Category類型爲Intent.CATEGORY_LAUNCHER

的Activity了。

   終於知道我們自己寫的app的入口activity爲啥要設置這樣的action和Category了吧??     

[java] view plain copy
  1. for (int j=0; i<N && j<batchSize; j++) {  
  2.      // This builds the icon bitmaps.  
  3.      mBgAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),  
  4.      mIconCache, mLabelCache));  
  5.      i++;  
  6.    
  7. }  
[java] view plain copy
  1. final ArrayList<ApplicationInfo> added = mBgAllAppsList.added;  
  2. mBgAllAppsList.added = new ArrayList<ApplicationInfo>();  
[java] view plain copy
  1.  if (first) {  
  2.      callbacks.bindAllApplications(added);  
  3. else {  
  4.       callbacks.bindAppsAdded(added);  
  5. }  
     各個app的入口activity信息將會被用於構造ApplicationInfo對象。上面的new ApplicationInfo

通過調用構造函數,將icon設置。

[java] view plain copy
  1. public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,  
  2.            HashMap<Object, CharSequence> labelCache) {  
  3.        final String packageName = info.activityInfo.applicationInfo.packageName;  
  4.   
  5.        this.componentName = new ComponentName(packageName, info.activityInfo.name);  
  6.        this.container = ItemInfo.NO_ID;  
  7.        this.setActivity(componentName,  
  8.                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);  
  9.   
  10.        try {  
  11.            int appFlags = pm.getApplicationInfo(packageName, 0).flags;  
  12.            if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {  
  13.                flags |= DOWNLOADED_FLAG;  
  14.   
  15.                if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {  
  16.                    flags |= UPDATED_SYSTEM_APP_FLAG;  
  17.                }  
  18.            }  
  19.            firstInstallTime = pm.getPackageInfo(packageName, 0).firstInstallTime;  
  20.        } catch (NameNotFoundException e) {  
  21.            Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);  
  22.        }  
  23.   
  24.        iconCache.getTitleAndIcon(this, info, labelCache);  
  25.    }  

[java] view plain copy
  1. public void getTitleAndIcon(ApplicationInfo application, ResolveInfo info,  
  2.            HashMap<Object, CharSequence> labelCache) {  
  3.        synchronized (mCache) {  
  4.            CacheEntry entry = cacheLocked(application.componentName, info, labelCache);  
  5.   
  6.            application.title = entry.title;  
  7.            application.iconBitmap = entry.icon;  
  8.        }  
  9.    }  

[java] view plain copy
  1. private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,  
  2.            HashMap<Object, CharSequence> labelCache) {  
  3.        if (LauncherLog.DEBUG_LAYOUT) {  
  4.            LauncherLog.d(TAG, "cacheLocked: componentName = " + componentName  
  5.                    + ", info = " + info + ", HashMap<Object, CharSequence>:size = "  
  6.                    +  ((labelCache == null) ? "null" : labelCache.size()));  
  7.        }  
  8.   
  9.        CacheEntry entry = mCache.get(componentName);  
  10.        if (entry == null) {  
  11.            entry = new CacheEntry();  
  12.   
  13.            mCache.put(componentName, entry);  
  14.   
  15.            ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);  
  16.            if (labelCache != null && labelCache.containsKey(key)) {  
  17.                entry.title = labelCache.get(key).toString();  
  18.                if (LauncherModel.DEBUG_LOADERS) {  
  19.                    LauncherLog.d(TAG, "CacheLocked get title from cache: title = " + entry.title);  
  20.                }                  
  21.            } else {  
  22.                entry.title = info.loadLabel(mPackageManager).toString();  
  23.                if (LauncherModel.DEBUG_LOADERS) {  
  24.                    LauncherLog.d(TAG, "CacheLocked get title from pms: title = " + entry.title);  
  25.                }                  
  26.                if (labelCache != null) {  
  27.                    labelCache.put(key, entry.title);  
  28.                }  
  29.            }  
  30.            if (entry.title == null) {  
  31.                entry.title = info.activityInfo.name;  
  32.                if (LauncherModel.DEBUG_LOADERS) {  
  33.                    LauncherLog.d(TAG, "CacheLocked get title from activity information: entry.title = " + entry.title);  
  34.                }  
  35.            }  
  36.   
  37.            entry.icon = Utilities.createIconBitmap(  
  38.                    getFullResIcon(info), mContext);  
  39.        }  
  40.        return entry;  
  41.    }  
       看來是通過在cacheLocked裏調用這個createIconBitmap實現的啊。

    有了這些ApplicationInfo實例之後,就可以在桌面上展示系統中所有的應用程序了。當我們點擊

“HOME"按鍵的時候,各個應用圖標就會被顯示。現在我們來看看Launcher::onClick的處理流程:

[java] view plain copy
  1. public void onClick(View v) {  
  2. ...  
  3. ...  
  4.   
  5. if (tag instanceof ShortcutInfo) {  
  6.             // Open shortcut  
  7.             final Intent intent = ((ShortcutInfo) tag).intent;  
  8.             int[] pos = new int[2];  
  9.             v.getLocationOnScreen(pos);  
  10.             intent.setSourceBounds(new Rect(pos[0], pos[1],  
  11.                     pos[0] + v.getWidth(), pos[1] + v.getHeight()));  
  12.   
  13.             boolean success = startActivitySafely(v, intent, tag);  
  14.   
  15.             if (success && v instanceof BubbleTextView) {  
  16.                 mWaitingForResume = (BubbleTextView) v;  
  17.                 mWaitingForResume.setStayPressed(true);  
  18.             }  
  19.         } else if (tag instanceof FolderInfo) {  
  20.             if (v instanceof FolderIcon) {  
  21.                 FolderIcon fi = (FolderIcon) v;  
  22.                 handleFolderClick(fi);  
  23.             }  
  24.         } else if (v == mAllAppsButton) {  
  25.             if (isAllAppsVisible()) {  
  26.                 showWorkspace(true);  
  27.             } else {  
  28.                 onClickAllAppsButton(v);  
  29.             }  
  30. }  

      這裏我們點擊的是HOME按鍵,對應的是 v == mAllAppsButton這個case。且看onClickAllAppsButton :

[java] view plain copy
  1. public void onClickAllAppsButton(View v) {  
  2.     showAllApps(true);  
  3. }  
[java] view plain copy
  1. void showAllApps(boolean animated) {  
  2. ...  
  3. ...  
  4.  /// M: Call the appropriate callback for the IMTKWidget on the current page when enter all apps list.  
  5.         mWorkspace.startCovered(mWorkspace.getCurrentPage());  
  6.         showAppsCustomizeHelper(animated, false);  
  7.         mAppsCustomizeTabHost.requestFocus();  
  8.   
  9. ...  
  10. ...  
  11. }  
     裏面具體怎麼畫出來的,我還真不清楚。只能幫大家引路到這裏了。

     當我們點擊應用程序圖標的時候,執行的是tag instanceof ShortcutInfo這個case。最終通過調用

 final Intent intent = ((ShortcutInfo) tag).intent; 和 boolean success = startActivitySafely(v, intent, tag);

來啓動對應app的入口activity。


    Launcher的流程暫且分析到這裏。我們回過頭來看,總共有

(1)PackageManagerService解析app的AndroidManifest.xml。PackageManagerService將應用程序

的apk歸檔文件中的配置文件AndroidManifest.xml 解析,得到的application,service和activity等信息

保存在PackageManagerService中。

(2)啓動Launcher這個app的入口activity,調用其onCreate方法。調用startHomeActivityLocked流程中,

向PackageManagerService查詢Category類型爲HOME的Activity,發現只有Launcher;接着進入

startActivity的流程。在performLaunchActivity調用Instrumentation類的callActivityOnCreate方法。

最後,調用到Launcher 這個app對應的onCreate方法。

(3)構造每個app的入口activity信息對應的ApplicationInfo對象,設置應用程序圖標。

Launcher.onCreate調用流程中,通過調用PackageManagerService.queryIntentActivities接口來查詢

所有Action類型爲Intent.ACTION_MAIN,並且Category類型爲Intent.CATEGORY_LAUNCHER的Activity。

(4)Launcher::onClick中調用onClickAllAppsButton來顯示佈滿app的頁面(HOME)。

(5)點擊應用程序圖標時,在Launcher::onClick中調用startActivitySafely啓動該應用的入口activity。

    

    要是給咱們自己整個簡單的Launcher,只需要保存各個app配置文件AndroidManifest.xml 的各個

重要信息(例如入口activity),然後通過讀取配置文件,將應用程序的圖標和名稱讀出來保存起來,

當響應HOME按鍵時,畫出各個應用程序圖標和名稱等信息。當點擊應用程序圖標時,獲取其

入口Activity等信息,調用startActivity等函數去啓動入口Activity。



  更多源碼分析,請參考:Android系統默認Home應用程序(Launcher)的啓動過程源代碼分析



發佈了23 篇原創文章 · 獲贊 39 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章