Android SharedElement和自定义EnterTransition导致内存泄漏
具有共享元素动画和自定义enter动画会导致活动泄漏 知道原因是什么吗Android SharedElement和自定义EnterTransition导致内存泄漏,android,memory-leaks,android-memory,leakcanary,Android,Memory Leaks,Android Memory,Leakcanary,具有共享元素动画和自定义enter动画会导致活动泄漏 知道原因是什么吗 09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * com.feeln.android.activity.MovieDetailActivity已泄漏: 09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * GC根android.app.ActivityThread$A
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * com.feeln.android.activity.MovieDetailActivity已泄漏:
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * GC根android.app.ActivityThread$ApplicationThread.this$0
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 参考android.app.ActivityThread.mActivities
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 参考android.util.ArrayMap.mArray
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 引用数组java.lang.Object[].[1]
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 参考android.app.ActivityThread$ActivityClientRecord.activity
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 参考com.feeln.android.activity.MovieDetailActivity.mActivityTransitionState
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 参考android.app.ActivityTransitionState.mEnterTransitionCoordinator
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 参考android.app.EnterTransitionCoordinator.mEnterViewsTransition
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 参考android.transition.transition.mParent
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 参考android.transition.transition.mListeners
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 参考java.util.ArrayList.array
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 引用数组java.lang.Object[].[1]
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 引用android.transition.TransitionManager$MultiListener$1.val$runningTransitions(匿名类扩展了android.transition.transition$TransitionListenerAdapter)
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 参考android.util.ArrayMap.mArray
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 引用数组java.lang.Object[].[2]
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 参考com.android.internal.policy.impl.PhoneWindow$DecorView.mContext
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 泄漏com.feeln.android.activity.MovieDetailActivity实例
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ [09-21 16:19:31.007 28269:31066 D/金丝雀]
*参考键:af2b6234-297e-4bab-96e9-02f1c4bca171
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 设备:LGE google Nexus 5 hammerhead
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 安卓版本:5.1.1API:22金丝雀:1.3.1
09-21 16:19:31.007 28269-31066/com.sample.android D/LeakCanary﹕ * 持续时间:观察=6785ms,gc=262ms,堆转储=8553ms,分析=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;
}
};
private-SharedElementCallback mCallback=new-SharedElementCallback(){
@凌驾
在SharedElementStart(列出sharedElementNames、列出sharedElements、列出sharedElementSnapshots)上公开作废{
if(Build.VERSION.SDK\u INT>=Build.VERSION\u code.LOLLIPOP)
{
if(sharedElements.size()>0)
getWindow().setEnterTransition(makeEnterTransition(getWindow().getEnterTransition(),getSharedElement(sharedElements)));
}
}
私有视图getSharedElement(列出SharedElement)
{
用于(最终视图:sharedElements)
{
if(图像视图的视图实例)
{
返回视图;
}
}
返回null;
}
};
泄漏的原因在于TransitionManager.s运行Transitions
,其中每个DecorView
都会添加而不会删除DecorView
有链接到他的活动
的上下文
。由于sRunningTransitions
是静态字段,因此它具有指向活动
的永久引用链,GC永远不会收集这些引用
我不知道为什么TransitionManager.sRunningTransitions需要,但是如果您从中删除活动
的DecorView
,您的问题就会得到解决。下面的代码就是例子,如何做到这一点。在活动课上:
@Override
protected void onDestroy() {
super.onDestroy();
removeActivityFromTransitionManager(Activity activity);
}
private static void removeActivityFromTransitionManager(Activity activity) {
if (Build.VERSION.SDK_INT < 21) {
return;
}
Class transitionManagerClass = TransitionManager.class;
try {
Field runningTransitionsField = transitionManagerClass.getDeclaredField("sRunningTransitions");
runningTransitionsField.setAccessible(true);
//noinspection unchecked
ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>> runningTransitions
= (ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>)
runningTransitionsField.get(transitionManagerClass);
if (runningTransitions.get() == null || runningTransitions.get().get() == null) {
return;
}
ArrayMap map = runningTransitions.get().get();
View decorView = activity.getWindow().getDecorView();
if (map.containsKey(decorView)) {
map.remove(decorView);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@覆盖
受保护的空onDestroy(){
super.ondestory();
从TransitionManager(活动)移除活动;
}
来自TransitionManager的私有静态void RemoveActivity(活动){
if(Build.VERSION.SDK_INT<21){
返回;
}
类TransitionManager类=TransitionManager.Class;
试一试{
Field runningTransitionsField=transitionManagerClass.getDeclaredField(“sRunningTransitions”);
runningTransitionsField.setAccessible(true);
//未检查
线程本地运行转换
=(本地线程)
运行TransitionField.get(transitionManagerClass);
if(runningTransitions.get()==null | | runningTransitions.get().get()==null){
返回;
}
ArrayMap=runningTransitions.get().get();
View decorView=activity.getWindow().getDecorView();
if(集装箱地图(decorView)){
移除地图(decorView);
}
}捕获(无此字段例外){
e、 printStackTrace();
}捕获(非法访问例外e){
e、 printStackTrace();
}
}
由@Delargo提供的解决方案没有
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setEnterSharedElementCallback(new LeakFreeSupportSharedElementCallback());
setExitSharedElementCallback(new LeakFreeSupportSharedElementCallback());
}
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)
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) {}
}