Improve settings cold start latency.
- Move view initialization from onViewCreated to onCreateView. This
doesn't really improve anything, it simply aligns the code more
with view's lifecycle management.
- Move dashboard category init into background. The init contains logic
invoking packageManager, which can be very expensive.
- Remove any call to DashboardFeatureProvider from SummaryLoader, and
delay the getCategory call until someone calls setListener().
- call updateCategory() from background thread.
Test: rerun app launch test. Avg latency drops back to pre-suggestion-v2
level.
Test: robotest
Fixes: 68761512
Change-Id: I5ec85af08e7b610786e439bda93b3651f5975593
diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java
index b7c8715..8f66b67 100644
--- a/src/com/android/settings/dashboard/DashboardSummary.java
+++ b/src/com/android/settings/dashboard/DashboardSummary.java
@@ -24,6 +24,7 @@
import android.os.Handler;
import android.service.settings.suggestions.Suggestion;
import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
import android.support.v7.widget.LinearLayoutManager;
import android.util.Log;
import android.view.LayoutInflater;
@@ -50,6 +51,7 @@
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.suggestions.SuggestionList;
import com.android.settingslib.suggestions.SuggestionParser;
+import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.List;
@@ -182,12 +184,6 @@
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.dashboard, container, false);
- }
-
- @Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mLayoutManager == null) return;
@@ -198,9 +194,10 @@
}
@Override
- public void onViewCreated(View view, Bundle bundle) {
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
long startTime = System.currentTimeMillis();
- mDashboard = view.findViewById(R.id.dashboard_container);
+ final View root = inflater.inflate(R.layout.dashboard, container, false);
+ mDashboard = root.findViewById(R.id.dashboard_container);
mLayoutManager = new LinearLayoutManager(getContext());
mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
if (bundle != null) {
@@ -218,19 +215,19 @@
mSummaryLoader.setSummaryConsumer(mAdapter);
ActionBarShadowController.attachToRecyclerView(
getActivity().findViewById(R.id.search_bar_container), getLifecycle(), mDashboard);
-
+ rebuildUI();
if (DEBUG_TIMING) {
- Log.d(TAG, "onViewCreated took "
+ Log.d(TAG, "onCreateView took "
+ (System.currentTimeMillis() - startTime) + " ms");
}
- rebuildUI();
+ return root;
}
@VisibleForTesting
void rebuildUI() {
if (!mSuggestionFeatureProvider.isSuggestionEnabled(getContext())) {
Log.d(TAG, "Suggestion v1 feature is disabled, skipping suggestion v1");
- updateCategory();
+ ThreadUtils.postOnBackgroundThread(() -> updateCategory());
} else {
new SuggestionLoader().execute();
// Set categories on their own if loading suggestions takes too long.
@@ -340,11 +337,12 @@
}
}
+ @WorkerThread
void updateCategory() {
final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(
CategoryKey.CATEGORY_HOMEPAGE);
mSummaryLoader.updateSummaryToCache(category);
- mAdapter.setCategory(category);
+ ThreadUtils.postOnMainThread(() -> mAdapter.setCategory(category));
}
/**
diff --git a/src/com/android/settings/dashboard/SummaryLoader.java b/src/com/android/settings/dashboard/SummaryLoader.java
index 5816bba8..fe55be8 100644
--- a/src/com/android/settings/dashboard/SummaryLoader.java
+++ b/src/com/android/settings/dashboard/SummaryLoader.java
@@ -60,23 +60,6 @@
private boolean mWorkerListening;
private ArraySet<BroadcastReceiver> mReceivers = new ArraySet<>();
- public SummaryLoader(Activity activity, List<DashboardCategory> categories) {
- mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
- .getDashboardFeatureProvider(activity);
- mCategoryKey = null;
- mWorkerThread = new HandlerThread("SummaryLoader", Process.THREAD_PRIORITY_BACKGROUND);
- mWorkerThread.start();
- mWorker = new Worker(mWorkerThread.getLooper());
- mActivity = activity;
- for (int i = 0; i < categories.size(); i++) {
- List<Tile> tiles = categories.get(i).tiles;
- for (int j = 0; j < tiles.size(); j++) {
- Tile tile = tiles.get(j);
- mWorker.obtainMessage(Worker.MSG_GET_PROVIDER, tile).sendToTarget();
- }
- }
- }
-
public SummaryLoader(Activity activity, String categoryKey) {
mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
.getDashboardFeatureProvider(activity);
@@ -85,17 +68,6 @@
mWorkerThread.start();
mWorker = new Worker(mWorkerThread.getLooper());
mActivity = activity;
-
- final DashboardCategory category =
- mDashboardFeatureProvider.getTilesForCategory(categoryKey);
- if (category == null || category.tiles == null) {
- return;
- }
-
- List<Tile> tiles = category.tiles;
- for (Tile tile : tiles) {
- mWorker.obtainMessage(Worker.MSG_GET_PROVIDER, tile).sendToTarget();
- }
}
public void release() {
@@ -153,15 +125,32 @@
* Only call from the main thread.
*/
public void setListening(boolean listening) {
- if (mListening == listening) return;
+ if (mListening == listening) {
+ return;
+ }
mListening = listening;
// Unregister listeners immediately.
for (int i = 0; i < mReceivers.size(); i++) {
mActivity.unregisterReceiver(mReceivers.valueAt(i));
}
mReceivers.clear();
+
mWorker.removeMessages(Worker.MSG_SET_LISTENING);
- mWorker.obtainMessage(Worker.MSG_SET_LISTENING, listening ? 1 : 0, 0).sendToTarget();
+ if (!listening) {
+ // Stop listen
+ mWorker.obtainMessage(Worker.MSG_SET_LISTENING, 0 /* listening */).sendToTarget();
+ } else {
+ // Start listen
+ if (mSummaryProviderMap.isEmpty()) {
+ // Category not initialized yet, init before starting to listen
+ if (!mWorker.hasMessages(Worker.MSG_GET_CATEGORY_TILES_AND_SET_LISTENING)) {
+ mWorker.sendEmptyMessage(Worker.MSG_GET_CATEGORY_TILES_AND_SET_LISTENING);
+ }
+ } else {
+ // Category already initialized, start listening immediately
+ mWorker.obtainMessage(Worker.MSG_SET_LISTENING, 1 /* listening */).sendToTarget();
+ }
+ }
}
private SummaryProvider getSummaryProvider(Tile tile) {
@@ -236,9 +225,13 @@
}
private synchronized void setListeningW(boolean listening) {
- if (mWorkerListening == listening) return;
+ if (mWorkerListening == listening) {
+ return;
+ }
mWorkerListening = listening;
- if (DEBUG) Log.d(TAG, "Listening " + listening);
+ if (DEBUG) {
+ Log.d(TAG, "Listening " + listening);
+ }
for (SummaryProvider p : mSummaryProviderMap.keySet()) {
try {
p.setListening(listening);
@@ -271,7 +264,6 @@
}
-
public interface SummaryProvider {
void setListening(boolean listening);
}
@@ -285,8 +277,9 @@
}
private class Worker extends Handler {
- private static final int MSG_GET_PROVIDER = 1;
- private static final int MSG_SET_LISTENING = 2;
+ private static final int MSG_GET_CATEGORY_TILES_AND_SET_LISTENING = 1;
+ private static final int MSG_GET_PROVIDER = 2;
+ private static final int MSG_SET_LISTENING = 3;
public Worker(Looper looper) {
super(looper);
@@ -295,6 +288,18 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
+ case MSG_GET_CATEGORY_TILES_AND_SET_LISTENING:
+ final DashboardCategory category =
+ mDashboardFeatureProvider.getTilesForCategory(mCategoryKey);
+ if (category == null || category.tiles == null) {
+ return;
+ }
+ final List<Tile> tiles = category.tiles;
+ for (Tile tile : tiles) {
+ makeProviderW(tile);
+ }
+ setListeningW(true);
+ break;
case MSG_GET_PROVIDER:
Tile tile = (Tile) msg.obj;
makeProviderW(tile);
diff --git a/tests/robotests/src/com/android/settings/dashboard/SummaryLoaderTest.java b/tests/robotests/src/com/android/settings/dashboard/SummaryLoaderTest.java
index 146be9c..44b6139 100644
--- a/tests/robotests/src/com/android/settings/dashboard/SummaryLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/SummaryLoaderTest.java
@@ -16,6 +16,10 @@
package com.android.settings.dashboard;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@@ -23,6 +27,7 @@
import com.android.settings.TestConfig;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.drawer.CategoryKey;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
@@ -35,12 +40,6 @@
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.when;
-
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SummaryLoaderTest {
@@ -65,14 +64,14 @@
mCallbackInvoked = false;
final Activity activity = Robolectric.buildActivity(Activity.class).get();
- final List<DashboardCategory> categories = new ArrayList<>();
- mSummaryLoader = new SummaryLoader(activity, categories);
- mSummaryLoader.setSummaryConsumer(new SummaryLoader.SummaryConsumer() {
- @Override
- public void notifySummaryChanged(Tile tile) {
- mCallbackInvoked = true;
- }
- });
+
+ mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE);
+ mSummaryLoader.setSummaryConsumer(tile -> mCallbackInvoked = true);
+ }
+
+ @Test
+ public void newInstance_shouldNotLoadCategory() {
+ verifyZeroInteractions(mFeatureFactory.dashboardFeatureProvider);
}
@Test