您的位置:首页 > 其它

Launcher3学习记录-Launcher第一次启动时的快捷方式、Widget加载流程

2017-08-17 17:53 621 查看
Launcher3的主Activity是Launcher.java,在onCreate()方法中可以找到数据下载的入口。mModel 是类LauncherModel的引用。

1         if (!mModel.startLoader(mWorkspace.getRestorePage())) {
2             // If we are not binding synchronously, show a fade in animation when
3             // the first page bind completes.
4             mDragLayer.setAlpha(0);
5         } else {
6             setWorkspaceLoading(true);
7         }


在startLoader方法中可以看到,启动了一个名为LoaderTask的子线程。

1     public boolean startLoader(int synchronousBindPage) {
2         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
3         InstallShortcutReceiver.enableInstallQueue();
4         synchronized (mLock) {
5             // Don't bother to start the thread if we know it's not going to do anything
6             if (mCallbacks != null && mCallbacks.get() != null) {
7                 final Callbacks oldCallbacks = mCallbacks.get();
8                 // Clear any pending bind-runnables from the synchronized load process.
9                 runOnMainThread(new Runnable() {
10                     public void run() {
11                         oldCallbacks.clearPendingBinds();
12                     }
13                 });
14
15                 // If there is already one running, tell it to stop.
16                 stopLoaderLocked();
17                 mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
18                 // TODO: mDeepShortcutsLoaded does not need to be true for synchronous bind.
19                 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded
20                         && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) {
21                     mLoaderTask.runBindSynchronousPage(synchronousBindPage);
22                     return true;
23                 } else {
24                     sWorkerThread.setPriority(Thread.NORM_PRIORITY);
25                     sWorker.post(mLoaderTask);
26                 }
27             }
28         }
29         return false;
30     }


可以看到,LoaderTask 类 实现了Runnable接口,run()方法中明确了加载的三步骤:1、loadAndBindWorkspace() 加载桌面,包括加载桌面上的应用、widget、快捷方式等图标;2、loadAndBindAllApps() 加载AllApps 界面;3、loadAndBindDeepShortcuts() 加载 deep shortcuts ,这个是7.1的ROM上才会有的功能。

1     private class LoaderTask implements Runnable {
2         public void run() {
3             synchronized (mLock) {
4                 if (mStopped) {
5                     return;
6                 }
7                 mIsLoaderTaskRunning = true;
8             }
9             // Optimize for end-user experience: if the Launcher is up and // running with the
10             // All Apps interface in the foreground, load All Apps first. Otherwise, load the
11             // workspace first (default).
12             keep_running: {
13                 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
14                 loadAndBindWorkspace();
15
16                 if (mStopped) {
17                     break keep_running;
18                 }
19
20                 waitForIdle();
21
22                 // second step
23                 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
24                 loadAndBindAllApps();
25
26                 waitForIdle();
27
28                 // third step
29                 if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
30                 loadAndBindDeepShortcuts();
31             }
32
33             // Clear out this reference, otherwise we end up holding it until all of the
34             // callback runnables are done.
35             mContext = null;
36
37             synchronized (mLock) {
38                 // If we are still the last one to be scheduled, remove ourselves.
39                 if (mLoaderTask == this) {
40                     mLoaderTask = null;
41                 }
42                 mIsLoaderTaskRunning = false;
43                 mHasLoaderCompletedOnce = true;
44             }
45         }
46     }


接下来看下loadAndBindWorkspace() 方法,其中loadWorkspace()方法 加载数据,bindWorkspace()方法 将快捷方式和widget显示到界面上。

1         private void loadAndBindWorkspace() {
2             mIsLoadingAndBindingWorkspace = true;
3
4             // Load the workspace
5             if (DEBUG_LOADERS) {
6                 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
7             }
8
9             if (!mWorkspaceLoaded) {
10                 loadWorkspace();
11                 synchronized (LoaderTask.this) {
12                     if (mStopped) {
13                         return;
14                     }
15                     mWorkspaceLoaded = true;
16                 }
17             }
18
19             // Bind the workspace
20             bindWorkspace(mPageToBindFirst);
21         }


