Launcher3学习记录-Launcher第一次启动时的快捷方式、Widget加载流程
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的流程就结束了。