《Android进阶》之第二篇 launcher
1 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params, 2 boolean markCells) { 3 final LayoutParams lp = params; 4 5 // Hotseat icons - remove text 6 if (child instanceof BubbleTextView) { 7 BubbleTextView bubbleChild = (BubbleTextView) child; 8 bubbleChild.setTextVisibility(!mIsHotseat); 9 } 10 11 child.setScaleX(getChildrenScale()); 12 child.setScaleY(getChildrenScale()); 13 14 // Generate an id for each view, this assumes we have at most 256x256 cells 15 // per workspace screen 16 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) { 17 // If the horizontal or vertical span is set to -1, it is taken to 18 // mean that it spans the extent of the CellLayout 19 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX; 20 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY; 21 22 child.setId(childId); 23 24 mShortcutsAndWidgets.addView(child, index, lp); 25 26 if (markCells) markCellsAsOccupiedForView(child); 27 28 return true; 29 } 30 return false; 31 }
allapp这就是加载每个icon到view的那个位置
1、将就的地方 launcher.java
static int[] getSpanForWidget(Context context, ComponentName component, int minWidth, int minHeight) { // Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null); Rect padding = new Rect(20, 20, 300, 300); // We want to account for the extra amount of padding that we are adding to the widget // to ensure that it gets the full amount of space that it has requested int requiredWidth = minWidth + padding.left + padding.right; int requiredHeight = minHeight + padding.top + padding.bottom; return CellLayout.rectToCell(requiredWidth, requiredHeight, null); }
2、launcher.java
1 /** 2 * Add the icons for all apps. 3 * 4 * Implementation of the method from LauncherModel.Callbacks. 5 */ 6 public void bindAllApplications(final ArrayList<AppInfo> apps) { 7 if (AppsCustomizePagedView.DISABLE_ALL_APPS) { 8 if (mIntentsOnWorkspaceFromUpgradePath != null) { 9 if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) { 10 getHotseat().addAllAppsFolder(mIconCache, apps, 11 mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace); 12 } 13 mIntentsOnWorkspaceFromUpgradePath = null; 14 } 15 } else { 16 if (mAppsCustomizeContent != null) { 17 mAppsCustomizeContent.setApps(apps); 18 } 19 } 20 }
3、判断是否在桌面
public boolean isAllAppsVisible() {
return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
}
1、Callbacks接口
LauncherModel里面,需要先分析一个Callbacks接口。
1 public interface Callbacks { 2 public boolean setLoadOnResume(); 3 public int getCurrentWorkspaceScreen(); 4 public void startBinding(); 5 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, 6 boolean forceAnimateIcons); 7 public void bindScreens(ArrayList<Long> orderedScreenIds); 8 public void bindAddScreens(ArrayList<Long> orderedScreenIds); 9 public void bindFolders(HashMap<Long,FolderInfo> folders); 10 public void finishBindingItems(boolean upgradePath); 11 public void bindAppWidget(LauncherAppWidgetInfo info); 12 public void bindAllApplications(ArrayList<AppInfo> apps); 13 public void bindAppsAdded(ArrayList<Long> newScreens, 14 ArrayList<ItemInfo> addNotAnimated, 15 ArrayList<ItemInfo> addAnimated, 16 ArrayList<AppInfo> addedApps); 17 public void bindAppsUpdated(ArrayList<AppInfo> apps); 18 public void bindComponentsRemoved(ArrayList<String> packageNames, 19 ArrayList<AppInfo> appInfos, 20 boolean matchPackageNamesOnly); 21 public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts); 22 public void bindSearchablesChanged(); 23 public boolean isAllAppsButtonRank(int rank); 24 public void onPageBoundSynchronously(int page); 25 public void dumpLogsToLocalData(); 26 }
Callbacks接口提供了很多接口,用于返回相关的数据给Launcher模块,下面我们对每个接口作用做个阐释。
setLoadOnResume() :当Launcher.java类的Activity处于onPause的时候,如果重新恢复,需要调用onResume,此时需要在onResume调用这个接口,恢复Launcher数据。
getCurrentWorkspace():获取屏幕序号(0~4)
startBinding():通知Launcher开始加载数据。清空容器数据,重新加载
bindItems(ArrayList<ItemInfo> shortcuts, int start, int end):加载App shortcut、Live Folder、widget到Launcher相关容器。
bindFolders(HashMap<Long, FolderInfo> folders):加载folder的内容
finishBindingItems():数据加载完成。
bindAppWidget(LauncherAppWidgetInfo item):workspace加载APP 快捷方式
bindAllApplications(final ArrayList<ApplicationInfo> apps):所有应用列表接着APP图标数据
bindAppsAdded(ArrayList<ApplicationInfo> apps):通知Launcher新安装了一个APP,更新数据。
bindAppsUpdated(ArrayList<ApplicationInfo> apps):通知Launcher一个APP更新了。(覆盖安装)
bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent):通知Launcher,应用被删除
bindPackagesUpdated():多个应用更新。
isAllAppsVisible():返回所有应用列表是否可见状态。
bindSearchablesChanged():Google搜索栏或者删除区域发生变化时通知Launcher
2、数据加载流程
Launcher.java类继承了Callbacks接口,并实现了该接口。LauncherModel里面会调用这些接口,反馈数据和状态给Launcher。数据加载总体分为两部分,一部分是加载workspace的数据,另一部分是加载All APP界面的数据。
3、startLoader()
下面我们先分析startLoader()接口,startLoader主要是启动了一个线程,用于加载数据。
1 public void startLoader(boolean isLaunching, int synchronousBindPage) { 2 synchronized (mLock) { 3 if (DEBUG_LOADERS) { 4 Log.d(TAG, "startLoader isLaunching=" + isLaunching); 5 } 6 7 // Clear any deferred bind-runnables from the synchronized load process 8 // We must do this before any loading/binding is scheduled below. 9 mDeferredBindRunnables.clear(); 10 11 // Don't bother to start the thread if we know it's not going to do anything 12 if (mCallbacks != null && mCallbacks.get() != null) { 13 // If there is already one running, tell it to stop. 14 // also, don't downgrade isLaunching if we're already running 15 isLaunching = isLaunching || stopLoaderLocked(); 16 mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching); 17 if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) { 18 mLoaderTask.runBindSynchronousPage(synchronousBindPage); 19 } else { 20 sWorkerThread.setPriority(Thread.NORM_PRIORITY); 21 sWorker.post(mLoaderTask); 22 } 23 } 24 } 25 }
4、LoaderTask的run()方法
1 public void run() { 2 boolean isUpgrade = false; 3 4 synchronized (mLock) { 5 mIsLoaderTaskRunning = true; 6 } 7 // Optimize for end-user experience: if the Launcher is up and // running with the 8 // All Apps interface in the foreground, load All Apps first. Otherwise, load the 9 // workspace first (default). 10 keep_running: { 11 // Elevate priority when Home launches for the first time to avoid 12 // starving at boot time. Staring at a blank home is not cool. 13 synchronized (mLock) { 14 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " + 15 (mIsLaunching ? "DEFAULT" : "BACKGROUND")); 16 android.os.Process.setThreadPriority(mIsLaunching 17 ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND); 18 } 19 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); 20 isUpgrade = loadAndBindWorkspace(); 21 22 if (mStopped) { 23 break keep_running; 24 } 25 26 // Whew! Hard work done. Slow us down, and wait until the UI thread has 27 // settled down. 28 synchronized (mLock) { 29 if (mIsLaunching) { 30 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND"); 31 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 32 } 33 } 34 waitForIdle(); 35 36 // second step 37 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); 38 loadAndBindAllApps(); 39 40 // Restore the default thread priority after we are done loading items 41 synchronized (mLock) { 42 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); 43 } 44 } 45 46 // Update the saved icons if necessary 47 if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons"); 48 synchronized (sBgLock) { 49 for (Object key : sBgDbIconCache.keySet()) { 50 updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key)); 51 } 52 sBgDbIconCache.clear(); 53 } 54 55 if (AppsCustomizePagedView.DISABLE_ALL_APPS) { 56 // Ensure that all the applications that are in the system are 57 // represented on the home screen. 58 if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) { 59 verifyApplications(); 60 } 61 } 62 63 // Clear out this reference, otherwise we end up holding it until all of the 64 // callback runnables are done. 65 mContext = null; 66 67 synchronized (mLock) { 68 // If we are still the last one to be scheduled, remove ourselves. 69 if (mLoaderTask == this) { 70 mLoaderTask = null; 71 } 72 mIsLoaderTaskRunning = false; 73 } 74 }
1 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); 2 isUpgrade = loadAndBindWorkspace(); 3 4 if (mStopped) { 5 break keep_running; 6 } 7 8 // Whew! Hard work done. Slow us down, and wait until the UI thread has 9 // settled down. 10 synchronized (mLock) { 11 if (mIsLaunching) { 12 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND"); 13 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 14 } 15 } 16 waitForIdle(); 17 18 // second step 19 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); 20 loadAndBindAllApps();
5、workspace加载数据
loadAndBindWorkspace()方法主要就是执行loadWorkspace()和 bindWorkspace()方法。
下面分别对这两个方法进行分析。
1 /** Returns whether this is an upgrade path */ 2 private boolean loadAndBindWorkspace() { 3 mIsLoadingAndBindingWorkspace = true; 4 5 // Load the workspace 6 if (DEBUG_LOADERS) { 7 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded); 8 } 9 10 boolean isUpgradePath = false; 11 if (!mWorkspaceLoaded) { 12 isUpgradePath = loadWorkspace(); 13 synchronized (LoaderTask.this) { 14 if (mStopped) { 15 return isUpgradePath; 16 } 17 mWorkspaceLoaded = true; 18 } 19 } 20 21 // Bind the workspace 22 bindWorkspace(-1, isUpgradePath); 23 return isUpgradePath; 24 }
workspace的数据加载总的来说也是按照元素属性来区分加载,分为App快捷方式、Widget、Folder元素。
这几个元素分别加载到不同的容器里面。其中sItemsIdMap保存所有元素的id和ItemInfo组成的映射。其他
元素分别加载到3个不同的容器里面,用于后面绑定数据用。
1 /** Returns whether this is an upgradge path */ 2 private boolean loadWorkspace() { 3 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 4 5 final Context context = mContext; 6 final ContentResolver contentResolver = context.getContentResolver(); 7 final PackageManager manager = context.getPackageManager(); 8 final AppWidgetManager widgets = AppWidgetManager.getInstance(context); 9 final boolean isSafeMode = manager.isSafeMode(); 10 11 LauncherAppState app = LauncherAppState.getInstance(); 12 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 13 int countX = (int) grid.numColumns; 14 int countY = (int) grid.numRows; 15 16 // Make sure the default workspace is loaded, if needed 17 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0); 18 19 // Check if we need to do any upgrade-path logic 20 boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb(); 21 22 synchronized (sBgLock) { 23 clearSBgDataStructures(); 24 25 final ArrayList<Long> itemsToRemove = new ArrayList<Long>(); 26 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI; 27 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri); 28 final Cursor c = contentResolver.query(contentUri, null, null, null, null); 29 30 // +1 for the hotseat (it can be larger than the workspace) 31 // Load workspace in reverse order to ensure that latest items are loaded first (and 32 // before any earlier duplicates) 33 final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>(); 34 35 try { 36 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 37 final int intentIndex = c.getColumnIndexOrThrow 38 (LauncherSettings.Favorites.INTENT); 39 final int titleIndex = c.getColumnIndexOrThrow 40 (LauncherSettings.Favorites.TITLE); 41 final int iconTypeIndex = c.getColumnIndexOrThrow( 42 LauncherSettings.Favorites.ICON_TYPE); 43 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 44 final int iconPackageIndex = c.getColumnIndexOrThrow( 45 LauncherSettings.Favorites.ICON_PACKAGE); 46 final int iconResourceIndex = c.getColumnIndexOrThrow( 47 LauncherSettings.Favorites.ICON_RESOURCE); 48 final int containerIndex = c.getColumnIndexOrThrow( 49 LauncherSettings.Favorites.CONTAINER); 50 final int itemTypeIndex = c.getColumnIndexOrThrow( 51 LauncherSettings.Favorites.ITEM_TYPE); 52 final int appWidgetIdIndex = c.getColumnIndexOrThrow( 53 LauncherSettings.Favorites.APPWIDGET_ID); 54 final int appWidgetProviderIndex = c.getColumnIndexOrThrow( 55 LauncherSettings.Favorites.APPWIDGET_PROVIDER); 56 final int screenIndex = c.getColumnIndexOrThrow( 57 LauncherSettings.Favorites.SCREEN); 58 final int cellXIndex = c.getColumnIndexOrThrow 59 (LauncherSettings.Favorites.CELLX); 60 final int cellYIndex = c.getColumnIndexOrThrow 61 (LauncherSettings.Favorites.CELLY); 62 final int spanXIndex = c.getColumnIndexOrThrow 63 (LauncherSettings.Favorites.SPANX); 64 final int spanYIndex = c.getColumnIndexOrThrow( 65 LauncherSettings.Favorites.SPANY); 66 //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 67 //final int displayModeIndex = c.getColumnIndexOrThrow( 68 // LauncherSettings.Favorites.DISPLAY_MODE); 69 70 ShortcutInfo info; 71 String intentDescription; 72 LauncherAppWidgetInfo appWidgetInfo; 73 int container; 74 long id; 75 Intent intent; 76 77 while (!mStopped && c.moveToNext()) { 78 AtomicBoolean deleteOnItemOverlap = new AtomicBoolean(false); 79 try { 80 int itemType = c.getInt(itemTypeIndex); 81 82 switch (itemType) { 83 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 84 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 85 id = c.getLong(idIndex); 86 intentDescription = c.getString(intentIndex); 87 try { 88 intent = Intent.parseUri(intentDescription, 0); 89 ComponentName cn = intent.getComponent(); 90 if (cn != null && !isValidPackageComponent(manager, cn)) { 91 if (!mAppsCanBeOnRemoveableStorage) { 92 // Log the invalid package, and remove it from the db 93 Launcher.addDumpLog(TAG, "Invalid package removed: " + cn, true); 94 itemsToRemove.add(id); 95 } else { 96 // If apps can be on external storage, then we just 97 // leave them for the user to remove (maybe add 98 // visual treatment to it) 99 Launcher.addDumpLog(TAG, "Invalid package found: " + cn, true); 100 } 101 continue; 102 } 103 } catch (URISyntaxException e) { 104 Launcher.addDumpLog(TAG, "Invalid uri: " + intentDescription, true); 105 continue; 106 } 107 108 if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 109 info = getShortcutInfo(manager, intent, context, c, iconIndex, 110 titleIndex, mLabelCache); 111 } else { 112 info = getShortcutInfo(c, context, iconTypeIndex, 113 iconPackageIndex, iconResourceIndex, iconIndex, 114 titleIndex); 115 116 // App shortcuts that used to be automatically added to Launcher 117 // didn't always have the correct intent flags set, so do that 118 // here 119 if (intent.getAction() != null && 120 intent.getCategories() != null && 121 intent.getAction().equals(Intent.ACTION_MAIN) && 122 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { 123 intent.addFlags( 124 Intent.FLAG_ACTIVITY_NEW_TASK | 125 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 126 } 127 } 128 129 if (info != null) { 130 info.id = id; 131 info.intent = intent; 132 container = c.getInt(containerIndex); 133 info.container = container; 134 info.screenId = c.getInt(screenIndex); 135 info.cellX = c.getInt(cellXIndex); 136 info.cellY = c.getInt(cellYIndex); 137 info.spanX = 1; 138 info.spanY = 1; 139 // Skip loading items that are out of bounds 140 if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 141 if (checkItemDimensions(info)) { 142 Launcher.addDumpLog(TAG, "Skipped loading out of bounds shortcut: " 143 + info + ", " + grid.numColumns + "x" + grid.numRows, true); 144 continue; 145 } 146 } 147 // check & update map of what's occupied 148 deleteOnItemOverlap.set(false); 149 if (!checkItemPlacement(occupied, info, deleteOnItemOverlap)) { 150 if (deleteOnItemOverlap.get()) { 151 itemsToRemove.add(id); 152 } 153 break; 154 } 155 156 switch (container) { 157 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 158 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: 159 sBgWorkspaceItems.add(info); 160 break; 161 default: 162 // Item is in a user folder 163 FolderInfo folderInfo = 164 findOrMakeFolder(sBgFolders, container); 165 folderInfo.add(info); 166 break; 167 } 168 sBgItemsIdMap.put(info.id, info); 169 170 // now that we've loaded everthing re-save it with the 171 // icon in case it disappears somehow. 172 queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex); 173 } else { 174 throw new RuntimeException("Unexpected null ShortcutInfo"); 175 } 176 break; 177 178 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 179 id = c.getLong(idIndex); 180 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id); 181 182 folderInfo.title = c.getString(titleIndex); 183 folderInfo.id = id; 184 container = c.getInt(containerIndex); 185 folderInfo.container = container; 186 folderInfo.screenId = c.getInt(screenIndex); 187 folderInfo.cellX = c.getInt(cellXIndex); 188 folderInfo.cellY = c.getInt(cellYIndex); 189 folderInfo.spanX = 1; 190 folderInfo.spanY = 1; 191 192 // Skip loading items that are out of bounds 193 if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 194 if (checkItemDimensions(folderInfo)) { 195 Log.d(TAG, "Skipped loading out of bounds folder"); 196 continue; 197 } 198 } 199 // check & update map of what's occupied 200 deleteOnItemOverlap.set(false); 201 if (!checkItemPlacement(occupied, folderInfo, 202 deleteOnItemOverlap)) { 203 if (deleteOnItemOverlap.get()) { 204 itemsToRemove.add(id); 205 } 206 break; 207 } 208 209 switch (container) { 210 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 211 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: 212 sBgWorkspaceItems.add(folderInfo); 213 break; 214 } 215 216 sBgItemsIdMap.put(folderInfo.id, folderInfo); 217 sBgFolders.put(folderInfo.id, folderInfo); 218 break; 219 220 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 221 // Read all Launcher-specific widget details 222 int appWidgetId = c.getInt(appWidgetIdIndex); 223 String savedProvider = c.getString(appWidgetProviderIndex); 224 225 id = c.getLong(idIndex); 226 227 final AppWidgetProviderInfo provider = 228 widgets.getAppWidgetInfo(appWidgetId); 229 230 if (!isSafeMode && (provider == null || provider.provider == null || 231 provider.provider.getPackageName() == null)) { 232 String log = "Deleting widget that isn't installed anymore: id=" 233 + id + " appWidgetId=" + appWidgetId; 234 Log.e(TAG, log); 235 Launcher.addDumpLog(TAG, log, false); 236 itemsToRemove.add(id); 237 } else { 238 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, 239 provider.provider); 240 appWidgetInfo.id = id; 241 appWidgetInfo.screenId = c.getInt(screenIndex); 242 appWidgetInfo.cellX = c.getInt(cellXIndex); 243 appWidgetInfo.cellY = c.getInt(cellYIndex); 244 appWidgetInfo.spanX = c.getInt(spanXIndex); 245 appWidgetInfo.spanY = c.getInt(spanYIndex); 246 int[] minSpan = Launcher.getMinSpanForWidget(context, provider); 247 appWidgetInfo.minSpanX = minSpan[0]; 248 appWidgetInfo.minSpanY = minSpan[1]; 249 250 container = c.getInt(containerIndex); 251 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP && 252 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 253 Log.e(TAG, "Widget found where container != " + 254 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); 255 continue; 256 } 257 258 appWidgetInfo.container = c.getInt(containerIndex); 259 // Skip loading items that are out of bounds 260 if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 261 if (checkItemDimensions(appWidgetInfo)) { 262 Log.d(TAG, "Skipped loading out of bounds app widget"); 263 continue; 264 } 265 } 266 // check & update map of what's occupied 267 deleteOnItemOverlap.set(false); 268 if (!checkItemPlacement(occupied, appWidgetInfo, 269 deleteOnItemOverlap)) { 270 if (deleteOnItemOverlap.get()) { 271 itemsToRemove.add(id); 272 } 273 break; 274 } 275 String providerName = provider.provider.flattenToString(); 276 if (!providerName.equals(savedProvider)) { 277 ContentValues values = new ContentValues(); 278 values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, 279 providerName); 280 String where = BaseColumns._ID + "= ?"; 281 String[] args = {Integer.toString(c.getInt(idIndex))}; 282 contentResolver.update(contentUri, values, where, args); 283 } 284 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); 285 sBgAppWidgets.add(appWidgetInfo); 286 } 287 break; 288 } 289 } catch (Exception e) { 290 Launcher.addDumpLog(TAG, "Desktop items loading interrupted: " + e, true); 291 } 292 } 293 } finally { 294 if (c != null) { 295 c.close(); 296 } 297 } 298 299 // Break early if we've stopped loading 300 if (mStopped) { 301 clearSBgDataStructures(); 302 return false; 303 } 304 305 if (itemsToRemove.size() > 0) { 306 ContentProviderClient client = contentResolver.acquireContentProviderClient( 307 LauncherSettings.Favorites.CONTENT_URI); 308 // Remove dead items 309 for (long id : itemsToRemove) { 310 if (DEBUG_LOADERS) { 311 Log.d(TAG, "Removed id = " + id); 312 } 313 // Don't notify content observers 314 try { 315 client.delete(LauncherSettings.Favorites.getContentUri(id, false), 316 null, null); 317 } catch (RemoteException e) { 318 Log.w(TAG, "Could not remove id = " + id); 319 } 320 } 321 } 322 323 if (loadedOldDb) { 324 long maxScreenId = 0; 325 // If we're importing we use the old screen order. 326 for (ItemInfo item: sBgItemsIdMap.values()) { 327 long screenId = item.screenId; 328 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 329 !sBgWorkspaceScreens.contains(screenId)) { 330 sBgWorkspaceScreens.add(screenId); 331 if (screenId > maxScreenId) { 332 maxScreenId = screenId; 333 } 334 } 335 } 336 Collections.sort(sBgWorkspaceScreens); 337 338 LauncherAppState.getLauncherProvider().updateMaxScreenId(maxScreenId); 339 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens); 340 341 // Update the max item id after we load an old db 342 long maxItemId = 0; 343 // If we're importing we use the old screen order. 344 for (ItemInfo item: sBgItemsIdMap.values()) { 345 maxItemId = Math.max(maxItemId, item.id); 346 } 347 LauncherAppState.getLauncherProvider().updateMaxItemId(maxItemId); 348 } else { 349 TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(mContext); 350 for (Integer i : orderedScreens.keySet()) { 351 sBgWorkspaceScreens.add(orderedScreens.get(i)); 352 } 353 354 // Remove any empty screens 355 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens); 356 for (ItemInfo item: sBgItemsIdMap.values()) { 357 long screenId = item.screenId; 358 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 359 unusedScreens.contains(screenId)) { 360 unusedScreens.remove(screenId); 361 } 362 } 363 364 // If there are any empty screens remove them, and update. 365 if (unusedScreens.size() != 0) { 366 sBgWorkspaceScreens.removeAll(unusedScreens); 367 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens); 368 } 369 } 370 371 if (DEBUG_LOADERS) { 372 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); 373 Log.d(TAG, "workspace layout: "); 374 int nScreens = occupied.size(); 375 for (int y = 0; y < countY; y++) { 376 String line = ""; 377 378 Iterator<Long> iter = occupied.keySet().iterator(); 379 while (iter.hasNext()) { 380 long screenId = iter.next(); 381 if (screenId > 0) { 382 line += " | "; 383 } 384 for (int x = 0; x < countX; x++) { 385 line += ((occupied.get(screenId)[x][y] != null) ? "#" : "."); 386 } 387 } 388 Log.d(TAG, "[ " + line + " ]"); 389 } 390 } 391 } 392 return loadedOldDb; 393 }
6、workspace绑定数据
Launcher的内容绑定分为五步:分别对应着startBinding()、bindItems()、bindFolders()、 bindAppWidgets()、
finishBindingItems()的调用。下面针对bindWorkspace做个简单的流程分析。
1 /** 2 * Binds all loaded data to actual views on the main thread. 3 */ 4 private void bindWorkspace(int synchronizeBindPage, final boolean isUpgradePath) { 5 final long t = SystemClock.uptimeMillis(); 6 Runnable r; 7 8 // Don't use these two variables in any of the callback runnables. 9 // Otherwise we hold a reference to them. 10 final Callbacks oldCallbacks = mCallbacks.get(); 11 if (oldCallbacks == null) { 12 // This launcher has exited and nobody bothered to tell us. Just bail. 13 Log.w(TAG, "LoaderTask running with no launcher"); 14 return; 15 } 16 17 final boolean isLoadingSynchronously = (synchronizeBindPage > -1); 18 final int currentScreen = isLoadingSynchronously ? synchronizeBindPage : 19 oldCallbacks.getCurrentWorkspaceScreen(); 20 21 // Load all the items that are on the current page first (and in the process, unbind 22 // all the existing workspace items before we call startBinding() below. 23 unbindWorkspaceItemsOnMainThread(); 24 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>(); 25 ArrayList<LauncherAppWidgetInfo> appWidgets = 26 new ArrayList<LauncherAppWidgetInfo>(); 27 HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(); 28 HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>(); 29 ArrayList<Long> orderedScreenIds = new ArrayList<Long>(); 30 synchronized (sBgLock) { 31 workspaceItems.addAll(sBgWorkspaceItems); 32 appWidgets.addAll(sBgAppWidgets); 33 folders.putAll(sBgFolders); 34 itemsIdMap.putAll(sBgItemsIdMap); 35 orderedScreenIds.addAll(sBgWorkspaceScreens); 36 } 37 38 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>(); 39 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>(); 40 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = 41 new ArrayList<LauncherAppWidgetInfo>(); 42 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = 43 new ArrayList<LauncherAppWidgetInfo>(); 44 HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>(); 45 HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>(); 46 47 // Separate the items that are on the current screen, and all the other remaining items 48 filterCurrentWorkspaceItems(currentScreen, workspaceItems, currentWorkspaceItems, 49 otherWorkspaceItems); 50 filterCurrentAppWidgets(currentScreen, appWidgets, currentAppWidgets, 51 otherAppWidgets); 52 filterCurrentFolders(currentScreen, itemsIdMap, folders, currentFolders, 53 otherFolders); 54 sortWorkspaceItemsSpatially(currentWorkspaceItems); 55 sortWorkspaceItemsSpatially(otherWorkspaceItems); 56 57 // Tell the workspace that we're about to start binding items 58 r = new Runnable() { 59 public void run() { 60 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 61 if (callbacks != null) { 62 callbacks.startBinding(); 63 } 64 } 65 }; 66 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); 67 68 bindWorkspaceScreens(oldCallbacks, orderedScreenIds); 69 70 // Load items on the current page 71 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, 72 currentFolders, null); 73 if (isLoadingSynchronously) { 74 r = new Runnable() { 75 public void run() { 76 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 77 if (callbacks != null) { 78 callbacks.onPageBoundSynchronously(currentScreen); 79 } 80 } 81 }; 82 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); 83 } 84 85 // Load all the remaining pages (if we are loading synchronously, we want to defer this 86 // work until after the first render) 87 mDeferredBindRunnables.clear(); 88 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders, 89 (isLoadingSynchronously ? mDeferredBindRunnables : null)); 90 91 // Tell the workspace that we're done binding items 92 r = new Runnable() { 93 public void run() { 94 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 95 if (callbacks != null) { 96 callbacks.finishBindingItems(isUpgradePath); 97 } 98 99 // If we're profiling, ensure this is the last thing in the queue. 100 if (DEBUG_LOADERS) { 101 Log.d(TAG, "bound workspace in " 102 + (SystemClock.uptimeMillis()-t) + "ms"); 103 } 104 105 mIsLoadingAndBindingWorkspace = false; 106 } 107 }; 108 if (isLoadingSynchronously) { 109 mDeferredBindRunnables.add(r); 110 } else { 111 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); 112 } 113 }
上面就是Launcher的workspace绑定数据的过程,跟加载数据过程很相似,也是区分3中类型的元素进行加载。
下面我们总结一下,workspace的加载和绑定数据的过程。我们现在回头看,可以发现,其实workspace里面就是
存放了3中数据ItemInfo、FolderInfo、LauncherAppWidgetInfo。分别对应我们的APP快捷方式、文件夹、Widget
数据。其中FolderInfo、LauncherAppWidgetInfo都是继承了ItemInfo。数据加载过程,就是从Launcher的数据库
读取数据然后按元素属性分别放到3个ArrayList里面。绑定数据过程就是把3个ArrayList的队列关联到Launcher界面里面。
7、ALL APP数据加载绑定
1 private void loadAllApps() { 2 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 3 4 final Callbacks oldCallbacks = mCallbacks.get(); 5 if (oldCallbacks == null) { 6 // This launcher has exited and nobody bothered to tell us. Just bail. 7 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)"); 8 return; 9 } 10 11 final PackageManager packageManager = mContext.getPackageManager(); 12 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); 13 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); 14 15 // Clear the list of apps 16 mBgAllAppsList.clear(); 17 18 // Query for the set of apps 19 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 20 List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0); 21 if (DEBUG_LOADERS) { 22 Log.d(TAG, "queryIntentActivities took " 23 + (SystemClock.uptimeMillis()-qiaTime) + "ms"); 24 Log.d(TAG, "queryIntentActivities got " + apps.size() + " apps"); 25 } 26 // Fail if we don't have any apps 27 if (apps == null || apps.isEmpty()) { 28 return; 29 } 30 // Sort the applications by name 31 final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 32 Collections.sort(apps, 33 new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache)); 34 if (DEBUG_LOADERS) { 35 Log.d(TAG, "sort took " 36 + (SystemClock.uptimeMillis()-sortTime) + "ms"); 37 } 38 39 // Create the ApplicationInfos 40 for (int i = 0; i < apps.size(); i++) { 41 ResolveInfo app = apps.get(i); 42 // This builds the icon bitmaps. 43 mBgAllAppsList.add(new AppInfo(packageManager, app, 44 mIconCache, mLabelCache)); 45 } 46 47 // Huh? Shouldn't this be inside the Runnable below? 48 final ArrayList<AppInfo> added = mBgAllAppsList.added; 49 mBgAllAppsList.added = new ArrayList<AppInfo>(); 50 51 // Post callback on main thread 52 mHandler.post(new Runnable() { 53 public void run() { 54 final long bindTime = SystemClock.uptimeMillis(); 55 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 56 if (callbacks != null) { 57 callbacks.bindAllApplications(added); 58 if (DEBUG_LOADERS) { 59 Log.d(TAG, "bound " + added.size() + " apps in " 60 + (SystemClock.uptimeMillis() - bindTime) + "ms"); 61 } 62 } else { 63 Log.i(TAG, "not binding apps: no Launcher activity"); 64 } 65 } 66 }); 67 68 if (DEBUG_LOADERS) { 69 Log.d(TAG, "Icons processed in " 70 + (SystemClock.uptimeMillis() - loadTime) + "ms"); 71 } 72 }
// Post callback on main thread 很重要
AppInfo由四部分组成
List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
// Create the ApplicationInfos for (int i = 0; i < apps.size(); i++) { ResolveInfo app = apps.get(i); // This builds the icon bitmaps. mBgAllAppsList.add(new AppInfo(packageManager, app, mIconCache, mLabelCache)); }
AllAPP的数据加载和绑定跟workspace的差不多,也是先加载数据然后绑定数据,通知Launcher。加载数据的时候
从PackageManager获取所有已经安装的APK包信息,然后过滤只包含需要显示在所有应用列表的应用,需要包含
ACTION_MAIN和CATEGORY_LAUNCHER两个属性。这个我们在编写应用程序的时候都应该知道。
AllAPP加载跟workspace不同的地方是加载的同时,完成数据绑定的操作,也就是说第一次加载AllAPP页面的数据,
会同时绑定数据到Launcher。第二次需要加载的时候,只会把数据直接绑定到Launcher,而不会重新搜索加载数据。
Launcher启动加载和绑定数据就是这样完成。绑定完数据,Launcher就可以运行。
绑定数据,回调接口
1 /** 2 * Add the icons for all apps. 3 * 4 * Implementation of the method from LauncherModel.Callbacks. 5 */ 6 public void bindAllApplications(final ArrayList<AppInfo> apps) { 7 if (AppsCustomizePagedView.DISABLE_ALL_APPS) { 8 if (mIntentsOnWorkspaceFromUpgradePath != null) { 9 if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) { 10 getHotseat().addAllAppsFolder(mIconCache, apps, 11 mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace); 12 } 13 mIntentsOnWorkspaceFromUpgradePath = null; 14 } 15 } else { 16 if (mAppsCustomizeContent != null) { 17 mAppsCustomizeContent.setApps(apps); 18 } 19 } 20 }
8、ALL APP显示:
public void syncAppsPageItems(int page, boolean immediate) { // ensure that we have the right number of items on the pages final boolean isRtl = isLayoutRtl(); int numCells = mCellCountX * mCellCountY; int startIndex = page * numCells; int endIndex = Math.min(startIndex + numCells, mApps.size()); AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(page); layout.removeAllViewsOnPage(); ArrayList<Object> items = new ArrayList<Object>(); ArrayList<Bitmap> images = new ArrayList<Bitmap>(); for (int i = startIndex; i < endIndex; ++i) { AppInfo info = mApps.get(i); PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate( R.layout.apps_customize_application, layout, false); icon.applyFromApplicationInfo(info, true, this); icon.setOnClickListener(this); icon.setOnLongClickListener(this); icon.setOnTouchListener(this); icon.setOnKeyListener(this); int index = i - startIndex; int x = index % mCellCountX; int y = index / mCellCountX; if (isRtl) { x = mCellCountX - x - 1; } layout.addViewToCellLayout(icon, -1, i, new CellLayout.LayoutParams(x,y, 1,1), false); items.add(info); images.add(info.iconBitmap); } enableHwLayersOnVisiblePages(); }
1 public void applyFromApplicationInfo(AppInfo info, boolean scaleUp, 2 PagedViewIcon.PressedCallback cb) { 3 mIcon = info.iconBitmap; 4 mPressedCallback = cb; 5 setCompoundDrawables(null, Utilities.createIconDrawable(mIcon), 6 null, null); 7 setText(info.title); 8 setTag(info); 9 }
将图标和title加载到控件上
启动应用程序:
1 @Override 2 public void onClick(View v) { 3 // When we have exited all apps or are in transition, disregard clicks 4 if (!mLauncher.isAllAppsVisible() || 5 mLauncher.getWorkspace().isSwitchingState()) return; 6 7 if (v instanceof PagedViewIcon) { 8 // Animate some feedback to the click 9 final AppInfo appInfo = (AppInfo) v.getTag(); 10 11 // Lock the drawable state to pressed until we return to Launcher 12 if (mPressedIcon != null) { 13 mPressedIcon.lockDrawableState(); 14 } 15 mLauncher.startActivitySafely(v, appInfo.intent, appInfo); 16 mLauncher.getStats().recordLaunch(appInfo.intent); 17 } else if (v instanceof PagedViewWidget) { 18 // Let the user know that they have to long press to add a widget 19 if (mWidgetInstructionToast != null) { 20 mWidgetInstructionToast.cancel(); 21 } 22 mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add, 23 Toast.LENGTH_SHORT); 24 mWidgetInstructionToast.show(); 25 26 // Create a little animation to show that the widget can move 27 float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); 28 final ImageView p = (ImageView) v.findViewById(R.id.widget_preview); 29 AnimatorSet bounce = LauncherAnimUtils.createAnimatorSet(); 30 ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY); 31 tyuAnim.setDuration(125); 32 ValueAnimator tydAnim = LauncherAnimUtils.ofFloat(p, "translationY", 0f); 33 tydAnim.setDuration(100); 34 bounce.play(tyuAnim).before(tydAnim); 35 bounce.setInterpolator(new AccelerateInterpolator()); 36 bounce.start(); 37 } 38 }
卸载程序后,如何更新页面,探究:
1 protected void invalidatePageData(int currentPage, boolean immediateAndOnly) { 2 if (!mIsDataReady) { 3 return; 4 } 5 6 if (mContentIsRefreshable) { 7 // Force all scrolling-related behavior to end 8 mScroller.forceFinished(true); 9 mNextPage = INVALID_PAGE; 10 11 // Update all the pages 12 syncPages(); 13 14 // We must force a measure after we've loaded the pages to update the content width and 15 // to determine the full scroll width 16 measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), 17 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); 18 19 // Set a new page as the current page if necessary 20 if (currentPage > -1) { 21 setCurrentPage(Math.min(getPageCount() - 1, currentPage)); 22 } 23 24 // Mark each of the pages as dirty 25 final int count = getChildCount(); 26 mDirtyPageContent.clear(); 27 for (int i = 0; i < count; ++i) { 28 mDirtyPageContent.add(true); 29 } 30 31 // Load any pages that are necessary for the current window of views 32 loadAssociatedPages(mCurrentPage, immediateAndOnly); 33 requestLayout(); 34 } 35 if (isPageMoving()) { 36 // If the page is moving, then snap it to the final position to ensure we don't get 37 // stuck between pages 38 snapToDestination(); 39 } 40 }
查询loadAssociatedPages
安装程序后桌面出错: