Search code examples
androidmemory-leaks

Android: Memory leak in MediaProjection


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")!!
    )

See ScreenCaptureService

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: enter image description here


Solution

  • 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