VoiceInteractor内存泄漏问题求助:Activity销毁后仍存在泄漏
VoiceInteractor 引发Activity销毁后内存泄漏问题
问题场景
Activity获取焦点后执行startLocalVoiceInteraction(Bundle.EMPTY),按下返回键销毁Activity,onLocalVoiceInteractionStopped方法已回调,但LeakCanary提示存在内存泄漏,期望Activity销毁后无内存泄漏。
LeakCanary泄漏分析结果
┬─── │ GC Root: Global variable in native code │ ├─ android.app.VoiceInteractor$2 instance │ Leaking: UNKNOWN │ Retaining 7.7 kB in 202 objects │ Anonymous subclass of com.android.internal.app. │ IVoiceInteractorCallback$Stub │ ↓ VoiceInteractor$2.this$0 │ ~~~~~~ ├─ android.app.VoiceInteractor instance │ Leaking: UNKNOWN │ Retaining 7.1 kB in 201 objects │ mActivity instance of com.test.media.ui.activity.RemoteViewActivity │ with mDestroyed = true │ mContext instance of com.test.media.ui.activity.RemoteViewActivity │ with mDestroyed = true │ ↓ VoiceInteractor.mActivity │ ~~~~~~~~~ ╰→ com.test.media.ui.activity.RemoteViewActivity instance Leaking: YES (ObjectWatcher监测到此对象是因为com.android.car.media.ui.activity.RemoteViewActivity已收到Activity#onDestroy()回调且Activity#mDestroyed为true) Retaining 99.6 kB in 1639 objects key = 73f5a0e0-3120-4b30-8871-630974629f74 watchDurationMillis = 37297 retainedDurationMillis = 32296 mApplication instance of com.test.media.AudioUniverseApplication mBase instance of androidx.appcompat.view.ContextThemeWrapper METADATA Build.VERSION.SDK_INT: 33 Build.MANUFACTURER: Google LeakCanary version: 2.12 App process name: com.test.media Class count: 26345 Instance count: 225076 Primitive array count: 141328 Object array count: 33476 Thread count: 44 Heap total bytes: 59606146 Bitmap count: 35 Bitmap total bytes: 31128459 Large bitmap count: 0 Large bitmap total bytes: 0 Db 1: open /data/user/0/com.test.media/no_backup/androidx.work.workdb Db 2: open /data/user/0/com.test.media/databases/leaks.db Stats: LruCache[maxSize=3000,hits=121354,misses=229641,hitRate=34%] RandomAccess[bytes=11694491,reads=229641,travel=74637550168,range=34466142,size= 73324879] Analysis duration: 3654 ms
复现代码
class VoiceInteractionTestActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_remote_view) } override fun onResume() { super.onResume() if (isLocalVoiceInteractionSupported && !isVoiceInteraction) { startLocalVoiceInteraction(Bundle()) lifecycleScope.launch { delay(1000) voiceInteractor.submitRequest(commandRequest) } } } private val commandRequest = object : VoiceInteractor.CommandRequest("command", Bundle()) { override fun onCommandResult(isCompleted: Boolean, result: Bundle?) { super.onCommandResult(isCompleted, result) } } }
Activity配置
<activity android:name=".ui.activity.VoiceInteractionTestActivity" android:launchMode="singleTask" />
另一次泄漏分析结果
┬─── │ GC Root: Global variable in native code │ ├─ android.app.VoiceInteractor$2 instance │ Leaking: UNKNOWN │ Retaining 56.8 kB in 964 objects │ Anonymous subclass of com.android.internal.app. │ IVoiceInteractorCallback$Stub │ ↓ VoiceInteractor$2.this$0 │ ~~~~~~ ├─ android.app.VoiceInteractor instance │ Leaking: UNKNOWN │ Retaining 56.3 kB in 963 objects │ mActivity instance of com.test.media.ui.activity. │ VoiceInteractionTestActivity with mDestroyed = true │ mContext instance of com.test.media.ui.activity. │ VoiceInteractionTestActivity with mDestroyed = true │ ↓ VoiceInteractor.mActivity │ ~~~~~~~~~ ╰→ com.test.media.ui.activity.VoiceInteractionTestActivity instance Leaking: YES (ObjectWatcher监测到此对象是因为com.android.car.media.ui.activity.VoiceInteractionTestActivity已收到Activity#onDestroy()回调且Activity#mDestroyed为true) Retaining 55.0 kB in 950 objects key = 7332a37b-0bec-4d77-a377-45b18e072f06 watchDurationMillis = 18182 retainedDurationMillis = 13181 mApplication instance of com.test.media.AudioUniverseApplication mBase instance of androidx.appcompat.view.ContextThemeWrapper METADATA Build.VERSION.SDK_INT: 33 Build.MANUFACTURER: Google LeakCanary version: 2.12 App process name: com.test.media Class count: 25489 Instance count: 221110 Primitive array count: 139050 Object array count: 33223 Thread count: 39 Heap total bytes: 58513000 Bitmap count: 28 Bitmap total bytes: 19651696 Large bitmap count: 0 Large bitmap total bytes: 0 Db 1: open /data/user/0/com.test.media/no_backup/androidx.work.workdb Stats: LruCache[maxSize=3000,hits=120002,misses=226044,hitRate=34%] RandomAccess[bytes=11679539,reads=226044,travel=69732047202,range=33728319,size= 72013645] Analysis duration: 3858 ms
问题分析与解决方案
问题根源
从泄漏链可见:Native层全局变量持有VoiceInteractor$2(IVoiceInteractorCallback$Stub的匿名子类),该内部类强引用VoiceInteractor实例,而VoiceInteractor的mActivity字段仍指向已销毁的Activity,导致Activity无法被GC回收。即使onLocalVoiceInteractionStopped回调,系统内部可能存在引用清理延迟。
解决方案
- 主动停止语音交互并清理请求
在ActivityonDestroy中主动调用stopLocalVoiceInteraction(),同时取消所有提交的请求,强制系统解除绑定:
override fun onDestroy() { super.onDestroy() stopLocalVoiceInteraction() commandRequest?.let { voiceInteractor?.cancelRequest(it) } commandRequest = null }
- 用弱引用避免Activity被强持有
修改CommandRequest,通过弱引用关联Activity,防止匿名内部类直接持有Activity强引用:
private val commandRequest = object : VoiceInteractor.CommandRequest("command", Bundle()) { private val activityWeakRef = WeakReference(this@VoiceInteractionTestActivity) override fun onCommandResult(isCompleted: Boolean, result: Bundle?) { super.onCommandResult(isCompleted, result) val activity = activityWeakRef.get() ?: return // 后续业务逻辑 } }
- 适配singleTask启动模式
由于Activity使用singleTask启动模式,需在onNewIntent中检查语音交互状态,避免重复启动导致的引用残留:
override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) if (isVoiceInteraction) { stopLocalVoiceInteraction() } }
补充说明
该问题在Android 33版本上已有不少开发者反馈,属于系统层面潜在bug。若上述方案无法彻底解决,可关注系统更新修复进展。
内容的提问来源于stack exchange,提问作者Dong Leif
相关产品推荐
相关产品推荐

