Android MediaProjection内存泄漏求助:复现步骤与规避方法?
Android MediaProjection 内存泄漏问题及排查方案
问题概述
MediaProjection在启动并停止后,会出现内存泄漏问题,导致关联的Service无法被GC回收,LeakCanary可检测到该泄漏。泄漏链路为:Native全局变量 → MediaProjectionCallback → MediaProjection → Context → Service
复现步骤
- 遵循Android官方MediaProjection使用指引,完成启动、停止MediaProjection的流程即可复现。
- 核心操作步骤:
- 请求屏幕捕获权限:
(代码位于Demo的MainActivity类中)startActivityForResult( mediaProjectionManager.createScreenCaptureIntent(), REQUEST_CODE ) - 初始化MediaProjection:
(代码位于Demo的ScreenCaptureService类中)mediaProjection = mediaProjectionManager.getMediaProjection( intent.getIntExtra("RESULT_CODE", 0), intent.getParcelableExtra<Intent>("DATA")!! )
- 请求屏幕捕获权限:
泄漏追踪信息
LeakCanary检测到的泄漏日志如下:
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
源码分析
MediaProjectionManager创建MediaProjection的逻辑
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构造方法内部逻辑
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是MediaProjection的内部类,会持有外部类MediaProjection的强引用;MediaProjection持有传入的Context实例(此处为Service的Context);MediaProjectionCallback被Native层的全局变量引用,导致整个引用链无法被GC回收,最终Service实例泄漏。
可行规避方案
使用Application Context替代组件Context
获取MediaProjection时,传入Application Context而非Service/Activity的Context,因为Application Context的生命周期与应用一致,不会因组件销毁导致泄漏:mediaProjection = mediaProjectionManager.getMediaProjection( resultCode, resultData, applicationContext // 替换为Application Context )主动销毁MediaProjection并置空引用
在组件(如Service)的onDestroy方法中,主动调用MediaProjection.stop(),并将引用置空,切断引用链:override fun onDestroy() { super.onDestroy() mediaProjection?.stop() mediaProjection = null }避免长期持有MediaProjection引用
仅在需要屏幕捕获时持有MediaProjection实例,完成操作后立即释放,减少引用持有时间。
内容的提问来源于stack exchange,提问作者Puzirki
相关产品推荐
相关产品推荐

