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文件):
-
<activity
-
android:name="com.android.launcher2.Launcher"
-
android:launchMode="singleTask"
-
android:clearTaskOnLaunch="true"
-
android:stateNotNeeded="true"
-
android:theme="@style/Theme"
-
android:configChanges="mcc|mnc"
-
android:windowSoftInputMode="adjustPan"
-
android:screenOrientation="nosensor">
-
<intent-filter>
-
<action android:name="android.intent.action.MAIN" />
-
<category android:name="android.intent.category.HOME" />
-
<category android:name="android.intent.category.DEFAULT" />
-
<category android:name="android.intent.category.MONKEY"/>
-
</intent-filter>
-
</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:
-
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
-
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
接着從mContext變量中獲得PackageManagerService的接口:
-
final PackageManager packageManager = mContext.getPackageManager();
下一步就是通過這個PackageManagerService.queryIntentActivities接口來查詢所有Action
類型爲Intent.ACTION_MAIN,並且Category類型爲Intent.CATEGORY_LAUNCHER的Activity了。
-
final PackageManager packageManager = mContext.getPackageManager();
-
-
List<ResolveInfo> apps = null;
-
-
...
-
...
-
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了吧??
-
for (int j=0; i<N && j<batchSize; j++) {
-
-
mBgAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
-
mIconCache, mLabelCache));
-
i++;
-
-
}
-
final ArrayList<ApplicationInfo> added = mBgAllAppsList.added;
-
mBgAllAppsList.added = new ArrayList<ApplicationInfo>();
-
if (first) {
-
callbacks.bindAllApplications(added);
-
} else {
-
callbacks.bindAppsAdded(added);
-
}
各個app的入口activity信息將會被用於構造ApplicationInfo對象。上面的new ApplicationInfo
通過調用構造函數,將icon設置。
-
public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
-
HashMap<Object, CharSequence> labelCache) {
-
final String packageName = info.activityInfo.applicationInfo.packageName;
-
-
this.componentName = new ComponentName(packageName, info.activityInfo.name);
-
this.container = ItemInfo.NO_ID;
-
this.setActivity(componentName,
-
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-
-
try {
-
int appFlags = pm.getApplicationInfo(packageName, 0).flags;
-
if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
-
flags |= DOWNLOADED_FLAG;
-
-
if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
-
flags |= UPDATED_SYSTEM_APP_FLAG;
-
}
-
}
-
firstInstallTime = pm.getPackageInfo(packageName, 0).firstInstallTime;
-
} catch (NameNotFoundException e) {
-
Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);
-
}
-
-
iconCache.getTitleAndIcon(this, info, labelCache);
-
}
-
public void getTitleAndIcon(ApplicationInfo application, ResolveInfo info,
-
HashMap<Object, CharSequence> labelCache) {
-
synchronized (mCache) {
-
CacheEntry entry = cacheLocked(application.componentName, info, labelCache);
-
-
application.title = entry.title;
-
application.iconBitmap = entry.icon;
-
}
-
}
-
private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,
-
HashMap<Object, CharSequence> labelCache) {
-
if (LauncherLog.DEBUG_LAYOUT) {
-
LauncherLog.d(TAG, "cacheLocked: componentName = " + componentName
-
+ ", info = " + info + ", HashMap<Object, CharSequence>:size = "
-
+ ((labelCache == null) ? "null" : labelCache.size()));
-
}
-
-
CacheEntry entry = mCache.get(componentName);
-
if (entry == null) {
-
entry = new CacheEntry();
-
-
mCache.put(componentName, entry);
-
-
ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
-
if (labelCache != null && labelCache.containsKey(key)) {
-
entry.title = labelCache.get(key).toString();
-
if (LauncherModel.DEBUG_LOADERS) {
-
LauncherLog.d(TAG, "CacheLocked get title from cache: title = " + entry.title);
-
}
-
} else {
-
entry.title = info.loadLabel(mPackageManager).toString();
-
if (LauncherModel.DEBUG_LOADERS) {
-
LauncherLog.d(TAG, "CacheLocked get title from pms: title = " + entry.title);
-
}
-
if (labelCache != null) {
-
labelCache.put(key, entry.title);
-
}
-
}
-
if (entry.title == null) {
-
entry.title = info.activityInfo.name;
-
if (LauncherModel.DEBUG_LOADERS) {
-
LauncherLog.d(TAG, "CacheLocked get title from activity information: entry.title = " + entry.title);
-
}
-
}
-
-
entry.icon = Utilities.createIconBitmap(
-
getFullResIcon(info), mContext);
-
}
-
return entry;
-
}
看來是通過在cacheLocked裏調用這個createIconBitmap實現的啊。
有了這些ApplicationInfo實例之後,就可以在桌面上展示系統中所有的應用程序了。當我們點擊
“HOME"按鍵的時候,各個應用圖標就會被顯示。現在我們來看看Launcher::onClick的處理流程:
-
public void onClick(View v) {
-
...
-
...
-
-
if (tag instanceof ShortcutInfo) {
-
-
final Intent intent = ((ShortcutInfo) tag).intent;
-
int[] pos = new int[2];
-
v.getLocationOnScreen(pos);
-
intent.setSourceBounds(new Rect(pos[0], pos[1],
-
pos[0] + v.getWidth(), pos[1] + v.getHeight()));
-
-
boolean success = startActivitySafely(v, intent, tag);
-
-
if (success && v instanceof BubbleTextView) {
-
mWaitingForResume = (BubbleTextView) v;
-
mWaitingForResume.setStayPressed(true);
-
}
-
} else if (tag instanceof FolderInfo) {
-
if (v instanceof FolderIcon) {
-
FolderIcon fi = (FolderIcon) v;
-
handleFolderClick(fi);
-
}
-
} else if (v == mAllAppsButton) {
-
if (isAllAppsVisible()) {
-
showWorkspace(true);
-
} else {
-
onClickAllAppsButton(v);
-
}
-
}
這裏我們點擊的是HOME按鍵,對應的是 v == mAllAppsButton這個case。且看onClickAllAppsButton :
-
public void onClickAllAppsButton(View v) {
-
showAllApps(true);
-
}
-
void showAllApps(boolean animated) {
-
...
-
...
-
-
mWorkspace.startCovered(mWorkspace.getCurrentPage());
-
showAppsCustomizeHelper(animated, false);
-
mAppsCustomizeTabHost.requestFocus();
-
-
...
-
...
-
}
裏面具體怎麼畫出來的,我還真不清楚。只能幫大家引路到這裏了。
當我們點擊應用程序圖標的時候,執行的是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)的啓動過程源代碼分析