拥有共享元素动画以及自定义输入动画会导致活动泄漏。
知道可能是什么原因?
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * com.feeln.android.activity.MovieDetailActivity has leaked:
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * GC ROOT android.app.ActivityThread$ApplicationThread.this$0
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references android.app.ActivityThread.mActivities
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references android.util.ArrayMap.mArray
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references array java.lang.Object[].[1]
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references android.app.ActivityThread$ActivityClientRecord.activity
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references com.feeln.android.activity.MovieDetailActivity.mActivityTransitionState
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references android.app.ActivityTransitionState.mEnterTransitionCoordinator
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references android.app.EnterTransitionCoordinator.mEnterViewsTransition
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references android.transition.TransitionSet.mParent
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references android.transition.TransitionSet.mListeners
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references java.util.ArrayList.array
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references array java.lang.Object[].[1]
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references android.transition.TransitionManager$MultiListener$1.val$runningTransitions (anonymous class extends android.transition.Transition$TransitionListenerAdapter)
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references android.util.ArrayMap.mArray
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references array java.lang.Object[].[2]
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * references com.android.internal.policy.impl.PhoneWindow$DecorView.mContext
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * leaks com.feeln.android.activity.MovieDetailActivity instance
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ [ 09-21 16:19:31.007 28269:31066 D/LeakCanary ]
* Reference Key: af2b6234-297e-4bab-96e9-02f1c4bca171
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * Device: LGE google Nexus 5 hammerhead
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * Android Version: 5.1.1 API: 22 LeakCanary: 1.3.1
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * Durations: watch=6785ms, gc=262ms, heap dump=8553ms, analysis=33741ms
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ [ 09-21 16:19:31.007 28269:31066 D/LeakCanary ]
要重现,您需要拥有一个大的共享图像动画,还需要一个自定义的EnterAnimation和setEnterSharedElementCallback。所有这些都来自支持库。
以下是我设置EnterTransition的方法:
private SharedElementCallback mCallback = new SharedElementCallback() {
@Override
public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
if(sharedElements.size()>0)
getWindow().setEnterTransition(makeEnterTransition(getWindow().getEnterTransition(), getSharedElement(sharedElements)));
}
}
private View getSharedElement(List<View> sharedElements)
{
for (final View view : sharedElements)
{
if (view instanceof ImageView)
{
return view;
}
}
return null;
}
};
答案 0 :(得分:15)
泄密事件发生在TransitionManager.sRunningTransitions
,DecorView
每个DecorView
添加并永不删除。 Activity
已与Context
的{{1}}相关联。由于sRunningTransitions
是静态字段,因此它具有永久性的Activity
引用链,它永远不会被GC收集。
我不知道为什么需要TransitionManager.sRunningTransitions,但是如果你从中移除Activity
的{{1}},你的问题就会得到解决。按照代码示例,怎么做。在您的活动类中:
DecorView
答案 1 :(得分:6)
@Delargo的解决方案对我不起作用。但是,我在Android问题跟踪器上偶然发现this solution,它最终为我工作。
这个想法是在使用活动转换的活动中使用以下类(适当地命名为LeakFreeSupportSharedElementCallback
,从SharedElementCallback
继承)。只需将整个班级复制到您的项目中即可。
您还需要以下类中的静态方法createDrawableBitmap(Drawable)
和createViewBitmap(View, Matrix, RectF)
。这些由LeakFreeSupportSharedElementCallback
类使用。
获得LeakFreeSupportSharedElementCallback
类设置后,将以下内容添加到使用活动转换框架的活动中:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setEnterSharedElementCallback(new LeakFreeSupportSharedElementCallback());
setExitSharedElementCallback(new LeakFreeSupportSharedElementCallback());
}
在转换动画之后,GC会释放内存。
答案 2 :(得分:0)
谢尔盖·瓦西连科(Sergei Vasilenko)与法米(Fahmy)的解决方案似乎对我来说是最有效的,但前者确实引入了崩溃事件,拉登亚克(Mladen Rakonjac)提到:
Attempt to invoke virtual method 'boolean java.util.ArrayList.remove(java.lang.Object)' on a null object reference
android.transition.TransitionManager$MultiListener$1.onTransitionEnd (TransitionManager.java:306)
之所以发生这种情况,是因为TransitionListener
中有一个TransitionManager
,试图通过使用DecorView作为键来访问正在运行的转换列表。但是,由于黑客删除了DecorView,并且此过渡过程的某些部分是异步的,而且侦听器不希望得到空答案,因此有时会在此处导致崩溃:
mTransition.addListener(new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
ArrayList<Transition> currentTransitions =
runningTransitions.get(mSceneRoot); //"mSceneRoot" is basically the DecorView
currentTransitions.remove(transition); //This line crashes, because "currentTransitions" is null
transition.removeListener(this);
}
});
为解决此问题,我对解决方法进行了以下更改:
fun AppCompatActivity.removeActivityFromTransitionManager() {
if (Build.VERSION.SDK_INT < 21) {
return;
}
val transitionManagerClass: Class<*> = TransitionManager::class.java
try {
val runningTransitionsField: Field =
transitionManagerClass.getDeclaredField("sRunningTransitions")
runningTransitionsField.isAccessible = true
@Suppress("UNCHECKED_CAST")
val runningTransitions: ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>?> =
runningTransitionsField.get(transitionManagerClass) as ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>?>
if (runningTransitions.get() == null || runningTransitions.get()?.get() == null) {
return
}
val map: ArrayMap<ViewGroup, ArrayList<Transition>> =
runningTransitions.get()?.get() as ArrayMap<ViewGroup, ArrayList<Transition>>
map[window.decorView]?.let { transitionList ->
transitionList.forEach { transition ->
//Add a listener to all transitions. The last one to finish will remove the decor view:
transition.addListener(object : Transition.TransitionListener {
override fun onTransitionEnd(transition: Transition) {
//When a transition is finished, it gets removed from the transition list
// internally right before this callback. Remove the decor view only when
// all the transitions related to it are done:
if (transitionList.isEmpty()) {
map.remove(window.decorView)
}
transition.removeListener(this)
}
override fun onTransitionCancel(transition: Transition?) {}
override fun onTransitionPause(transition: Transition?) {}
override fun onTransitionResume(transition: Transition?) {}
override fun onTransitionStart(transition: Transition?) {}
})
}
//If there are no active transitions, just remove the decor view immediately:
if (transitionList.isEmpty()) {
map.remove(window.decorView)
}
}
} catch (_: Throwable) {}
}
所以基本上,我的解决方法是进行以下操作:
TransitionListener
。当每个过渡结束时,这些侦听器将检查它们是否是完成的最后一个过渡,如果是,则将删除DecorView。
这种方法使DecorView可用于赛车过渡,但确保最后将其删除。现在,我不确定这是否可以解决与方向更改有关的崩溃问题,但是我对此持谨慎乐观的态度。