问题现象
触发场景
- 应用最后一个Activity退出;
- activity
android:launchMode="singleInstance"
; - 其他Task stack清空的场景;
问题原因
在LeakCanary中已经给出了答案,在Android Q中android.app.IRequestFinishCallback$Stub
,导致内存泄漏;
具体看下Android Q (Api 29) 的 Activity
源码:
public void onBackPressed() {
if (mActionBar != null && mActionBar.collapseActionView()) {
return;
}
FragmentManager fragmentManager = mFragments.getFragmentManager();
if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) {
return;
}
if (!isTaskRoot()) {
// If the activity is not the root of the task, allow finish to proceed normally.
finishAfterTransition();
return;
}
try {
// Inform activity task manager that the activity received a back press
// while at the root of the task. This call allows ActivityTaskManager
// to intercept or defer finishing.
ActivityTaskManager.getService().onBackPressedOnTaskRoot(mToken,
new IRequestFinishCallback.Stub() {
public void requestFinish() {
mHandler.post(() -> finishAfterTransition());
}
});
} catch (RemoteException e) {
finishAfterTransition();
}
}
这个内存泄漏小学生都知道:mHandler.post(() -> finishAfterTransition());
Android R 的Bugfix
Android R (Api 30)的中解决方案,和众人皆知的Handler内存泄漏解决办法一样,改为静态内部类,引用改为弱引用;
private static final class RequestFinishCallback extends IRequestFinishCallback.Stub {
private final WeakReference<Activity> mActivityRef;
RequestFinishCallback(WeakReference<Activity> activityRef) {
mActivityRef = activityRef;
}
@Override
public void requestFinish() {
Activity activity = mActivityRef.get();
if (activity != null) {
activity.mHandler.post(activity::finishAfterTransition);
}
}
}
public void onBackPressed() {
if (mActionBar != null && mActionBar.collapseActionView()) {
return;
}
FragmentManager fragmentManager = mFragments.getFragmentManager();
if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) {
return;
}
if (!isTaskRoot()) {
// If the activity is not the root of the task, allow finish to proceed normally.
finishAfterTransition();
return;
}
try {
// Inform activity task manager that the activity received a back press
// while at the root of the task. This call allows ActivityTaskManager
// to intercept or defer finishing.
ActivityTaskManager.getService().onBackPressedOnTaskRoot(mToken,
new RequestFinishCallback(new WeakReference<>(this)));
} catch (RemoteException e) {
finishAfterTransition();
}
}
非Android R如何解决?
- 不解决,只有Activity指定了SingleInstance或者关闭应用最后一个Activity才会出现内存泄漏,所以内存泄漏的场景很少,并且影响不大的情况下,可以选择放弃修复;
- 重写
onBackPressed()
方法,直接调用finishAfterTransition()
,但是onBackPressedDispatcher
等功能就失效了; - 我的解决方案,注释里有具体方案描述:
abstract class BaseActivity : AppCompatActivity() {
private var fallbackOnBackPressed: Runnable? = null
companion object {
@JvmStatic
val fallbackOnBackPressedField by lazy {
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.Q) {
null
} else {
// OnBackPressedDispatcher 中只有fallbackOnBackPressed是Runnable
// 这样可以避免代码混淆带来的影响
OnBackPressedDispatcher::class.java.declaredFields.find {
it.type.isAssignableFrom(Runnable::class.java)
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fixAndroidQMemoryLeak()
}
private fun fixAndroidQMemoryLeak() {
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.Q) return
fallbackOnBackPressedField?.runCatching {
isAccessible = true
// 缓存默认的 fallbackOnBackPressed,如果不是TaskRoot还可以直接用它
fallbackOnBackPressed = get(onBackPressedDispatcher) as? Runnable
if (fallbackOnBackPressed != null) {
// 替换默认的 fallbackOnBackPressed
set(onBackPressedDispatcher, Runnable {
onFallbackOnBackPressed()
})
}
fallbackOnBackPressedField?.isAccessible = false
}?.onFailure {
fallbackOnBackPressedField?.isAccessible = false
}
}
/**
* FragmentActivity等都是利用[mOnBackPressedDispatcher]addCallback完成Fragment pop,
* 并且会优先于[OnBackPressedDispatcher]#mFallbackOnBackPressed执行
*/
@RequiresApi(Build.VERSION_CODES.Q)
private fun onFallbackOnBackPressed() {
// 如果不是TaskRoot,不存在内存泄漏,执行原有函数就可以
if (!isTaskRoot) {
fallbackOnBackPressed?.run()
return
}
// actionBar#collapseActionView 属于私有函数,所以就不要在Activity中使用[android.app.ActionBar]
// if (actionBar != null && actionBar.collapseActionView()) return
if (!fragmentManager.isStateSaved && fragmentManager.popBackStackImmediate()) return
finishAfterTransition()
}
}