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
This commit is contained in:
Fan Zhang
2017-11-01 14:37:49 -07:00
parent 910d452874
commit f0b027f4ce
3 changed files with 64 additions and 62 deletions

View File

@@ -24,6 +24,7 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.service.settings.suggestions.Suggestion; import android.service.settings.suggestions.Suggestion;
import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -50,6 +51,7 @@ import com.android.settingslib.drawer.SettingsDrawerActivity.CategoryListener;
import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.Tile;
import com.android.settingslib.suggestions.SuggestionList; import com.android.settingslib.suggestions.SuggestionList;
import com.android.settingslib.suggestions.SuggestionParser; import com.android.settingslib.suggestions.SuggestionParser;
import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -181,12 +183,6 @@ public class DashboardSummary extends InstrumentedFragment
} }
} }
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.dashboard, container, false);
}
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
@@ -198,9 +194,10 @@ public class DashboardSummary extends InstrumentedFragment
} }
@Override @Override
public void onViewCreated(View view, Bundle bundle) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
long startTime = System.currentTimeMillis(); 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 = new LinearLayoutManager(getContext());
mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
if (bundle != null) { if (bundle != null) {
@@ -218,19 +215,19 @@ public class DashboardSummary extends InstrumentedFragment
mSummaryLoader.setSummaryConsumer(mAdapter); mSummaryLoader.setSummaryConsumer(mAdapter);
ActionBarShadowController.attachToRecyclerView( ActionBarShadowController.attachToRecyclerView(
getActivity().findViewById(R.id.search_bar_container), getLifecycle(), mDashboard); getActivity().findViewById(R.id.search_bar_container), getLifecycle(), mDashboard);
rebuildUI();
if (DEBUG_TIMING) { if (DEBUG_TIMING) {
Log.d(TAG, "onViewCreated took " Log.d(TAG, "onCreateView took "
+ (System.currentTimeMillis() - startTime) + " ms"); + (System.currentTimeMillis() - startTime) + " ms");
} }
rebuildUI(); return root;
} }
@VisibleForTesting @VisibleForTesting
void rebuildUI() { void rebuildUI() {
if (!mSuggestionFeatureProvider.isSuggestionEnabled(getContext())) { if (!mSuggestionFeatureProvider.isSuggestionEnabled(getContext())) {
Log.d(TAG, "Suggestion v1 feature is disabled, skipping suggestion v1"); Log.d(TAG, "Suggestion v1 feature is disabled, skipping suggestion v1");
updateCategory(); ThreadUtils.postOnBackgroundThread(() -> updateCategory());
} else { } else {
new SuggestionLoader().execute(); new SuggestionLoader().execute();
// Set categories on their own if loading suggestions takes too long. // Set categories on their own if loading suggestions takes too long.
@@ -340,11 +337,12 @@ public class DashboardSummary extends InstrumentedFragment
} }
} }
@WorkerThread
void updateCategory() { void updateCategory() {
final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory( final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(
CategoryKey.CATEGORY_HOMEPAGE); CategoryKey.CATEGORY_HOMEPAGE);
mSummaryLoader.updateSummaryToCache(category); mSummaryLoader.updateSummaryToCache(category);
mAdapter.setCategory(category); ThreadUtils.postOnMainThread(() -> mAdapter.setCategory(category));
} }
/** /**

View File

@@ -60,23 +60,6 @@ public class SummaryLoader {
private boolean mWorkerListening; private boolean mWorkerListening;
private ArraySet<BroadcastReceiver> mReceivers = new ArraySet<>(); 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) { public SummaryLoader(Activity activity, String categoryKey) {
mDashboardFeatureProvider = FeatureFactory.getFactory(activity) mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
.getDashboardFeatureProvider(activity); .getDashboardFeatureProvider(activity);
@@ -85,17 +68,6 @@ public class SummaryLoader {
mWorkerThread.start(); mWorkerThread.start();
mWorker = new Worker(mWorkerThread.getLooper()); mWorker = new Worker(mWorkerThread.getLooper());
mActivity = activity; 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() { public void release() {
@@ -153,15 +125,32 @@ public class SummaryLoader {
* Only call from the main thread. * Only call from the main thread.
*/ */
public void setListening(boolean listening) { public void setListening(boolean listening) {
if (mListening == listening) return; if (mListening == listening) {
return;
}
mListening = listening; mListening = listening;
// Unregister listeners immediately. // Unregister listeners immediately.
for (int i = 0; i < mReceivers.size(); i++) { for (int i = 0; i < mReceivers.size(); i++) {
mActivity.unregisterReceiver(mReceivers.valueAt(i)); mActivity.unregisterReceiver(mReceivers.valueAt(i));
} }
mReceivers.clear(); mReceivers.clear();
mWorker.removeMessages(Worker.MSG_SET_LISTENING); 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) { private SummaryProvider getSummaryProvider(Tile tile) {
@@ -236,9 +225,13 @@ public class SummaryLoader {
} }
private synchronized void setListeningW(boolean listening) { private synchronized void setListeningW(boolean listening) {
if (mWorkerListening == listening) return; if (mWorkerListening == listening) {
return;
}
mWorkerListening = listening; mWorkerListening = listening;
if (DEBUG) Log.d(TAG, "Listening " + listening); if (DEBUG) {
Log.d(TAG, "Listening " + listening);
}
for (SummaryProvider p : mSummaryProviderMap.keySet()) { for (SummaryProvider p : mSummaryProviderMap.keySet()) {
try { try {
p.setListening(listening); p.setListening(listening);
@@ -271,7 +264,6 @@ public class SummaryLoader {
} }
public interface SummaryProvider { public interface SummaryProvider {
void setListening(boolean listening); void setListening(boolean listening);
} }
@@ -285,8 +277,9 @@ public class SummaryLoader {
} }
private class Worker extends Handler { private class Worker extends Handler {
private static final int MSG_GET_PROVIDER = 1; private static final int MSG_GET_CATEGORY_TILES_AND_SET_LISTENING = 1;
private static final int MSG_SET_LISTENING = 2; private static final int MSG_GET_PROVIDER = 2;
private static final int MSG_SET_LISTENING = 3;
public Worker(Looper looper) { public Worker(Looper looper) {
super(looper); super(looper);
@@ -295,6 +288,18 @@ public class SummaryLoader {
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
switch (msg.what) { 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: case MSG_GET_PROVIDER:
Tile tile = (Tile) msg.obj; Tile tile = (Tile) msg.obj;
makeProviderW(tile); makeProviderW(tile);

View File

@@ -16,6 +16,10 @@
package com.android.settings.dashboard; 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.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -23,6 +27,7 @@ import android.content.Intent;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.drawer.CategoryKey;
import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.Tile;
@@ -35,12 +40,6 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric; import org.robolectric.Robolectric;
import org.robolectric.annotation.Config; 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) @RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SummaryLoaderTest { public class SummaryLoaderTest {
@@ -65,14 +64,14 @@ public class SummaryLoaderTest {
mCallbackInvoked = false; mCallbackInvoked = false;
final Activity activity = Robolectric.buildActivity(Activity.class).get(); final Activity activity = Robolectric.buildActivity(Activity.class).get();
final List<DashboardCategory> categories = new ArrayList<>();
mSummaryLoader = new SummaryLoader(activity, categories); mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE);
mSummaryLoader.setSummaryConsumer(new SummaryLoader.SummaryConsumer() { mSummaryLoader.setSummaryConsumer(tile -> mCallbackInvoked = true);
@Override
public void notifySummaryChanged(Tile tile) {
mCallbackInvoked = true;
} }
});
@Test
public void newInstance_shouldNotLoadCategory() {
verifyZeroInteractions(mFeatureFactory.dashboardFeatureProvider);
} }
@Test @Test