It looks like there is Memory leak in Android's MediaProjection. Has anybody seen this leak? Is there some way to avoid it?
How to reproduce: just follow this manual: https://developer.android.com/guide/topics/large-screens/media-projection, and start/stop MediaProjection.
I have created demo app https://github.com/RPetrov/MediaProjectionLeakDemo
But shortly:
At first make request permission:
startActivityForResult(
mediaProjectionManager.createScreenCaptureIntent(),
REQUEST_CODE
)
See MainActivity
At second setup MediaProjection:
mediaProjection = mediaProjectionManager.getMediaProjection(
intent.getIntExtra("RESULT_CODE", 0),
intent.getParcelableExtra<Intent>("DATA")!!
)
You can just start / stop MediaProjection, and there is There is Leak Canary trace:
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
625 bytes retained by leaking objects
Displaying only 1 leak trace out of 5 with the same signature
Signature: a986e3ada7a0f3d1cc20672fc5b8f41b2c2189be
┬───
│ GC Root: Global variable in native code
│
├─ android.media.projection.MediaProjection$MediaProjectionCallback instance
│ Leaking: UNKNOWN
│ Retaining 3.2 kB in 27 objects
│ ↓ MediaProjection$MediaProjectionCallback.this$0
│ ~~~~~~
├─ android.media.projection.MediaProjection instance
│ Leaking: UNKNOWN
│ Retaining 2.7 kB in 26 objects
│ mContext instance of android.app.ContextImpl
│ ↓ MediaProjection.mContext
│ ~~~~~~~~
├─ android.app.ContextImpl instance
│ Leaking: UNKNOWN
│ Retaining 1.6 kB in 20 objects
│ mOuterContext instance of rpetrov.test.mediaprojectionleakdemo.ScreenCaptureService
│ ContextImpl.mOuterContext is an instance of rpetrov.test.mediaprojectionleakdemo.ScreenCaptureService
│ ↓ ContextImpl.mOuterContext
│ ~~~~~~~~~~~~~
╰→ rpetrov.test.mediaprojectionleakdemo.ScreenCaptureService instance
Leaking: YES (ObjectWatcher was watching this because rpetrov.test.mediaprojectionleakdemo.ScreenCaptureService
received Service#onDestroy() callback and Service not held by ActivityThread)
Retaining 125 B in 2 objects
key = 68f51a96-1353-4155-b47b-4c9adeee759e
watchDurationMillis = 5904
retainedDurationMillis = 900
mApplication instance of android.app.Application
mBase instance of android.app.ContextImpl
leak: MediaProjectionCallback -> MediaProjection -> context
MediaProjectionManager creates MediaProjection with context:
public MediaProjection getMediaProjection(int resultCode, @NonNull Intent resultData) {
if (resultCode != Activity.RESULT_OK || resultData == null) {
return null;
}
IBinder projection = resultData.getIBinderExtra(EXTRA_MEDIA_PROJECTION);
if (projection == null) {
return null;
}
return new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection));
}
MediaProjection creates MediaProjectionCallback:
public MediaProjection(Context context, IMediaProjection impl) {
mCallbacks = new ArrayMap<Callback, CallbackRecord>();
mContext = context;
mImpl = impl;
try {
mImpl.start(new MediaProjectionCallback());
} catch (RemoteException e) {
throw new RuntimeException("Failed to start media projection", e);
}
}
MediaProjectionCallback holds MediaProjection, MediaProjection holds context:
In your MainActivity, change the initialization of the MediaProjectionManager to use the Application Context instead of the Activity Context.
val mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
Should be
val mediaProjectionManager = applicationContext.getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager