Fix leaks of media session service.

References to the service are kept from MediaSessionStub
and from a long-delayed Handler messages in ConnectionTimeoutHandler.

Remove strong references from these places by making the timeout
handler static and ensuring ConnectedControllersManager only keeps
a weak reference to the service (as it's part of MediaSessionStub).

Issue: androidx/media#346
PiperOrigin-RevId: 527543396
This commit is contained in:
tonihei 2023-04-27 12:54:17 +01:00 committed by Marc Baechinger
parent e2bae0c50d
commit 8c262d6c07
3 changed files with 25 additions and 6 deletions

View File

@ -66,6 +66,9 @@
`handlePlayPauseButtonAction` to write custom UI elements with a `handlePlayPauseButtonAction` to write custom UI elements with a
play/pause button. play/pause button.
* Fix memory leak of `MediaSessionService` or `MediaLibraryService`
([#346](https://github.com/androidx/media/issues/346)).
* Audio: * Audio:
* Fix bug where some playbacks fail when tunneling is enabled and * Fix bug where some playbacks fail when tunneling is enabled and

View File

@ -26,6 +26,7 @@ import androidx.media3.session.MediaSession.ControllerInfo;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -62,14 +63,14 @@ import org.checkerframework.checker.nullness.qual.NonNull;
private final ArrayMap<ControllerInfo, ConnectedControllerRecord<T>> controllerRecords = private final ArrayMap<ControllerInfo, ConnectedControllerRecord<T>> controllerRecords =
new ArrayMap<>(); new ArrayMap<>();
private final MediaSessionImpl sessionImpl; private final WeakReference<MediaSessionImpl> sessionImpl;
public ConnectedControllersManager(MediaSessionImpl session) { public ConnectedControllersManager(MediaSessionImpl session) {
// Initialize default values. // Initialize default values.
lock = new Object(); lock = new Object();
// Initialize members with params. // Initialize members with params.
sessionImpl = session; sessionImpl = new WeakReference<>(session);
} }
public void addController( public void addController(
@ -136,6 +137,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
} }
record.sequencedFutureManager.release(); record.sequencedFutureManager.release();
@Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get();
if (sessionImpl == null || sessionImpl.isReleased()) {
return;
}
postOrRun( postOrRun(
sessionImpl.getApplicationHandler(), sessionImpl.getApplicationHandler(),
() -> { () -> {
@ -214,8 +219,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
synchronized (lock) { synchronized (lock) {
info = controllerRecords.get(controllerInfo); info = controllerRecords.get(controllerInfo);
} }
@Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get();
return info != null return info != null
&& info.playerCommands.contains(commandCode) && info.playerCommands.contains(commandCode)
&& sessionImpl != null
&& sessionImpl.getPlayerWrapper().getAvailableCommands().contains(commandCode); && sessionImpl.getPlayerWrapper().getAvailableCommands().contains(commandCode);
} }
@ -248,6 +255,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
@GuardedBy("lock") @GuardedBy("lock")
private void flushCommandQueue(ConnectedControllerRecord<T> info) { private void flushCommandQueue(ConnectedControllerRecord<T> info) {
@Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get();
if (sessionImpl == null) {
return;
}
AtomicBoolean continueRunning = new AtomicBoolean(true); AtomicBoolean continueRunning = new AtomicBoolean(true);
while (continueRunning.get()) { while (continueRunning.get()) {
continueRunning.set(false); continueRunning.set(false);

View File

@ -145,12 +145,13 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
appPackageName = context.getPackageName(); appPackageName = context.getPackageName();
sessionManager = MediaSessionManager.getSessionManager(context); sessionManager = MediaSessionManager.getSessionManager(context);
controllerLegacyCbForBroadcast = new ControllerLegacyCbForBroadcast(); controllerLegacyCbForBroadcast = new ControllerLegacyCbForBroadcast();
connectionTimeoutHandler =
new ConnectionTimeoutHandler(session.getApplicationHandler().getLooper());
mediaPlayPauseKeyHandler = mediaPlayPauseKeyHandler =
new MediaPlayPauseKeyHandler(session.getApplicationHandler().getLooper()); new MediaPlayPauseKeyHandler(session.getApplicationHandler().getLooper());
connectedControllersManager = new ConnectedControllersManager<>(session); connectedControllersManager = new ConnectedControllersManager<>(session);
connectionTimeoutMs = DEFAULT_CONNECTION_TIMEOUT_MS; connectionTimeoutMs = DEFAULT_CONNECTION_TIMEOUT_MS;
connectionTimeoutHandler =
new ConnectionTimeoutHandler(
session.getApplicationHandler().getLooper(), connectedControllersManager);
// Select a media button receiver component. // Select a media button receiver component.
ComponentName receiverComponentName = queryPackageManagerForMediaButtonReceiver(context); ComponentName receiverComponentName = queryPackageManagerForMediaButtonReceiver(context);
@ -1359,12 +1360,16 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
} }
private class ConnectionTimeoutHandler extends Handler { private static class ConnectionTimeoutHandler extends Handler {
private static final int MSG_CONNECTION_TIMED_OUT = 1001; private static final int MSG_CONNECTION_TIMED_OUT = 1001;
public ConnectionTimeoutHandler(Looper looper) { private final ConnectedControllersManager<RemoteUserInfo> connectedControllersManager;
public ConnectionTimeoutHandler(
Looper looper, ConnectedControllersManager<RemoteUserInfo> connectedControllersManager) {
super(looper); super(looper);
this.connectedControllersManager = connectedControllersManager;
} }
@Override @Override