Batch loading of icons for AllApps.
AllAppsList now maintains <data> and <added> in sorted
order, to amortize the cost of sorting the apps list over
multiple batches.
Launcher boosts thread priority on first launch, but we now
reduce thread priority to normal after the main workspace
has been drawn but before all apps are loaded.
Experimental feature: a short delay is introduced between
batches to help free up the CPU (as well as to show that we
are indeed batching apps).
Bug: 2562420
Change-Id: I2035ec3e819b4e7993a80c6d03bfad3914c95a7a
diff --git a/src/com/android/launcher2/AllApps2D.java b/src/com/android/launcher2/AllApps2D.java
index 90c87fb..eb7cd83 100644
--- a/src/com/android/launcher2/AllApps2D.java
+++ b/src/com/android/launcher2/AllApps2D.java
@@ -50,6 +50,8 @@
private static final String TAG = "Launcher.AllApps2D";
+ private static final int BATCH_SIZE = 6; // give us a few apps at a time
+
private Launcher mLauncher;
private DragController mDragController;
@@ -300,6 +302,10 @@
return -1;
}
+ public int getAppBatchSize() {
+ return BATCH_SIZE;
+ }
+
public void dumpState() {
ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList", mAllAppsList);
}
diff --git a/src/com/android/launcher2/AllApps3D.java b/src/com/android/launcher2/AllApps3D.java
index 35e4cfc..64c8c4c 100644
--- a/src/com/android/launcher2/AllApps3D.java
+++ b/src/com/android/launcher2/AllApps3D.java
@@ -73,6 +73,8 @@
private static final int SELECTION_ICONS = 1;
private static final int SELECTION_HOME = 2;
+ private static final int BATCH_SIZE = 0; // give us all the apps at once
+
private Launcher mLauncher;
private DragController mDragController;
@@ -1593,6 +1595,10 @@
}
}
+ public int getAppBatchSize() {
+ return BATCH_SIZE;
+ }
+
public void dumpState() {
Log.d(TAG, "sRS=" + sRS);
Log.d(TAG, "sRollo=" + sRollo);
diff --git a/src/com/android/launcher2/AllAppsList.java b/src/com/android/launcher2/AllAppsList.java
index 9d4c5b0..96d9369 100644
--- a/src/com/android/launcher2/AllAppsList.java
+++ b/src/com/android/launcher2/AllAppsList.java
@@ -24,6 +24,7 @@
import android.content.pm.ResolveInfo;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
@@ -56,10 +57,17 @@
/**
* Add the supplied ApplicationInfo objects to the list, and enqueue it into the
* list to broadcast when notify() is called.
+ *
+ * Postcondition: data and added are sorted in order of LauncherModel.APP_NAME_COMPARATOR.
*/
public void add(ApplicationInfo info) {
- data.add(info);
- added.add(info);
+ int pos = Collections.binarySearch(data, info, LauncherModel.APP_NAME_COMPARATOR);
+ if (pos < 0) pos = -1 - pos;
+ data.add(pos, info);
+
+ pos = Collections.binarySearch(added, info, LauncherModel.APP_NAME_COMPARATOR);
+ if (pos < 0) pos = -1 - pos;
+ added.add(pos, info);
}
public void clear() {
@@ -86,9 +94,7 @@
if (matches.size() > 0) {
for (ResolveInfo info : matches) {
- ApplicationInfo item = new ApplicationInfo(info, mIconCache);
- data.add(item);
- added.add(item);
+ add(new ApplicationInfo(info, mIconCache));
}
}
}
@@ -139,9 +145,7 @@
info.activityInfo.applicationInfo.packageName,
info.activityInfo.name);
if (applicationInfo == null) {
- applicationInfo = new ApplicationInfo(info, mIconCache);
- data.add(applicationInfo);
- added.add(applicationInfo);
+ add(new ApplicationInfo(info, mIconCache));
} else {
mIconCache.remove(applicationInfo.componentName);
mIconCache.getTitleAndIcon(applicationInfo, info);
diff --git a/src/com/android/launcher2/AllAppsView.java b/src/com/android/launcher2/AllAppsView.java
index ed39a32..8888737 100644
--- a/src/com/android/launcher2/AllAppsView.java
+++ b/src/com/android/launcher2/AllAppsView.java
@@ -40,8 +40,10 @@
public void removeApps(ArrayList<ApplicationInfo> list);
public void updateApps(ArrayList<ApplicationInfo> list);
+
+ public int getAppBatchSize();
public void dumpState();
-
+
public void surrender();
}
diff --git a/src/com/android/launcher2/DeferredHandler.java b/src/com/android/launcher2/DeferredHandler.java
index ce60352..7801642 100644
--- a/src/com/android/launcher2/DeferredHandler.java
+++ b/src/com/android/launcher2/DeferredHandler.java
@@ -87,6 +87,12 @@
post(new IdleRunnable(runnable));
}
+ public void cancelRunnable(Runnable runnable) {
+ synchronized (mQueue) {
+ while (mQueue.remove(runnable)) { }
+ }
+ }
+
public void cancel() {
synchronized (mQueue) {
mQueue.clear();
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index b5f20e4..b82e0cf 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -2086,6 +2086,15 @@
}
/**
+ * Find out how many apps we should send to the grid at a time.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public int getAppBatchSize() {
+ return mAllAppsGrid.getAppBatchSize();
+ }
+
+ /**
* Prints out out state for debugging.
*/
public void dumpState() {
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index 5e1abe6..9766831 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -62,6 +62,8 @@
static final boolean DEBUG_LOADERS = false;
static final String TAG = "Launcher.Model";
+ final int ALL_APPS_LOAD_DELAY = 150; // ms
+
private final LauncherApplication mApp;
private final Object mLock = new Object();
private DeferredHandler mHandler = new DeferredHandler();
@@ -86,6 +88,7 @@
public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
public void bindAppsRemoved(ArrayList<ApplicationInfo> apps);
+ public int getAppBatchSize();
}
LauncherModel(LauncherApplication app, IconCache iconCache) {
@@ -575,6 +578,13 @@
}
}
+ // Whew! Hard work done.
+ synchronized (mLock) {
+ if (mIsLaunching) {
+ android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+ }
+ }
+
// Load all apps if they're dirty
int allAppsSeq;
boolean allAppsDirty;
@@ -587,7 +597,7 @@
}
}
if (allAppsDirty) {
- loadAllApps();
+ loadAndBindAllApps();
}
synchronized (mLock) {
// If we're not stopped, and nobody has incremented mAllAppsSeq.
@@ -599,11 +609,6 @@
}
}
- // Bind all apps
- if (allAppsDirty) {
- bindAllApps();
- }
-
// Clear out this reference, otherwise we end up holding it until all of the
// callback runnables are done.
mContext = null;
@@ -1014,7 +1019,9 @@
});
}
- private void loadAllApps() {
+ private void loadAndBindAllApps() {
+ final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
@@ -1026,53 +1033,79 @@
final PackageManager packageManager = mContext.getPackageManager();
final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
+ int N;
+ int batchSize = callbacks.getAppBatchSize();
+
synchronized (mLock) {
mBeforeFirstLoad = false;
-
mAllAppsList.clear();
- if (apps != null) {
- long t = SystemClock.uptimeMillis();
+ if (apps == null) return;
+ N = apps.size();
+ if (batchSize <= 0)
+ batchSize = N;
+ }
- int N = apps.size();
- for (int i=0; i<N && !mStopped; i++) {
+ int i=0;
+ while (i < N && !mStopped) {
+ synchronized (mLock) {
+ final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+
+ for (int j=0; i<N && j<batchSize; j++) {
// This builds the icon bitmaps.
mAllAppsList.add(new ApplicationInfo(apps.get(i), mIconCache));
+ i++;
}
+ // re-sort before binding this batch to the grid
Collections.sort(mAllAppsList.data, APP_NAME_COMPARATOR);
Collections.sort(mAllAppsList.added, APP_NAME_COMPARATOR);
if (DEBUG_LOADERS) {
- Log.d(TAG, "cached app icons in "
- + (SystemClock.uptimeMillis()-t) + "ms");
+ Log.d(TAG, "batch of " + batchSize + " icons processed in "
+ + (SystemClock.uptimeMillis()-t2) + "ms");
}
}
+
+ mHandler.post(bindAllAppsTask);
+
+ if (ALL_APPS_LOAD_DELAY > 0) {
+ try {
+ Thread.sleep(ALL_APPS_LOAD_DELAY);
+ } catch (InterruptedException exc) { }
+ }
+ }
+
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "cached all " + N + " apps in "
+ + (SystemClock.uptimeMillis()-t) + "ms");
}
}
- private void bindAllApps() {
- synchronized (mLock) {
- final ArrayList<ApplicationInfo> results
- = (ArrayList<ApplicationInfo>) mAllAppsList.data.clone();
- // We're adding this now, so clear out this so we don't re-send them.
- mAllAppsList.added = new ArrayList<ApplicationInfo>();
- final Callbacks old = mCallbacks.get();
- mHandler.post(new Runnable() {
- public void run() {
- final long t = SystemClock.uptimeMillis();
- final int count = results.size();
+ final Runnable bindAllAppsTask = new Runnable() {
+ public void run() {
+ final long t = SystemClock.uptimeMillis();
+ int count = 0;
+ Callbacks callbacks = null;
+ ArrayList<ApplicationInfo> results = null;
+ synchronized (mLock) {
+ mHandler.cancelRunnable(this);
- Callbacks callbacks = tryGetCallbacks(old);
- if (callbacks != null) {
- callbacks.bindAllApplications(results);
- }
+ results = (ArrayList<ApplicationInfo>) mAllAppsList.data.clone();
+ // We're adding this now, so clear out this so we don't re-send them.
+ mAllAppsList.added = new ArrayList<ApplicationInfo>();
+ count = results.size();
- if (DEBUG_LOADERS) {
- Log.d(TAG, "bound app " + count + " icons in "
- + (SystemClock.uptimeMillis() - t) + "ms");
- }
- }
- });
+ callbacks = tryGetCallbacks(mCallbacks.get());
+ }
+
+ if (callbacks != null && count > 0) {
+ callbacks.bindAllApplications(results);
+ }
+
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "bound " + count + " apps in "
+ + (SystemClock.uptimeMillis() - t) + "ms");
+ }
}
- }
+ };
public void dumpState() {
Log.d(TAG, "mLoader.mLoaderThread.mContext=" + mContext);