主目錄見:Android高級進階知識(這是總目錄索引)
Launcher3源碼地址:Launcher3-master
[This tutorial was written by Ticoo]
上文Launcher3 桌面加載流程分析(上),我們看到
LauncherModel 創建LoaderTask加載數據,我們繼續往下看
LoaderTask
創建LoaderTask,flags為 PagedView.INVALID_RESTORE_PAGE值-1001, 我們看它的run方法是如何執行的。
private class LoaderTask implements Runnable {
LoaderTask(Context context, int flags) {
mContext = context;
mFlags = flags;
}
public void run() {
...
keep_running: {
if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
loadAndBindWorkspace();
if (mStopped) {
break keep_running;
}
waitForIdle();
// second step
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
loadAndBindAllApps();
}
...
}
}
根據mStopped的狀態做一些預先的判斷外,最先執行的是 loadAndBindWorkspace()方法,加載和綁定Workspace的數據,包括屏幕數,應用數據,widget組件信息等等,然后調用waitForIdle() 等待loadAndBindWorkspace()里創建的一些子線程執行完,修改mStopped和mLoadAndBindStepFinished的狀態后執行loadAndBindAllApps(),加載所有應用,完成整個加載應用的流程。
整體流程就是上面的run方法,具體的細節我們一步步來看。
Workspace
Workspace是什么呢?大家自己看下Launcher3的主布局文件launcher.xml布局就很明了,workspace是Launcher的工作臺,承載應用數據,widget組件數據,文件夾數據以及其他的功能。
加載workspace的流程分兩步,
- 加載數據,loadWorkspace()
- 綁定workspace, bindWorkspace()
流程如下
private void loadAndBindWorkspace() {
mIsLoadingAndBindingWorkspace = true;
// Load the workspace
if (DEBUG_LOADERS) {
Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
}
if (!mWorkspaceLoaded) {
loadWorkspace();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
mWorkspaceLoaded = true;
}
}
// Bind the workspace
bindWorkspace(-1);
}
加載WorkSpace數據
加載WorkSpace數據的方法都在loadWorkSpace()里, 這個步驟是整個流程最核心的,雖然只有loadWorkSpace()這個方法,但是我目前的這個版本該方法的源碼就達到600多行,所以我們截取核心的代碼來分析,很多細節還是要大家自己去琢磨。
final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
final Context context = mContext;
final ContentResolver contentResolver = context.getContentResolver();
final PackageManager manager = context.getPackageManager();
final boolean isSafeMode = manager.isSafeMode();
final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
final boolean isSdCardReady = context.registerReceiver(null,
new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
LauncherAppState app = LauncherAppState.getInstance();
InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
int countX = profile.numColumns;
int countY = profile.numRows;
if (GridSizeMigrationTask.ENABLED &&
!GridSizeMigrationTask.migrateGridIfNeeded(mContext)) {
// Migration failed. Clear workspace.
mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE;
}
if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
LauncherAppState.getLauncherProvider().deleteDatabase();
}
if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
// append the user's Launcher2 shortcuts
Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
} else {
// Make sure the default workspace is loaded
Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
}
首先聲明了一些核心對象,ContentResolver,LauncherAppsCompat, LauncherAppState,InvariantDeviceProfile這些上一篇已經有介紹過就不再贅述。
前幾個if條件,是關于數據庫移植的,比如Lacuncher2,升級到Launcher3,桌面圖標大小發生變化的特殊場合處理,不是我們需要特別留意的。
加載WorkSpace資源文件
關鍵的代碼是 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(); 在首次打開Launcher時,會加載默認的數據,比如桌面首頁顯示什么內容,hotseat配置等等??碙auncherAppState代碼發現調用的是 LauncherProvider的loadDefaultFavoritesIfNecessary方法
/**
* Loads the default workspace based on the following priority scheme:
* 1) From the app restrictions
* 2) From a package provided by play store
* 3) From a partner configuration APK, already in the system image
* 4) The default configuration for the particular device
*/
synchronized public void loadDefaultFavoritesIfNecessary() {
SharedPreferences sp = Utilities.getPrefs(getContext());
if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
Log.d(TAG, "loading default workspace");
AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction();
if (loader == null) {
loader = AutoInstallsLayout.get(getContext(),
mOpenHelper.mAppWidgetHost, mOpenHelper);
}
if (loader == null) {
final Partner partner = Partner.get(getContext().getPackageManager());
if (partner != null && partner.hasDefaultLayout()) {
final Resources partnerRes = partner.getResources();
int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
"xml", partner.getPackageName());
if (workspaceResId != 0) {
loader = new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost,
mOpenHelper, partnerRes, workspaceResId);
}
}
}
final boolean usingExternallyProvidedLayout = loader != null;
if (loader == null) {
loader = getDefaultLayoutParser();
}
// There might be some partially restored DB items, due to buggy restore logic in
// previous versions of launcher.
createEmptyDB();
// Populate favorites table with initial favorites
if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
&& usingExternallyProvidedLayout) {
// Unable to load external layout. Cleanup and load the internal layout.
createEmptyDB();
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
getDefaultLayoutParser());
}
clearFlagEmptyDbCreated();
}
}
正常流程不會都執行,故簡單介紹一下,該方法跟注釋一樣,會從以下幾種方式中的一種加載默認布局
- 應用約束,調用createWorkspaceLoaderFromAppRestriction,獲取用戶設置的一組用于限制應用功能的Bundle串,獲取Bundle里workspace.configuration.package.name具體的應用包名,獲取WorkSpace默認配置資源。
- 從帶有 android.autoinstalls.config.action.PLAY_AUTO_INSTALL Action的應用里獲取workspace默認配置資源
- 從系統內置的partner應用里獲取workspace默認配置
- 調用getDefaultLayoutParser() 獲取我們Launcher里的默認資源
默認流程,會執行第四步, 然后創建數據庫,建表favorites和workspaceScreens,加載數據mOpenHelper.loadFavorites
private DefaultLayoutParser getDefaultLayoutParser() {
int defaultLayout = LauncherAppState.getInstance()
.getInvariantDeviceProfile().defaultLayoutId;
return new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost,
mOpenHelper, getContext().getResources(), defaultLayout);
}
而默認的資源就是我們配置 InvariantDeviceProfile的資源如R.xml.default_workspace_5x6,詳細見上一篇文章。故我們可以在res/xml/里修改我們的默認顯示應用的配置。
@Thunk
int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
ArrayList<Long> screenIds = new ArrayList<Long>();
// TODO: Use multiple loaders with fall-back and transaction.
int count = loader.loadLayout(db, screenIds);
// Add the screens specified by the items above
Collections.sort(screenIds);
int rank = 0;
ContentValues values = new ContentValues();
for (Long id : screenIds) {
values.clear();
values.put(LauncherSettings.WorkspaceScreens._ID, id);
values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS,
null, values) < 0) {
throw new RuntimeException("Failed initialize screen table"
+ "from default layout");
}
rank++;
}
// Ensure that the max ids are initialized
mMaxItemId = initializeMaxItemId(db);
mMaxScreenId = initializeMaxScreenId(db);
return count;
}
loadFavorites方法里調用DefaultLayoutParser.loadLayout(db, screenIds) 解析布局xml里的文件夾信息,應用信息,widget信息等等保存到數據庫, 并獲取到屏幕id集合,保存到workspaceScreens表中。至于怎么解析的,我們直接看關鍵的代碼
AutoInstallsLayout.java
/**
* Parses the layout and returns the number of elements added on the homescreen.
*/
protected int parseLayout(int layoutId, ArrayList<Long> screenIds)
throws XmlPullParserException, IOException {
XmlResourceParser parser = mSourceRes.getXml(layoutId);
beginDocument(parser, mRootTag);
final int depth = parser.getDepth();
int type;
HashMap<String, TagParser> tagParserMap = getLayoutElementsMap();
int count = 0;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
count += parseAndAddNode(parser, tagParserMap, screenIds);
}
return count;
}
通過XmlResourceParser 解析xml文件,獲取我們需要的配置。不同的標簽通過不同的解析對象處理,我們使用的是AutoInstallsLayout的子類DefaultLayoutParser,在getLayoutElementsMap()方法里,我們可以看到使用的處理對象,如應用解析器ResolveParser,文件夾解析器MyFolderParser等等,解析到信息后會保存到對應的數據庫中。這就是加載默認workspace的原理,解析的細節就不一一介紹了,請大家自己找到需要的解析器琢磨代碼了哦
DefaultLayoutParser.java
@Override
protected HashMap<String, TagParser> getLayoutElementsMap() {
HashMap<String, TagParser> parsers = new HashMap<String, TagParser>();
parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
parsers.put(TAG_APPWIDGET, new AppWidgetParser());
parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
parsers.put(TAG_RESOLVE, new ResolveParser());
parsers.put(TAG_FOLDER, new MyFolderParser());
parsers.put(TAG_PARTNER_FOLDER, new PartnerFolderParser());
return parsers;
}
我們簡單的介紹一個默認配置,
default_workspace_4x4.xml
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto">
<!-- Hotseat -->
<include launcher:workspace="@xml/dw_phone_hotseat" />
<!-- Bottom row -->
<resolve
launcher:screen="0"
launcher:x="0"
launcher:y="-1" >
<favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
<favorite launcher:uri="mailto:" />
</resolve>
<resolve
launcher:screen="0"
launcher:x="1"
launcher:y="-1" >
<favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
<favorite launcher:uri="#Intent;type=images/*;end" />
</resolve>
<resolve
launcher:screen="0"
launcher:x="3"
launcher:y="-1" >
<favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
<favorite launcher:uri="market://details?id=com.android.launcher" />
</resolve>
</favorites>
- @xml/dw_phone_hotseat, hotseat配置文件,配置規則一樣
- resolve標簽, 通過ResolveParser解析,包含內嵌標簽
- favorite, 一個app的信息,可以指定uri,或具體的包名,類名來識別app
- 可以有自定義的標簽,自己實現解析即可
至此,loadDefaultFavoritesIfNecessary()就執行完成,我們回到LauncherModel繼續看loadWorkspace()
讀取數據
接下來就是從數據庫讀取從配置文件讀到的信息,根據itemType, 走switch的不同case
final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
final Cursor c = contentResolver.query(contentUri, null, null, null, null);
// +1 for the hotseat (it can be larger than the workspace)
// Load workspace in reverse order to ensure that latest items are loaded first (and
// before any earlier duplicates)
final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();
HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
...
while (!mStopped && c.moveToNext()) {
try {
int itemType = c.getInt(itemTypeIndex);
boolean restored = 0 != c.getInt(restoredIndex);
boolean allowMissingTarget = false;
container = c.getInt(containerIndex);
switch (itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
...
sBgItemsIdMap.put(info.id, info);
...
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
...
sBgItemsIdMap.put(folderInfo.id, folderInfo);
sBgFolders.put(folderInfo.id, folderInfo);
...
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
...
比如 ITEM_TYPE_APPLICATION和ITEM_TYPE_SHORTCUT,執行到最后會保存到LongArrayMap sBgItemsIdMap里。而ITEM_TYPE_FOLDER,保存到LongArrayMap sBgFolders,同時也保存到sBgItemsIdMap,因為FolderInfo和應用的AppInfo,ShortcutInfo都是繼承ItemInfo,后續可以轉型處理。ITEM_TYPE_APPWIDGET,ITEM_TYPE_CUSTOM_APPWIDGET同理。
這里的細節比較多,通常我們也不用特地修正這里的查詢邏輯,故不做詳細闡述。
綁定WorkSpace數據
加載完workspace數據后,往下就是講數據綁定到workspace,調用bindWorkspace(-1)方法。上一步我, 知道數據都保存在集合sBgWorkspaceItems,sBgAppWidgets,sBgWorkspaceScreens里,這里Google不是直接遍歷里面的數據,綁定到View上。而是做了一個copy的操作,避免后續的某個線程修改全局變量影響到其他的工作線程。
private void bindWorkspace(int synchronizeBindPage) {
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
ArrayList<LauncherAppWidgetInfo> appWidgets =
new ArrayList<LauncherAppWidgetInfo>();
ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
final LongArrayMap<FolderInfo> folders;
final LongArrayMap<ItemInfo> itemsIdMap;
synchronized (sBgLock) {
workspaceItems.addAll(sBgWorkspaceItems);
appWidgets.addAll(sBgAppWidgets);
orderedScreenIds.addAll(sBgWorkspaceScreens);
folders = sBgFolders.clone();
itemsIdMap = sBgItemsIdMap.clone();
}
final boolean isLoadingSynchronously =
synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
int currScreen = isLoadingSynchronously ? synchronizeBindPage :
oldCallbacks.getCurrentWorkspaceScreen();
if (currScreen >= orderedScreenIds.size()) {
// There may be no workspace screens (just hotseat items and an empty page).
currScreen = PagedView.INVALID_RESTORE_PAGE;
}
final int currentScreen = currScreen;
final long currentScreenId = currentScreen < 0
? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
// Load all the items that are on the current page first (and in the process, unbind
// all the existing workspace items before we call startBinding() below.
unbindWorkspaceItemsOnMainThread();
// Separate the items that are on the current screen, and all the other remaining items
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
new ArrayList<LauncherAppWidgetInfo>();
ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
new ArrayList<LauncherAppWidgetInfo>();
LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();
filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
otherWorkspaceItems);
filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
otherAppWidgets);
filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
otherFolders);
sortWorkspaceItemsSpatially(currentWorkspaceItems);
sortWorkspaceItemsSpatially(otherWorkspaceItems);
// Tell the workspace that we're about to start binding items
r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.startBinding();
}
}
};
runOnMainThread(r);
Log.e(TAG, "orderedScreenIds:" + orderedScreenIds);
之后一系列filter和sort方法,將當前需要加載頁的數據,按screenId排序,并填充到新的集合里. 準備工作也就完成了,然后通過 tryGetCallbacks 獲取到一個Callbacks, 這個Callbacks也就是LauncherModel的mCallback,初始化是在Launcher onCreate里調用LauncherAppState.setLauncher,在LauncherModel的initialize()里完成賦值。 故,Callbacks就是我們的Launcher。
調用Launcher里實現的 startBinding(), 改變workspace的狀態,移除一些舊的View和數據
/**
* Refreshes the shortcuts shown on the workspace.
* <p>
* Implementation of the method from LauncherModel.Callbacks.
*/
public void startBinding() {
setWorkspaceLoading(true);
// If we're starting binding all over again, clear any bind calls we'd postponed in
// the past (see waitUntilResume) -- we don't need them since we're starting binding
// from scratch again
mBindOnResumeCallbacks.clear();
// Clear the workspace because it's going to be rebound
mWorkspace.clearDropTargets();
mWorkspace.removeAllWorkspaceScreens();
mWidgetsToAdvance.clear();
if (mHotseat != null) {
mHotseat.resetLayout();
}
}
之后就依次開始綁定
- 綁定WorkSpace的Screen,bindWorkspaceScreens
- 綁定Workspace當前頁的Items,包括應用信息,組件信息,bindWorkspaceItems
- 綁定Workspace其他頁的Items
bindWorkspace(int synchronizeBindPage)方法片段:
bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
// Load items on the current page
bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
currentFolders, null);
if (isLoadingSynchronously) {
r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
callbacks.onPageBoundSynchronously(currentScreen);
}
}
};
runOnMainThread(r);
}
// Load all the remaining pages (if we are loading synchronously, we want to defer this
// work until after the first render)
synchronized (mDeferredBindRunnables) {
mDeferredBindRunnables.clear();
}
bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
(isLoadingSynchronously ? mDeferredBindRunnables : null));
我們看到bindWorkspaceScreens,bindWorkspaceItems里最終調用的是
Launcher里的回調方法,bindScreens,bindItems,bindFolders等等.
以 綁定Screen為例
@Override
public void bindScreens(ArrayList<Long> orderedScreenIds) {
bindAddScreens(orderedScreenIds);
// If there are no screens, we need to have an empty screen
if (orderedScreenIds.size() == 0) {
mWorkspace.addExtraEmptyScreen();
}
// Create the custom content page (this call updates mDefaultScreen which calls
// setCurrentPage() so ensure that all pages are added before calling this).
if (hasCustomContentToLeft()) {
mWorkspace.createCustomContentContainer();
populateCustomContentContainer();
}
}
@Override
public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
}
}
根據獲取到Screen Id集合,調用Workspace的insertNewWorkspaceScreenBeforeEmptyScreen,
創建相對應的CellLayout,并添加到我們的Workspace這個容器里
public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
// Find the index to insert this view into. If the empty screen exists, then
// insert it before that.
int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
if (insertIndex < 0) {
insertIndex = mScreenOrder.size();
}
return insertNewWorkspaceScreen(screenId, insertIndex);
}
public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
if (mWorkspaceScreens.containsKey(screenId)) {
throw new RuntimeException("Screen id " + screenId + " already exists!");
}
// Inflate the cell layout, but do not add it automatically so that we can get the newly
// created CellLayout.
CellLayout newScreen = (CellLayout) mLauncher.getLayoutInflater().inflate(
R.layout.workspace_screen, this, false /* attachToRoot */);
newScreen.setOnLongClickListener(mLongClickListener);
newScreen.setOnClickListener(mLauncher);
newScreen.setSoundEffectsEnabled(false);
mWorkspaceScreens.put(screenId, newScreen);
mScreenOrder.add(insertIndex, screenId);
addView(newScreen, insertIndex);
LauncherAccessibilityDelegate delegate =
LauncherAppState.getInstance().getAccessibilityDelegate();
if (delegate != null && delegate.isInAccessibleDrag()) {
newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
}
return screenId;
}
圖標,文件夾,組件的創建流程都是類似的,類似的東西就不重復,具體細節得各位慢慢琢磨。
加載所有應用
加載完workspace后,會加載所有應用,更新應用圖標。
private void loadAndBindAllApps() {
if (DEBUG_LOADERS) {
Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
}
if (!mAllAppsLoaded) {
loadAllApps();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
}
updateIconCache();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
mAllAppsLoaded = true;
}
} else {
onlyBindAllApps();
}
}
加載所有應用,保存到mBgAllAppsList AllAppsList對象里。
private void loadAllApps() {
final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
final Callbacks oldCallbacks = mCallbacks.get();
if (oldCallbacks == null) {
// This launcher has exited and nobody bothered to tell us. Just bail.
Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
return;
}
final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
// Clear the list of apps
mBgAllAppsList.clear();
for (UserHandleCompat user : profiles) {
// Query for the set of apps
final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
if (DEBUG_LOADERS) {
Log.d(TAG, "getActivityList took "
+ (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
}
// Fail if we don't have any apps
// TODO: Fix this. Only fail for the current user.
if (apps == null || apps.isEmpty()) {
return;
}
boolean quietMode = mUserManager.isQuietModeEnabled(user);
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfoCompat app = apps.get(i);
// This builds the icon bitmaps.
mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode));
}
final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
if (heuristic != null) {
final Runnable r = new Runnable() {
@Override
public void run() {
heuristic.processUserApps(apps);
}
};
runOnMainThread(new Runnable() {
@Override
public void run() {
// Check isLoadingWorkspace on the UI thread, as it is updated on
// the UI thread.
if (mIsLoadingAndBindingWorkspace) {
synchronized (mBindCompleteRunnables) {
mBindCompleteRunnables.add(r);
}
} else {
runOnWorkerThread(r);
}
}
});
}
}
// Huh? Shouldn't this be inside the Runnable below?
final ArrayList<AppInfo> added = mBgAllAppsList.added;
mBgAllAppsList.added = new ArrayList<AppInfo>();
// Post callback on main thread
mHandler.post(new Runnable() {
public void run() {
final long bindTime = SystemClock.uptimeMillis();
final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindAllApplications(added);
if (DEBUG_LOADERS) {
Log.d(TAG, "bound " + added.size() + " apps in "
+ (SystemClock.uptimeMillis() - bindTime) + "ms");
}
} else {
Log.i(TAG, "not binding apps: no Launcher activity");
}
}
});
// Cleanup any data stored for a deleted user.
ManagedProfileHeuristic.processAllUsers(profiles, mContext);
if (DEBUG_LOADERS) {
Log.d(TAG, "Icons processed in "
+ (SystemClock.uptimeMillis() - loadTime) + "ms");
}
}
通過LauncherAppsCompat的對象,拿到所有安裝的應用,遍歷添加到AllAppsList里。
mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode));
同綁定workspace數據一樣,會調用Launcher里實現的回調方法 bindAllApplications,將數據填充到抽屜View容器里。
/**
* Add the icons for all apps.
* <p>
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAllApplications(final ArrayList<AppInfo> apps) {
if (waitUntilResume(mBindAllApplicationsRunnable, true)) {
mTmpAppsList = apps;
return;
}
if (mAppsView != null) {
mAppsView.setApps(apps);
}
if (mLauncherCallbacks != null) {
mLauncherCallbacks.bindAllApplications(apps);
}
}
這樣加載流程基本就結束了。
給大家梳理一個大致的流程,很多細節都沒有介紹,不懂的地方可以留言,謝謝。