Android中InputConnectionWrapper未被Finalize引发内存泄漏的原因是什么?
这问题我之前在项目里也碰到过,确实挺头疼的——明明已经把EditText从ViewGroup里移除了,堆里却还躺着一堆InputConnectionWrapper,而且还不是当前显示的那个EditText创建的。结合Android输入系统的工作机制,我梳理几个最可能的原因:
1. IME(输入法)未及时释放旧的InputConnection引用
Android的输入法和输入组件(比如EditText)之间是通过InputConnection来通信的。当你调用removeAllViews移除EditText时,如果这个EditText当时还处于获取焦点的状态,或者IME还没完成断开连接的流程,输入法进程会依然持有旧的InputConnectionWrapper强引用。因为跨进程的引用GC是没法自动处理的,这些对象就会一直留在内存里,直到IME主动释放或者进程重启。
2. 自定义InputConnectionWrapper的引用泄漏
如果你为了做输入拦截、格式限制等需求,自己实现了InputConnectionWrapper,那很可能是你在这个Wrapper里持有了Activity、Fragment或者ViewGroup的强引用(比如在构造方法里传入了上下文,或者持有了某个回调接口)。哪怕你移除了EditText,这个Wrapper因为被IME或者系统输入框架持有,同时它又握着你的页面组件引用,就形成了完整的泄漏链,GC根本收不掉。
3. ViewGroup移除操作的时机与生命周期不匹配
removeAllViews只是把View从ViewGroup的子View列表里移除,但EditText的InputConnection其实是和ViewRootImpl绑定的——而ViewRootImpl是和Window关联的。如果你的Fragment是在ViewPager、BottomNavigation这类容器里,可能Fragment的View已经被销毁了,但Activity的Window还存活,旧的InputConnectionWrapper可能还被ViewRootImpl或者InputMethodManager(IMM)的内部缓存持有,没法被回收。
4. 未清理EditText的关联监听器与回调
如果给EditText设置了TextWatcher、OnFocusChangeListener或者其他自定义监听器,而这些监听器又持有Activity/Fragment的强引用,那么哪怕EditText被移除,这些监听器会间接让InputConnectionWrapper被牵连——因为系统的InputConnectionWrapper会持有EditText的引用,EditText又持有监听器,监听器又握着页面组件,最终导致泄漏。
对应的解决办法
针对上面的原因,你可以按这个顺序排查修复:
- 先主动断开IME连接:在调用
removeAllViews之前,先让EditText失去焦点并隐藏输入法:editText.clearFocus() val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(editText.windowToken, 0) imm.restartInput(editText) // 强制IME重启输入连接,释放旧的Wrapper - 清理自定义Wrapper的引用:如果用了自定义InputConnectionWrapper,把里面的外部组件引用改成
WeakReference,并且在Fragment的onDestroyView或者onDetach方法里,主动清理所有相关的回调和引用。 - 移除EditText的所有监听器:在移除EditText之前,调用
editText.removeTextChangedListener()、editText.setOnFocusChangeListener(null)等方法,把所有监听器都移除或者置空,切断引用链。 - Fragment生命周期内彻底清理:在Fragment的
onDestroyView方法里,不仅调用ViewGroup的removeAllViews,还要把EditText的对象引用置为null,避免页面组件还握着它的强引用。
内容的提问来源于stack exchange,提问作者H.Kim

