MediaRouter: initialize GlobalMediaRouter lazily
MediaRouter.getInstance() took 16-22ms due to instantiation
of too many objects.
This CL makss it initialize lazily for some apps to reduce
initialization time.
Bug: 150662969
Test: ./gradlew mediarouter:mediarouter:connectedCheck
&& estimate time for MediaRouter.getInstance() running
w/ support v7 demos on Pixel 3XL, it reduced roughly
from 16ms to 3ms.
It also passed global TAP presubmit.
Change-Id: Idb2438daf6306efe560c746cb5386288e9a8623d
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2Provider.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2Provider.java
index 2b98060..9bb1db4 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2Provider.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2Provider.java
@@ -373,7 +373,6 @@
@Override
public void onTransfer(@NonNull MediaRouter2.RoutingController oldController,
@NonNull MediaRouter2.RoutingController newController) {
- // TODO: Call onPrepareTransfer() when the API is added.
mControllerMap.remove(oldController);
if (newController == mMediaRouter2.getSystemController()) {
mCallback.onSelectFallbackRoute(UNSELECT_REASON_ROUTE_CHANGED);
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
index 01d7ab2..9e98d05 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
@@ -139,9 +139,9 @@
public static final int UNSELECT_REASON_ROUTE_CHANGED = 3;
// Maintains global media router state for the process.
- // This field is initialized in MediaRouter.getInstance() before any
- // MediaRouter objects are instantiated so it is guaranteed to be
- // valid whenever any instance method is invoked.
+ // This field is initialized lazily when it is necessary.
+ // Access this field directly only when you don't want to initialize it.
+ // Use {@link #getGlobalRouter()} to get a valid instance.
static GlobalMediaRouter sGlobal;
// Context-bound state of the media router.
@@ -288,25 +288,41 @@
if (sGlobal == null) {
sGlobal = new GlobalMediaRouter(context.getApplicationContext());
- sGlobal.start();
}
+ // Use sGlobal directly to avoid initialization.
return sGlobal.getRouter(context);
}
/**
+ * Gets the initialized global router.
+ * Please make sure this is called in the main thread.
+ */
+ @MainThread
+ static GlobalMediaRouter getGlobalRouter() {
+ if (sGlobal == null) {
+ return null;
+ }
+ sGlobal.ensureInitialized();
+ return sGlobal;
+ }
+
+ /**
* Gets information about the {@link MediaRouter.RouteInfo routes} currently known to
* this media router.
*/
@NonNull
public List<RouteInfo> getRoutes() {
checkCallingThread();
- return sGlobal == null ? Collections.<RouteInfo>emptyList() : sGlobal.getRoutes();
+ GlobalMediaRouter globalMediaRouter = getGlobalRouter();
+ return globalMediaRouter == null ? Collections.<RouteInfo>emptyList() :
+ globalMediaRouter.getRoutes();
}
@Nullable
RouteInfo getRoute(String uniqueId) {
checkCallingThread();
- return sGlobal == null ? null : sGlobal.getRoute(uniqueId);
+ GlobalMediaRouter globalMediaRouter = getGlobalRouter();
+ return globalMediaRouter == null ? null : globalMediaRouter.getRoute(uniqueId);
}
/**
@@ -316,7 +332,9 @@
@NonNull
public List<ProviderInfo> getProviders() {
checkCallingThread();
- return sGlobal == null ? Collections.<ProviderInfo>emptyList() : sGlobal.getProviders();
+ GlobalMediaRouter globalMediaRouter = getGlobalRouter();
+ return globalMediaRouter == null ? Collections.<ProviderInfo>emptyList() :
+ globalMediaRouter.getProviders();
}
/**
@@ -330,7 +348,7 @@
@NonNull
public RouteInfo getDefaultRoute() {
checkCallingThread();
- return sGlobal.getDefaultRoute();
+ return getGlobalRouter().getDefaultRoute();
}
/**
@@ -341,7 +359,8 @@
@Nullable
public RouteInfo getBluetoothRoute() {
checkCallingThread();
- return sGlobal == null ? null : sGlobal.getBluetoothRoute();
+ GlobalMediaRouter globalMediaRouter = getGlobalRouter();
+ return globalMediaRouter == null ? null : globalMediaRouter.getBluetoothRoute();
}
/**
@@ -392,7 +411,7 @@
@NonNull
public RouteInfo getSelectedRoute() {
checkCallingThread();
- return sGlobal.getSelectedRoute();
+ return getGlobalRouter().getSelectedRoute();
}
/**
@@ -417,10 +436,11 @@
if (DEBUG) {
Log.d(TAG, "updateSelectedRoute: " + selector);
}
- RouteInfo route = sGlobal.getSelectedRoute();
+ GlobalMediaRouter globalRouter = getGlobalRouter();
+ RouteInfo route = globalRouter.getSelectedRoute();
if (!route.isDefaultOrBluetooth() && !route.matchesSelector(selector)) {
- route = sGlobal.chooseFallbackRoute();
- sGlobal.selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
+ route = globalRouter.chooseFallbackRoute();
+ globalRouter.selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
}
return route;
}
@@ -439,7 +459,7 @@
if (DEBUG) {
Log.d(TAG, "selectRoute: " + route);
}
- sGlobal.selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
+ getGlobalRouter().selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
}
/**
@@ -463,9 +483,11 @@
checkCallingThread();
// Choose the fallback route if it's not already selected.
- RouteInfo fallbackRoute = sGlobal.chooseFallbackRoute();
- if (sGlobal.getSelectedRoute() != fallbackRoute) {
- sGlobal.selectRoute(fallbackRoute, reason);
+ // Otherwise, select the default route.
+ GlobalMediaRouter globalRouter = getGlobalRouter();
+ RouteInfo fallbackRoute = globalRouter.chooseFallbackRoute();
+ if (globalRouter.getSelectedRoute() != fallbackRoute) {
+ globalRouter.selectRoute(fallbackRoute, reason);
}
}
@@ -479,7 +501,7 @@
throw new NullPointerException("route must not be null");
}
checkCallingThread();
- sGlobal.addMemberToDynamicGroup(route);
+ getGlobalRouter().addMemberToDynamicGroup(route);
}
/**
@@ -492,7 +514,7 @@
throw new NullPointerException("route must not be null");
}
checkCallingThread();
- sGlobal.removeMemberFromDynamicGroup(route);
+ getGlobalRouter().removeMemberFromDynamicGroup(route);
}
/**
@@ -505,7 +527,7 @@
throw new NullPointerException("route must not be null");
}
checkCallingThread();
- sGlobal.transferToRoute(route);
+ getGlobalRouter().transferToRoute(route);
}
/**
@@ -535,8 +557,7 @@
throw new IllegalArgumentException("selector must not be null");
}
checkCallingThread();
-
- return sGlobal.isRouteAvailable(selector, flags);
+ return getGlobalRouter().isRouteAvailable(selector, flags);
}
/**
@@ -708,7 +729,7 @@
updateNeeded = true;
}
if (updateNeeded) {
- sGlobal.updateDiscoveryRequest();
+ getGlobalRouter().updateDiscoveryRequest();
}
}
@@ -732,7 +753,7 @@
int index = findCallbackRecord(callback);
if (index >= 0) {
mCallbackRecords.remove(index);
- sGlobal.updateDiscoveryRequest();
+ getGlobalRouter().updateDiscoveryRequest();
}
}
@@ -752,7 +773,7 @@
@MainThread
public void setOnPrepareTransferListener(@Nullable OnPrepareTransferListener listener) {
checkCallingThread();
- sGlobal.mOnPrepareTransferListener = listener;
+ getGlobalRouter().mOnPrepareTransferListener = listener;
}
/**
@@ -776,7 +797,7 @@
if (DEBUG) {
Log.d(TAG, "addProvider: " + providerInstance);
}
- sGlobal.addProvider(providerInstance);
+ getGlobalRouter().addProvider(providerInstance);
}
/**
@@ -800,7 +821,7 @@
if (DEBUG) {
Log.d(TAG, "removeProvider: " + providerInstance);
}
- sGlobal.removeProvider(providerInstance);
+ getGlobalRouter().removeProvider(providerInstance);
}
/**
@@ -823,7 +844,7 @@
if (DEBUG) {
Log.d(TAG, "addRemoteControlClient: " + remoteControlClient);
}
- sGlobal.addRemoteControlClient(remoteControlClient);
+ getGlobalRouter().addRemoteControlClient(remoteControlClient);
}
/**
@@ -836,11 +857,12 @@
if (remoteControlClient == null) {
throw new IllegalArgumentException("remoteControlClient must not be null");
}
+ checkCallingThread();
if (DEBUG) {
Log.d(TAG, "removeRemoteControlClient: " + remoteControlClient);
}
- sGlobal.removeRemoteControlClient(remoteControlClient);
+ getGlobalRouter().removeRemoteControlClient(remoteControlClient);
}
/**
@@ -852,10 +874,11 @@
* @param mediaSession The {@link android.media.session.MediaSession} to use.
*/
public void setMediaSession(@Nullable Object mediaSession) {
+ checkCallingThread();
if (DEBUG) {
Log.d(TAG, "setMediaSession: " + mediaSession);
}
- sGlobal.setMediaSession(mediaSession);
+ getGlobalRouter().setMediaSession(mediaSession);
}
/**
@@ -867,15 +890,17 @@
* @param mediaSession The {@link MediaSessionCompat} to use.
*/
public void setMediaSessionCompat(@Nullable MediaSessionCompat mediaSession) {
+ checkCallingThread();
if (DEBUG) {
Log.d(TAG, "setMediaSessionCompat: " + mediaSession);
}
- sGlobal.setMediaSessionCompat(mediaSession);
+ getGlobalRouter().setMediaSessionCompat(mediaSession);
}
@Nullable
public MediaSessionCompat.Token getMediaSessionToken() {
return sGlobal == null ? null : sGlobal.getMediaSessionToken();
+ // Use sGlobal exceptionally due to unchecked thread.
}
/**
@@ -885,7 +910,8 @@
@Nullable
public MediaRouterParams getRouterParams() {
checkCallingThread();
- return sGlobal == null ? null : sGlobal.getRouterParams();
+ GlobalMediaRouter globalMediaRouter = getGlobalRouter();
+ return globalMediaRouter == null ? null : globalMediaRouter.getRouterParams();
}
/**
@@ -896,7 +922,7 @@
*/
public void setRouterParams(@Nullable MediaRouterParams params) {
checkCallingThread();
- sGlobal.setRouterParams(params);
+ getGlobalRouter().setRouterParams(params);
}
/**
@@ -920,7 +946,7 @@
if (sGlobal == null) {
return false;
}
- return sGlobal.isMediaTransferEnabled();
+ return getGlobalRouter().isMediaTransferEnabled();
}
/**
@@ -931,14 +957,15 @@
if (sGlobal == null) {
return 0;
}
- return sGlobal.getCallbackCount();
+ return getGlobalRouter().getCallbackCount();
}
/**
* Returns whether transferring media from remote to local is enabled.
*/
static boolean isTransferToLocalEnabled() {
- return sGlobal == null ? false : sGlobal.isTransferToLocalEnabled();
+ GlobalMediaRouter globalMediaRouter = getGlobalRouter();
+ return globalMediaRouter == null ? false : globalMediaRouter.isTransferToLocalEnabled();
}
/**
@@ -1216,7 +1243,7 @@
// - If this route is a selected member route of a group, it returns false.
public boolean isSelected() {
checkCallingThread();
- return sGlobal.getSelectedRoute() == this;
+ return getGlobalRouter().getSelectedRoute() == this;
}
/**
@@ -1228,7 +1255,7 @@
*/
public boolean isDefault() {
checkCallingThread();
- return sGlobal.getDefaultRoute() == this;
+ return getGlobalRouter().getDefaultRoute() == this;
}
/**
@@ -1240,7 +1267,7 @@
*/
public boolean isBluetooth() {
checkCallingThread();
- return sGlobal.getBluetoothRoute() == this;
+ return getGlobalRouter().getBluetoothRoute() == this;
}
/**
@@ -1380,7 +1407,7 @@
}
checkCallingThread();
- ContentResolver contentResolver = sGlobal.getContentResolver();
+ ContentResolver contentResolver = getGlobalRouter().getContentResolver();
int count = mControlFilters.size();
for (int i = 0; i < count; i++) {
if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) {
@@ -1414,7 +1441,7 @@
}
checkCallingThread();
- sGlobal.sendControlRequest(this, intent, callback);
+ getGlobalRouter().sendControlRequest(this, intent, callback);
}
/**
@@ -1529,7 +1556,7 @@
*/
public void requestSetVolume(int volume) {
checkCallingThread();
- sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume)));
+ getGlobalRouter().requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume)));
}
/**
@@ -1544,7 +1571,7 @@
public void requestUpdateVolume(int delta) {
checkCallingThread();
if (delta != 0) {
- sGlobal.requestUpdateVolume(this, delta);
+ getGlobalRouter().requestUpdateVolume(this, delta);
}
}
@@ -1581,7 +1608,7 @@
public Display getPresentationDisplay() {
checkCallingThread();
if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) {
- mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId);
+ mPresentationDisplay = getGlobalRouter().getDisplay(mPresentationDisplayId);
}
return mPresentationDisplay;
}
@@ -1618,7 +1645,7 @@
*/
public void select() {
checkCallingThread();
- sGlobal.selectRoute(this, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
+ getGlobalRouter().selectRoute(this, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
}
/**
@@ -1666,8 +1693,9 @@
@RestrictTo(LIBRARY)
@Nullable
public DynamicGroupRouteController getDynamicGroupController() {
+ checkCallingThread();
//TODO: handle multiple controllers case
- RouteController controller = sGlobal.mSelectedRouteController;
+ RouteController controller = getGlobalRouter().mSelectedRouteController;
if (controller instanceof DynamicGroupRouteController) {
return (DynamicGroupRouteController) controller;
}
@@ -1847,13 +1875,17 @@
if (groupMemberIds.size() != mMemberRoutes.size()) {
memberChanged = true;
}
- for (String groupMemberId : groupMemberIds) {
- String uniqueId = sGlobal.getUniqueId(getProvider(), groupMemberId);
- RouteInfo groupMember = sGlobal.getRoute(uniqueId);
- if (groupMember != null) {
- routes.add(groupMember);
- if (!memberChanged && !mMemberRoutes.contains(groupMember)) {
- memberChanged = true;
+ //TODO: Clean this up not to reference the global router
+ if (!groupMemberIds.isEmpty()) {
+ GlobalMediaRouter globalRouter = getGlobalRouter();
+ for (String groupMemberId : groupMemberIds) {
+ String uniqueId = globalRouter.getUniqueId(getProvider(), groupMemberId);
+ RouteInfo groupMember = globalRouter.getRoute(uniqueId);
+ if (groupMember != null) {
+ routes.add(groupMember);
+ if (!memberChanged && !mMemberRoutes.contains(groupMember)) {
+ memberChanged = true;
+ }
}
}
}
@@ -1896,7 +1928,7 @@
mMemberRoutes.add(route);
}
}
- sGlobal.mCallbackHandler.post(
+ getGlobalRouter().mCallbackHandler.post(
GlobalMediaRouter.CallbackHandler.MSG_ROUTE_CHANGED, this);
}
@@ -1970,8 +2002,6 @@
private final ProviderMetadata mMetadata;
private MediaRouteProviderDescriptor mDescriptor;
- private Resources mResources;
- private boolean mResourcesNotAvailable;
ProviderInfo(MediaRouteProvider provider) {
mProviderInstance = provider;
@@ -2012,21 +2042,6 @@
return Collections.unmodifiableList(mRoutes);
}
- Resources getResources() {
- if (mResources == null && !mResourcesNotAvailable) {
- String packageName = getPackageName();
- Context context = sGlobal.getProviderContext(packageName);
- if (context != null) {
- mResources = context.getResources();
- } else {
- Log.w(TAG, "Unable to obtain resources for route provider package: "
- + packageName);
- mResourcesNotAvailable = true;
- }
- }
- return mResources;
- }
-
boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) {
if (mDescriptor != descriptor) {
mDescriptor = descriptor;
@@ -2356,8 +2371,14 @@
implements SystemMediaRouteProvider.SyncCallback,
RegisteredMediaRouteProviderWatcher.Callback {
final Context mApplicationContext;
- final boolean mMediaTransferEnabled;
- final MediaRoute2Provider mMr2Provider;
+ boolean mIsInitialized;
+
+ SystemMediaRouteProvider mSystemProvider;
+ @VisibleForTesting
+ RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher;
+ boolean mMediaTransferEnabled;
+ MediaRoute2Provider mMr2Provider;
+
final ArrayList<WeakReference<MediaRouter>> mRouters = new ArrayList<>();
private final ArrayList<RouteInfo> mRoutes = new ArrayList<>();
private final Map<Pair<String, String>, String> mUniqueIdMap = new HashMap<>();
@@ -2368,22 +2389,12 @@
new RemoteControlClientCompat.PlaybackInfo();
private final ProviderCallback mProviderCallback = new ProviderCallback();
final CallbackHandler mCallbackHandler = new CallbackHandler();
- private final DisplayManagerCompat mDisplayManager;
- final SystemMediaRouteProvider mSystemProvider;
+ private DisplayManagerCompat mDisplayManager;
private final boolean mLowRam;
- private MediaRouterActiveScanThrottlingHelper mActiveScanThrottlingHelper =
- new MediaRouterActiveScanThrottlingHelper(
- new Runnable() {
- @Override
- public void run() {
- updateDiscoveryRequest();
- }
- });
+ private MediaRouterActiveScanThrottlingHelper mActiveScanThrottlingHelper;
private MediaRouterParams mRouterParams;
- @VisibleForTesting
- RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher;
- private RouteInfo mDefaultRoute;
+ RouteInfo mDefaultRoute;
private RouteInfo mBluetoothRoute;
RouteInfo mSelectedRoute;
RouteController mSelectedRouteController;
@@ -2398,10 +2409,13 @@
private int mCallbackCount;
OnPrepareTransferListener mOnPrepareTransferListener;
PrepareTransferNotifier mTransferNotifier;
+ RouteInfo mTransferredRoute;
+ RouteController mTransferredRouteController;
+
private MediaSessionRecord mMediaSession;
MediaSessionCompat mRccMediaSession;
private MediaSessionCompat mCompatSession;
- private MediaSessionCompat.OnActiveChangeListener mSessionActiveListener =
+ private final MediaSessionCompat.OnActiveChangeListener mSessionActiveListener =
new MediaSessionCompat.OnActiveChangeListener() {
@Override
public void onActiveChanged() {
@@ -2415,13 +2429,19 @@
}
};
- @SuppressLint({"SyntheticAccessor", "NewApi"})
GlobalMediaRouter(Context applicationContext) {
mApplicationContext = applicationContext;
- mDisplayManager = DisplayManagerCompat.getInstance(applicationContext);
mLowRam = ActivityManagerCompat.isLowRamDevice(
(ActivityManager)applicationContext.getSystemService(
Context.ACTIVITY_SERVICE));
+ }
+
+ @SuppressLint({"NewApi", "SyntheticAccessor"})
+ void ensureInitialized() {
+ if (mIsInitialized) {
+ return;
+ }
+ mIsInitialized = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
mMediaTransferEnabled = MediaTransferReceiver.isDeclared(mApplicationContext);
@@ -2435,13 +2455,23 @@
} else {
mMr2Provider = null;
}
+
// Add the system media route provider for interoperating with
// the framework media router. This one is special and receives
// synchronization messages from the media router.
- mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this);
+ mSystemProvider = SystemMediaRouteProvider.obtain(mApplicationContext, this);
+ start();
}
- public void start() {
+ private void start() {
+ // Using lambda would break some apps.
+ mActiveScanThrottlingHelper = new MediaRouterActiveScanThrottlingHelper(
+ new Runnable() {
+ @Override
+ public void run() {
+ updateDiscoveryRequest();
+ }
+ });
addProvider(mSystemProvider);
if (mMr2Provider != null) {
addProvider(mMr2Provider);
@@ -2486,6 +2516,9 @@
}
public Display getDisplay(int displayId) {
+ if (mDisplayManager == null) {
+ mDisplayManager = DisplayManagerCompat.getInstance(mApplicationContext);
+ }
return mDisplayManager.getDisplay(displayId);
}
@@ -3467,7 +3500,7 @@
}
}
- private final class Mr2ProviderCallback extends MediaRoute2Provider.Callback {
+ final class Mr2ProviderCallback extends MediaRoute2Provider.Callback {
@Override
public void onSelectRoute(@NonNull String routeDescriptorId,
@UnselectReason int reason) {
@@ -3703,7 +3736,7 @@
}
// Using Pair<RouteInfo, RouteInfo>
- @SuppressWarnings({"unchecked", "SyntheticAccessor"})
+ @SuppressWarnings({"unchecked"})
private void syncWithSystemProvider(int what, Object obj) {
switch (what) {
case MSG_ROUTE_ADDED:
@@ -3843,7 +3876,7 @@
mMemberRoutes = (memberRoutes == null) ? null : new ArrayList<>(memberRoutes);
// For the case it's not handled properly
- router.mCallbackHandler.postDelayed(() -> this.finishTransfer(),
+ router.mCallbackHandler.postDelayed(this::finishTransfer,
TRANSFER_TIMEOUT_MS);
}
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterActiveScanThrottlingHelper.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterActiveScanThrottlingHelper.java
index 4e05ca5..e1862ae 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterActiveScanThrottlingHelper.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterActiveScanThrottlingHelper.java
@@ -25,7 +25,7 @@
* suppressed.
*/
class MediaRouterActiveScanThrottlingHelper {
- // The constant is package visible for tests can set it to a shorter durationaq.
+ // The constant is package visible for tests can set it to a shorter duration.
static final long MAX_ACTIVE_SCAN_DURATION_MS = 30000;
private final Handler mHandler = new Handler(Looper.getMainLooper());