在android 11的 InputMethodManager的源码中,查看 windowDismissed(),如下
public void windowDismissed(IBinder appWindowToken) {
}
/* * 以下三个字段,在低于 android 10 的版本中是都存在的;之后,有变更。 * 低于 android 10,当Activity dismissed 时,可以通过反射,将它们置null,使它们不再持有该Activity中View的引用,防止内存泄露。 * InputMethodManager#mCurRootView * InputMethodManager#mServedView * InputMethodManager#mNextServedView * * android 10之后修复了所有已知问题。 * 对于较老的版本,建议使用 androidx.activity.ComponentActivity 及其子类。 * issues/37122102,说明 android N/7.0 (api 24),就开始了修复。 * * android 11 source code:http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java * android 10 source code:http://aospxref.com/android-10.0.0_r47/xref/frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java */
解决方案:对于低于android10,且非 ComponentActivity 的context,反射获取InputMethodManager 对象中的 mCurRootView 、mServedView 、mNextServedView 这三个属性,转换为View类型后,判断view的context等于要释放的Activity的context时,将这个属性置为null。
fun fixMemoryLeak(context: Context?) {
try {
context ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) return
if (context is ComponentActivity) return
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager ?: return
imm.javaClass.declaredFields.filter {
it.name == "mCurRootView" || it.name == "mServedView" || it.name == "mNextServedView"
}.forEach { filed ->
val origin = filed.isAccessible
if (!origin) {
filed.isAccessible = true
}
(filed.get(imm) as? View)?.takeIf { it.context == context }?.also {
filed.set(imm, null)
}
filed.isAccessible = origin
}
} catch (e: Exception) {
e.printStackTrace()
}
}
|