loadWorkspace()方法就比较复杂了,但是大致分为几部:1、加载launcher的默认布局;2、查询并遍历数据库,将数据缓存起来,同时将异常数据添加到待删除数组中,比如被禁用的应用、已卸载的应用 等异常数据;3、删除异常数据,异常的图标、异常的文件夹、异常的页面;4、重新整理剩下的数据。

1         private void loadWorkspace() {
2             ...
3             Log.d(TAG, "loadWorkspace: loading default favorites");
4             LauncherSettings.Settings.call(contentResolver, LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
5
6                 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));//加载页面顺序
7
8                 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
9                 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
10                 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
11                 while (!mStopped && c.moveToNext()) {
12                     int itemType = c.getInt(itemTypeIndex);
13                     container = c.getInt(containerIndex);
14                     switch (itemType) {
15                     case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
16                     case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
17                     case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
18                     ...
19                     sBgWorkspaceItems.add(info);
20                     sBgItemsIdMap.put(info.id, info);
21                     break;
22                     case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
23                     ...
24                     sBgWorkspaceItems.add(folderInfo);
25                     sBgItemsIdMap.put(folderInfo.id, folderInfo);
26                     sBgFolders.put(folderInfo.id, folderInfo);
27                     break;
28                     case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
29                     case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
30                     sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
31                     sBgAppWidgets.add(appWidgetInfo);
32                     break;
33                     }
34                 }
35             ...
36         }


这段加载过程将数据库中有用的数据,基本都加载到内存中来了,下面是各个数据的注释:

// sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
// LauncherModel to their ids
static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();

// sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
//       created by LauncherModel that are directly on the home screen (however, no widgets or
//       shortcuts within folders).
static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();

// sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
new ArrayList<LauncherAppWidgetInfo>();

// sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();

// sBgWorkspaceScreens is the ordered set of workspace screens.
static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();


加载完数据,接下来就得将数据转化成View绑定到界面了,bindWorkspace()方法中可以看到,分别调了bindWorkspaceScreens、bindWorkspaceItems方法,而这两个方法中,又分别调到了Launcher.java的bindScreens、bindItems、bindAppWidget

private void bindWorkspace(int synchronizeBindPage) {
...
bindWorkspaceScreens(oldCallbacks, orderedScreenIds);

Executor mainExecutor = new DeferredMainThreadExecutor();
// Load items on the current page.
bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor);

// In case of validFirstPage, only bind the first screen, and defer binding the
// remaining screens after first onDraw (and an optional the fade animation whichever
// happens later).
// This ensures that the first screen is immediately visible (eg. during rotation)
// In case of !validFirstPage, bind all pages one after other.
final Executor deferredExecutor =
validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor;

mainExecutor.execute(new Runnable() {
@Override
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.finishFirstPageBind(
validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
}
}
});

bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
...
}
private void bindWorkspaceScreens(final Callbacks oldCallbacks,
final ArrayList<Long> orderedScreens) {
final Runnable r = new Runnable() {
@Override
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindScreens(orderedScreens);
}
}
};
runOnMainThread(r);
}

private void bindWorkspaceItems(final Callbacks oldCallbacks,
final ArrayList<ItemInfo> workspaceItems,
final ArrayList<LauncherAppWidgetInfo> appWidgets,
final Executor executor) {

// Bind the workspace items
int N = workspaceItems.size();
for (int i = 0; i < N; i += ITEMS_CHUNK) {
final int start = i;
final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
final Runnable r = new Runnable() {
@Override
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindItems(workspaceItems, start, start+chunkSize,
false);
}
}
};
executor.execute(r);
}

// Bind the widgets, one at a time
N = appWidgets.size();
for (int i = 0; i < N; i++) {
final LauncherAppWidgetInfo widget = appWidgets.get(i);
final Runnable r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindAppWidget(widget);
}
}
};
executor.execute(r);
}
}


通过下面代码可以看到,bindScreens()方法跟踪到最后,Workspace的 addView()方法,将一个CellLayout 添加到Workspace;而bindItems()方法跟踪到最后,走到了 CellLayout的 addViewToCellLayout()方法,被添加到CellLayout的ShortcutAndWidgetContainer 中去了。

@Override
public void bindScreens(ArrayList<Long> orderedScreenIds) {
...
bindAddScreens(orderedScreenIds);
...
}

