当前位置: 首页 > ops >正文

记共享元素动画导致的内存泄露

最近在给项目的预览图片页增加共享元素动画的时候,发现了LeakCanary一直报内存泄露。

LeakCanary日志信息

┬───
│ GC Root: Thread object
│
├─ java.lang.Thread instance
│    Leaking: NO (the main thread always runs)
│    Thread name: 'main'
│    ↓ Thread.threadLocals
│             ~~~~~~~~~~~~
├─ java.lang.ThreadLocal$ThreadLocalMap instance
│    Leaking: UNKNOWN
│    Retaining 15.5 kB in 98 objects
│    ↓ ThreadLocal$ThreadLocalMap.table
│                                 ~~~~~
├─ java.lang.ThreadLocal$ThreadLocalMap$Entry[] array
│    Leaking: UNKNOWN
│    Retaining 15.5 kB in 97 objects
│    ↓ ThreadLocal$ThreadLocalMap$Entry[36]
│                                      ~~~~
├─ java.lang.ThreadLocal$ThreadLocalMap$Entry instance
│    Leaking: UNKNOWN
│    Retaining 28 B in 1 objects
│    ↓ ThreadLocal$ThreadLocalMap$Entry.value
│                                       ~~~~~
├─ android.util.ArrayMap instance
│    Leaking: UNKNOWN
│    Retaining 544 B in 21 objects
│    ↓ ArrayMap.mArray
│               ~~~~~~
├─ java.lang.Object[] array
│    Leaking: UNKNOWN
│    Retaining 503 B in 19 objects
│    ↓ Object[3]
│            ~~~
├─ android.transition.Transition$AnimationInfo instance
│    Leaking: UNKNOWN
│    Retaining 141 B in 6 objects
│    ↓ Transition$AnimationInfo.transition
│                               ~~~~~~~~~~
├─ android.transition.Fade instance
│    Leaking: UNKNOWN
│    Retaining 772 B in 21 objects
│    ↓ Transition.mParent
│                 ~~~~~~~
├─ android.transition.TransitionSet instance
│    Leaking: UNKNOWN
│    Retaining 1.5 kB in 50 objects
│    ↓ Transition.mListeners
│                 ~~~~~~~~~~
├─ java.util.ArrayList instance
│    Leaking: UNKNOWN
│    Retaining 116 B in 5 objects
│    ↓ ArrayList[1]
│               ~~~
├─ android.transition.TransitionManager$MultiListener$1 instance
│    Leaking: UNKNOWN
│    Retaining 36 B in 2 objects
│    Anonymous subclass of android.transition.TransitionListenerAdapter
│    ↓ TransitionManager$MultiListener$1.val$runningTransitions
│                                        ~~~~~~~~~~~~~~~~~~~~~~
├─ android.util.ArrayMap instance
│    Leaking: UNKNOWN
│    Retaining 541.5 kB in 8916 objects
│    ↓ ArrayMap.mArray
│               ~~~~~~
├─ java.lang.Object[] array
│    Leaking: UNKNOWN
│    Retaining 541.5 kB in 8914 objects
│    ↓ Object[8]
│            ~~~
├─ com.android.internal.policy.DecorView instance
│    Leaking: YES (View.mContext references a destroyed activity)
│    Retaining 136.4 kB in 2235 objects
│    View not part of a window view hierarchy
│    View.mAttachInfo is null (view detached)
│    View.mWindowAttachCount = 1
│    mContext instance of com.android.internal.policy.DecorContext, wrapping activity com.fengqun.whitepeachplanet.
│    activity.ImagePreviewActivity with mDestroyed = true
│    ↓ DecorView.mContentRoot
├─ android.widget.LinearLayout instance
│    Leaking: YES (DecorView↑ is leaking and View.mContext references a destroyed activity)
│    Retaining 3.0 kB in 36 objects
│    View is part of a window view hierarchy
│    View.mAttachInfo is null (view detached)
│    View.mWindowAttachCount = 1
│    mContext instance of com.fengqun.whitepeachplanet.activity.ImagePreviewActivity with mDestroyed = true
│    ↓ View.mContext
╰→ com.fengqun.whitepeachplanet.activity.ImagePreviewActivity instance
​     Leaking: YES (ObjectWatcher was watching this because com.fengqun.whitepeachplanet.activity.ImagePreviewActivit
​     received Activity#onDestroy() callback and Activity#mDestroyed is true)
​     Retaining 36.0 kB in 739 objects
​     key = cd70fcef-af19-457e-bb88-2350945ca1c4
​     watchDurationMillis = 5762
​     retainedDurationMillis = 753
​     mApplication instance of com.fengqun.whitepeachplanet.MyApplication
​     mBase instance of androidx.appcompat.view.ContextThemeWrapper

经过排查发现泄露的关键代码在这个 ActivityOptions.makeSceneTransitionAnimation 上。那么就从这里开始深入分析里面内容。

启动共享元素动画:
ActivityCompat.startActivity(activity,this,ActivityOptions.makeSceneTransitionAnimation(activity, *transitionImpl).toBundle()
)

这会创建包含共享元素信息的ActivityOptions对象

启动Activity:
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {getAutofillClientController().onStartActivity(intent, mIntent);if (options != null) {startActivityForResult(intent, -1, options);} else {// Note we want to go through this call for compatibility with// applications that may have overridden the method.startActivityForResult(intent, -1);}
}

因为携带了Bundle,

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {if (mParent == null) {options = transferSpringboardActivityOptions(options);Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);//...
}
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {//...try {intent.migrateExtraStreamToClipData(who);intent.prepareToLeaveProcess(who);int result = ActivityTaskManager.getService().startActivity(whoThread,who.getOpPackageName(), who.getAttributionTag(), intent,intent.resolveTypeIfNeeded(who.getContentResolver()), token,target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);notifyStartActivityResult(result, options);checkStartActivityResult(result, intent);} catch (RemoteException e) {throw new RuntimeException("Failure from system", e);}return null;
}

这里的ActivityTaskManager.getService() 实际返回的是 IActivityTaskManager 接口的 Binder 代理对象。实际上是ActivityTaskManagerService处理了startActivity()。通过在线源码阅读 可以得知他的调用应该发生在更底层的窗口。

这时我们在看看LeakCanary提供的信息。

    ↓ TransitionManager$MultiListener$1.val$runningTransitions

在TransitionManager找到了关键代码:

@Override
public boolean onPreDraw() {// Add to running list, handle end to remove itfinal ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =getRunningTransitions();ArrayList<Transition> currentTransitions = runningTransitions.get(mSceneRoot);ArrayList<Transition> previousRunningTransitions = null;if (currentTransitions == null) {currentTransitions = new ArrayList<Transition>();runningTransitions.put(mSceneRoot, currentTransitions);} else if (currentTransitions.size() > 0) {previousRunningTransitions = new ArrayList<Transition>(currentTransitions);}return true;
}

这里的getRunningTransitions最终指向的是竟然是静态的成员变量:

private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>sRunningTransitions =new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>();
@UnsupportedAppUsage
private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>();

这个静态的成员变量里面持有了ViewGroup。sPendingTransitions记录了正在进行的过渡动画的ViewGroup,而sRunningTransitions则通过ThreadLocal存储了当前运行的过渡动画。如果这些集合没有正确清理,可能会导致Activity被持续引用,而发生内存泄露

由于它们是私有的,考虑使用反射来访问,并将他们置空处理。 最终在onDestroy()方法中调用此方法

fun leakCanaryClean() {try {val pendingField = TransitionManager::class.java.getDeclaredField("sPendingTransitions")pendingField.isAccessible = true(pendingField.get(null) as? ArrayList<ViewGroup>)?.clear()val runningField = TransitionManager::class.java.getDeclaredField("sRunningTransitions")runningField.isAccessible = trueval threadLocal = runningField.get(null) as? ThreadLocal<*>threadLocal?.set(null)} catch (e: Exception) {LogUtils.e("清除预览图片反射异常: ${e.message}")}
}

至此因为系统持有ViewGroup导致的泄露问题就解决了。

当然这种内存泄露也不是递增的。通过AS 的Profiler可以看到,过段时间后。 gc还是能够回收掉ViewGroup的引用。 因为在Transition执行结束后,还是会remove掉的。当然系统也不会犯这种低级错误 🥲

最后通过简单的封装,就可以调用带动画预览效果了

ImageViewer.load(arrayList).selection(position).setShareView(viewMap.values.toList()).start()

在这里插入图片描述

http://www.xdnf.cn/news/8460.html

相关文章:

  • ABAP,谨慎使用UPDATE更新底表
  • WCS-PZ100V4B15闭环霍尔电流传感器
  • 动态库和静态库详解
  • 推进可解释人工智能迈向类人智能讨论总结分享
  • 【数组的定义数组与内存的关系】
  • 【信息系统项目管理师】第18章:项目绩效域 - 45个经典题目及详解
  • antv/g6 图谱封装配置(二)
  • 七、OpenGL 2.0 可编程着色器实现渲染控制权转移的四大核心机制
  • 使用js 写一个函数 将base64 转换成file
  • linux初识--基础指令
  • 云蝠语音智能体——电话面试中的智能助手
  • 【数据架构07】数据智能架构篇
  • JavaScript数据类型完全指南:从基础到实战
  • 交流电能表基本介绍
  • 《Python语言程序设计》第4章第7题,这次利用之前学过的第7章的内容使用对象和类,来修改这道题
  • 仿真APP助力提升卡车驾驶室驾乘舒适度与安全性
  • 模型压缩,AWQ与GPTQ量化方法分析
  • 【信息系统项目管理师】第20章:高级项目管理 - 28个经典题目及详解
  • 学习日记-day14-5.23
  • Redis淘汰策略
  • vue pinia 独立维护,仓库统一导出
  • 虚拟机Centos7:Cannot find a valid baseurl for repo: base/7/x86_64问题解决
  • Linux 下使用 Sysbench 进行性能测试
  • ConceptAttention:Diffusion Transformers learn highly interpretable features
  • Tailwind css实战,基于Kooboo构建AI对话框页面(一)
  • 纸牌游戏(基于集合,和自定义排序实现)
  • linux_cmake的笔记
  • 从底层原理分析Python 常用字符串拼接方法效率差异
  • BeeWorks局域网聊天工具:打造智能高效的企业级即时通讯新生态
  • Spring生态的核心思想