Improving LauncherModel performance
- no longer reloading DB on each configuration change
- adding/updating items in DB on background thread
Change-Id: Ie140f31608df84b0ca2d45eb7a210a8a3b36b52f
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index e8ae9d9..64b38c0 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -88,9 +88,27 @@
private WeakReference<Callbacks> mCallbacks;
- private AllAppsList mAllAppsList; // only access in worker thread
- private IconCache mIconCache;
+ // < only access in worker thread >
+ private AllAppsList mAllAppsList;
+ // sItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
+ // LauncherModel to their ids
+ static final HashMap<Long, ItemInfo> sItemsIdMap = new HashMap<Long, ItemInfo>();
+
+ // sItems 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> sItems = new ArrayList<ItemInfo>();
+
+ // sAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
+ static final ArrayList<LauncherAppWidgetInfo> sAppWidgets =
+ new ArrayList<LauncherAppWidgetInfo>();
+
+ // sFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
+ static final HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
+ // </ only access in worker thread >
+
+ private IconCache mIconCache;
private Bitmap mDefaultIcon;
private static int mCellCountX;
@@ -148,9 +166,8 @@
/**
* Move an item in the DB to a new <container, screen, cellX, cellY>
*/
- static void moveItemInDatabase(Context context, ItemInfo item, long container, int screen,
- int cellX, int cellY) {
-
+ static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
+ final int screen, final int cellX, final int cellY) {
item.container = container;
item.screen = screen;
item.cellX = cellX;
@@ -168,6 +185,24 @@
sWorker.post(new Runnable() {
public void run() {
cr.update(uri, values, null, null);
+ ItemInfo modelItem = sItemsIdMap.get(item.id);
+ if (item != modelItem) {
+ // the modelItem needs to match up perfectly with item if our model is to be
+ // consistent with the database-- for now, just require modelItem == item
+ throw new RuntimeException("Error: ItemInfo passed to moveItemInDatabase " +
+ "doesn't match original");
+ }
+
+ // Items are added/removed from the corresponding FolderInfo elsewhere, such
+ // as in Workspace.onDrop. Here, we just add/remove them from the list of items
+ // that are on the desktop, as appropriate
+ if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (!sItems.contains(modelItem)) {
+ sItems.add(modelItem);
+ }
+ } else {
+ sItems.remove(modelItem);
+ }
}
});
}
@@ -175,8 +210,8 @@
/**
* Resize an item in the DB to a new <spanX, spanY, cellX, cellY>
*/
- static void resizeItemInDatabase(Context context, ItemInfo item, int cellX, int cellY,
- int spanX, int spanY) {
+ static void resizeItemInDatabase(Context context, final ItemInfo item, final int cellX,
+ final int cellY, final int spanX, final int spanY) {
item.spanX = spanX;
item.spanY = spanY;
item.cellX = cellX;
@@ -195,6 +230,13 @@
sWorker.post(new Runnable() {
public void run() {
cr.update(uri, values, null, null);
+ ItemInfo modelItem = sItemsIdMap.get(item.id);
+ if (item != modelItem) {
+ // the modelItem needs to match up perfectly with item if our model is to be
+ // consistent with the database-- for now, just require modelItem == item
+ throw new RuntimeException("Error: ItemInfo passed to moveItemInDatabase " +
+ "doesn't match original");
+ }
}
});
}
@@ -316,14 +358,37 @@
final ContentResolver cr = context.getContentResolver();
item.onAddToDatabase(values);
+ Launcher l = (Launcher) context;
+ LauncherApplication app = (LauncherApplication) l.getApplication();
+ item.id = app.getLauncherProvider().generateNewId();
+ values.put(LauncherSettings.Favorites._ID, item.id);
item.updateValuesWithCoordinates(values, cellX, cellY);
- Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
- LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
+ sWorker.post(new Runnable() {
+ public void run() {
+ cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
+ LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
- if (result != null) {
- item.id = Integer.parseInt(result.getPathSegments().get(1));
- }
+ sItemsIdMap.put(item.id, item);
+ switch (item.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ sFolders.put(item.id, (FolderInfo) item);
+ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ sItems.add(item);
+ }
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ sItems.add(item);
+ }
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ sAppWidgets.add((LauncherAppWidgetInfo) item);
+ break;
+ }
+ }
+ });
}
/**
@@ -355,14 +420,26 @@
/**
* Update an item to the database in a specified container.
*/
- static void updateItemInDatabase(Context context, ItemInfo item) {
+ static void updateItemInDatabase(Context context, final ItemInfo item) {
final ContentValues values = new ContentValues();
final ContentResolver cr = context.getContentResolver();
item.onAddToDatabase(values);
item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
- cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null);
+ sWorker.post(new Runnable() {
+ public void run() {
+ cr.update(LauncherSettings.Favorites.getContentUri(item.id, false),
+ values, null, null);
+ final ItemInfo modelItem = sItemsIdMap.get(item.id);
+ if (item != modelItem) {
+ // the modelItem needs to match up perfectly with item if our model is to be
+ // consistent with the database-- for now, just require modelItem == item
+ throw new RuntimeException("Error: ItemInfo passed to moveItemInDatabase " +
+ "doesn't match original");
+ }
+ }
+ });
}
/**
@@ -370,43 +447,50 @@
* @param context
* @param item
*/
- static void deleteItemFromDatabase(Context context, ItemInfo item) {
+ static void deleteItemFromDatabase(Context context, final ItemInfo item) {
final ContentResolver cr = context.getContentResolver();
final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false);
sWorker.post(new Runnable() {
public void run() {
cr.delete(uriToDelete, null, null);
+ switch (item.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ sFolders.remove(item.id);
+ sItems.remove(item);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ sItems.remove(item);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ sAppWidgets.remove((LauncherAppWidgetInfo) item);
+ break;
+ }
+ sItemsIdMap.remove(item.id);
}
});
}
- static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
- deleteFolderContentsFromDatabase(context, info, false);
- }
-
/**
* Remove the contents of the specified folder from the database
*/
- static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info,
- boolean post) {
- // TODO: this post flag is temporary to fix an ordering of commands issue. In future,
- // all db operations will be moved to the worker thread, so this can be discarded at
- // that time.
+ static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
final ContentResolver cr = context.getContentResolver();
- if (!post) {
- cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
- cr.delete(LauncherSettings.Favorites.CONTENT_URI,
- LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
- } else {
- sWorker.post(new Runnable() {
+ sWorker.post(new Runnable() {
public void run() {
cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
+ sItemsIdMap.remove(info.id);
+ sFolders.remove(info.id);
+ sItems.remove(info);
+
cr.delete(LauncherSettings.Favorites.CONTENT_URI,
LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
+ for (ItemInfo childInfo : info.contents) {
+ sItemsIdMap.remove(childInfo.id);
+ }
}
});
- }
}
/**
@@ -547,10 +631,6 @@
private boolean mStopped;
private boolean mLoadAndBindStepFinished;
- final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
- final ArrayList<LauncherAppWidgetInfo> mAppWidgets = new ArrayList<LauncherAppWidgetInfo>();
- final HashMap<Long, FolderInfo> mFolders = new HashMap<Long, FolderInfo>();
-
LoaderTask(Context context, boolean isLaunching) {
mContext = context;
mIsLaunching = isLaunching;
@@ -562,14 +642,10 @@
private void loadAndBindWorkspace() {
// Load the workspace
-
- // For now, just always reload the workspace. It's ~100 ms vs. the
- // binding which takes many hundreds of ms.
- // We can reconsider.
if (DEBUG_LOADERS) {
Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
}
- if (true || !mWorkspaceLoaded) {
+ if (!mWorkspaceLoaded) {
loadWorkspace();
if (mStopped) {
return;
@@ -743,9 +819,10 @@
final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
final boolean isSafeMode = manager.isSafeMode();
- mItems.clear();
- mAppWidgets.clear();
- mFolders.clear();
+ sItems.clear();
+ sAppWidgets.clear();
+ sFolders.clear();
+ sItemsIdMap.clear();
final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
@@ -834,15 +911,16 @@
switch (container) {
case LauncherSettings.Favorites.CONTAINER_DESKTOP:
- mItems.add(info);
+ sItems.add(info);
break;
default:
// Item is in a user folder
FolderInfo folderInfo =
- findOrMakeFolder(mFolders, container);
+ findOrMakeFolder(sFolders, container);
folderInfo.add(info);
break;
}
+ sItemsIdMap.put(info.id, info);
// now that we've loaded everthing re-save it with the
// icon in case it disappears somehow.
@@ -861,7 +939,7 @@
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
id = c.getLong(idIndex);
- FolderInfo folderInfo = findOrMakeFolder(mFolders, id);
+ FolderInfo folderInfo = findOrMakeFolder(sFolders, id);
folderInfo.title = c.getString(titleIndex);
folderInfo.id = id;
@@ -877,11 +955,12 @@
}
switch (container) {
case LauncherSettings.Favorites.CONTAINER_DESKTOP:
- mItems.add(folderInfo);
+ sItems.add(folderInfo);
break;
}
- mFolders.put(folderInfo.id, folderInfo);
+ sItemsIdMap.put(folderInfo.id, folderInfo);
+ sFolders.put(folderInfo.id, folderInfo);
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
@@ -918,8 +997,8 @@
if (!checkItemPlacement(occupied, appWidgetInfo)) {
break;
}
-
- mAppWidgets.add(appWidgetInfo);
+ sItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
+ sAppWidgets.add(appWidgetInfo);
}
break;
}
@@ -993,7 +1072,7 @@
}
});
// Add the items to the workspace.
- N = mItems.size();
+ N = sItems.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);
@@ -1001,7 +1080,7 @@
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
- callbacks.bindItems(mItems, start, start+chunkSize);
+ callbacks.bindItems(sItems, start, start+chunkSize);
}
}
});
@@ -1010,7 +1089,7 @@
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
- callbacks.bindFolders(mFolders);
+ callbacks.bindFolders(sFolders);
}
}
});
@@ -1028,10 +1107,10 @@
// is just a hint for the order, and if it's wrong, we'll be okay.
// TODO: instead, we should have that push the current screen into here.
final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen();
- N = mAppWidgets.size();
+ N = sAppWidgets.size();
// once for the current screen
for (int i=0; i<N; i++) {
- final LauncherAppWidgetInfo widget = mAppWidgets.get(i);
+ final LauncherAppWidgetInfo widget = sAppWidgets.get(i);
if (widget.screen == currentScreen) {
mHandler.post(new Runnable() {
public void run() {
@@ -1045,7 +1124,7 @@
}
// once for the other screens
for (int i=0; i<N; i++) {
- final LauncherAppWidgetInfo widget = mAppWidgets.get(i);
+ final LauncherAppWidgetInfo widget = sAppWidgets.get(i);
if (widget.screen != currentScreen) {
mHandler.post(new Runnable() {
public void run() {
@@ -1238,7 +1317,7 @@
Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
- Log.d(TAG, "mItems size=" + mItems.size());
+ Log.d(TAG, "mItems size=" + sItems.size());
}
}