private void bindAddScreens(ArrayList<Long> orderedScreenIds) {
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
long screenId = orderedScreenIds.get(i);
if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) {
// No need to bind the first screen, as its always bound.
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
}
}
}
@Override
public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
final boolean forceAnimateIcons) {
...
Workspace workspace = mWorkspace;
long newShortcutsScreenId = -1;
for (int i = start; i < end; i++) {
final ItemInfo item = shortcuts.get(i);

// Short circuit if we are loading dock items for a configuration which has no dock
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
mHotseat == null) {
continue;
}

final View view;
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
ShortcutInfo info = (ShortcutInfo) item;
view = createShortcut(info);
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
view = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(FolderInfo) item, mIconCache);
break;
default:
throw new RuntimeException("Invalid Item Type");
}
...
workspace.addInScreenFromBind(view, item.container, item.screenId, item.cellX,
item.cellY, 1, 1);
if (animateIcons) {
// Animate all the applications up now
view.setAlpha(0f);
view.setScaleX(0f);
view.setScaleY(0f);
bounceAnims.add(createNewAppBounceAnimation(view, i));
newShortcutsScreenId = item.screenId;
}
}
...
}
/**
* Add the views for a widget to the workspace.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAppWidget(final LauncherAppWidgetInfo item) {
...
final LauncherAppWidgetProviderInfo appWidgetInfo;

if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
// If the provider is not ready, bind as a pending widget.
appWidgetInfo = null;
} else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
// The widget id is not valid. Try to find the widget based on the provider info.
appWidgetInfo = mAppWidgetManager.findProvider(item.providerName, item.user);
} else {
appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId);
}
...

if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
...
item.minSpanX = appWidgetInfo.minSpanX;
item.minSpanY = appWidgetInfo.minSpanY;
addAppWidgetToWorkspace(
mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo),
item, appWidgetInfo, false);
} else {
PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, false);
view.updateIcon(mIconCache);
view.updateAppWidget(null);
view.setOnClickListener(this);
addAppWidgetToWorkspace(view, item, null, false);
}
...
}

private void addAppWidgetToWorkspace(
AppWidgetHostView hostView, LauncherAppWidgetInfo item,
LauncherAppWidgetProviderInfo appWidgetInfo, boolean insert) {
hostView.setTag(item);
item.onBindAppWidget(this, hostView);

hostView.setFocusable(true);
hostView.setOnFocusChangeListener(mFocusHandler);

mWorkspace.addInScreen(hostView, item.container, item.screenId,
item.cellX, item.cellY, item.spanX, item.spanY, insert);

if (!item.isCustomWidget()) {
addWidgetToAutoAdvanceIfNeeded(hostView, appWidgetInfo);
}
}


public void 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();
}
insertNewWorkspaceScreen(screenId, insertIndex);
}

public CellLayout 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);

if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
}

return newScreen;
}

// At bind time, we use the rank (screenId) to compute x and y for hotseat items.
// See implementation for parameter definition.
public void addInScreenFromBind(View child, long container, long screenId, int x, int y,
int spanX, int spanY) {
addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
}

void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
boolean insert) {
addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
}
void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
boolean insert, boolean computeXYFromRank) {
...
final CellLayout layout;
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
layout = mLauncher.getHotseat().getLayout();
...
} else {
layout = getScreenWithId(screenId);
...
}
...
if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
// TODO: This branch occurs when the workspace is adding views
// outside of the defined grid
// maybe we should be deleting these items from the LauncherModel?
Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
}
...
}


public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
boolean markCells) {
final LayoutParams lp = params;

// Hotseat icons - remove text
if (child instanceof BubbleTextView) {
BubbleTextView bubbleChild = (BubbleTextView) child;
bubbleChild.setTextVisibility(!mIsHotseat);
}

child.setScaleX(getChildrenScale());
child.setScaleY(getChildrenScale());

// Generate an id for each view, this assumes we have at most 256x256 cells
// per workspace screen
if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
// If the horizontal or vertical span is set to -1, it is taken to
// mean that it spans the extent of the CellLayout
if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;

child.setId(childId);
if (LOGD) {
Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
}
mShortcutsAndWidgets.addView(child, index, lp);

if (markCells) markCellsAsOccupiedForView(child);

return true;
}
return false;
}


到这里,Launcher第一次启动时 加载快捷方式、Widget的流程就结束了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: