diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f383948cc3..d0ea6966c1 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -66,6 +66,9 @@ `handlePlayPauseButtonAction` to write custom UI elements with a play/pause button. + * Fix memory leak of `MediaSessionService` or `MediaLibraryService` + ([#346](https://github.com/androidx/media/issues/346)). + * Audio: * Fix bug where some playbacks fail when tunneling is enabled and diff --git a/libraries/session/src/main/java/androidx/media3/session/ConnectedControllersManager.java b/libraries/session/src/main/java/androidx/media3/session/ConnectedControllersManager.java index 40e3d9619c..71efbf6d8f 100644 --- a/libraries/session/src/main/java/androidx/media3/session/ConnectedControllersManager.java +++ b/libraries/session/src/main/java/androidx/media3/session/ConnectedControllersManager.java @@ -26,6 +26,7 @@ import androidx.media3.session.MediaSession.ControllerInfo; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import java.lang.ref.WeakReference; import java.util.ArrayDeque; import java.util.Deque; import java.util.concurrent.atomic.AtomicBoolean; @@ -62,14 +63,14 @@ import org.checkerframework.checker.nullness.qual.NonNull; private final ArrayMap> controllerRecords = new ArrayMap<>(); - private final MediaSessionImpl sessionImpl; + private final WeakReference sessionImpl; public ConnectedControllersManager(MediaSessionImpl session) { // Initialize default values. lock = new Object(); // Initialize members with params. - sessionImpl = session; + sessionImpl = new WeakReference<>(session); } public void addController( @@ -136,6 +137,10 @@ import org.checkerframework.checker.nullness.qual.NonNull; } record.sequencedFutureManager.release(); + @Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get(); + if (sessionImpl == null || sessionImpl.isReleased()) { + return; + } postOrRun( sessionImpl.getApplicationHandler(), () -> { @@ -214,8 +219,10 @@ import org.checkerframework.checker.nullness.qual.NonNull; synchronized (lock) { info = controllerRecords.get(controllerInfo); } + @Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get(); return info != null && info.playerCommands.contains(commandCode) + && sessionImpl != null && sessionImpl.getPlayerWrapper().getAvailableCommands().contains(commandCode); } @@ -248,6 +255,10 @@ import org.checkerframework.checker.nullness.qual.NonNull; @GuardedBy("lock") private void flushCommandQueue(ConnectedControllerRecord info) { + @Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get(); + if (sessionImpl == null) { + return; + } AtomicBoolean continueRunning = new AtomicBoolean(true); while (continueRunning.get()) { continueRunning.set(false); diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java index cefbcbb620..1699f29c7c 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java @@ -145,12 +145,13 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; appPackageName = context.getPackageName(); sessionManager = MediaSessionManager.getSessionManager(context); controllerLegacyCbForBroadcast = new ControllerLegacyCbForBroadcast(); - connectionTimeoutHandler = - new ConnectionTimeoutHandler(session.getApplicationHandler().getLooper()); mediaPlayPauseKeyHandler = new MediaPlayPauseKeyHandler(session.getApplicationHandler().getLooper()); connectedControllersManager = new ConnectedControllersManager<>(session); connectionTimeoutMs = DEFAULT_CONNECTION_TIMEOUT_MS; + connectionTimeoutHandler = + new ConnectionTimeoutHandler( + session.getApplicationHandler().getLooper(), connectedControllersManager); // Select a media button receiver component. 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; - public ConnectionTimeoutHandler(Looper looper) { + private final ConnectedControllersManager connectedControllersManager; + + public ConnectionTimeoutHandler( + Looper looper, ConnectedControllersManager connectedControllersManager) { super(looper); + this.connectedControllersManager = connectedControllersManager; } @Override