Activity内存泄漏时包含的view还有没有的救?

news2024/12/27 22:24:50

Activity泄漏会导致该Activity引用到的Bitmap、DrawingCache等无法释放,对内存造成大的压力,兜底回收是指对于已泄漏Activity,尝试回收其持有的资源,泄漏的仅仅是一个Activity空壳,从而降低对内存的压力。 做法也非常简单,在Activity onDestory时候从view的rootview开始,递归释放所有子view涉及的图片,背景,DrawingCache,监听器等等资源,让Activity成为一个不占资源的空壳,泄露了也不会导致图片资源被持有。

…
 …
 Drawable d = iv.getDrawable();
 if (d != null) {
 d.setCallback(null);
 } 
 iv.setImageDrawable(null);
 ...
 ...

在不保证项目不出现内存泄漏的问题的情况下,保底回收Activity或者Fragment中的所有drawable,内存这个东西嘛,能救回来一点是一点。

这里的题外话,不是说内存泄漏的检测不重要,在聊起来的时候总有人跟我说起来不是应该去解决内存泄漏吗?为什么要搞这个。如果你能保证你的项目在经过多年迭代、重构、不同水平层次的开发人员维护、后半夜上线不清醒的情况下修改点什么东西等等等等,这样的情况下保证一点内存泄漏的问题都没有的话,那当然当我没说。

这里做的处理是检测到Activity或者Fragment leaking之后,遍历所有持有的子view,释放占内存大户也就是view持有的图片背景资源,当然不限于drawable,如果还持有一些其他的该释放但是未释放的比如播放器资源,文件句柄文件流网络流也是可以根据情况来释放掉的。

说道这里其实我们可以提出一些疑问:

  1. 上述方案是在确定泄露的情况下做的,如何检测内存泄漏?
  2. 为什么不直接释放view而是释放掉drawable?

如何检测内存泄漏

说起检测内存泄漏就不能不提leakcanary,腾讯的Matrix的内存泄漏也是借鉴的它的原理。这里只简单剖析原理并实现一个最简单的leaking检测,不涉及到hprof的获取与分析。

leakcanary的原理

  1. onDestroy的时候通过ReferenceQueue创建WeakReference,并为它设置一个Key,存到Set中。
  2. 等待5s后尝试从ReferenceQueue中查找此WeakReference,找到就从Set中移除,不成功则GC后再试一次。
  3. 查看此Key是否还在Set中存在,如果存在则认为是泄漏。

上述的步骤就是它检测内存泄漏的工作原理,使用了带ReferenceQueueWeakReference的构造方法来创建弱引用,当目标对象只有WeakReference持有的情况下就可以被GC回收,回收之后会放到ReferenceQueue中。所以只要检测会不会出现在ReferenceQueue中就知道有没有被回收。

最简单的leakcanary工程

根据上面的leakcanary的原理,我们可以实现一个最简单的leakcanary,用来检测某个对象的内存泄漏,这个对象可以不止是activity,也可以是其他的对象比如包含的view或者drawable是否有泄漏,这样一个工具可以对我们本篇文章中的内容做实践上的支持。
主要的核心代码如下:

public class RefWatcher {
    private final GcTrigger gcTrigger;
    private final Set<String> retainedKeys;
    private final ReferenceQueue<Object> queue;

    public RefWatcher() {
        retainedKeys = new CopyOnWriteArraySet<>();
        queue = new ReferenceQueue<>();
        this.gcTrigger = GcTrigger.DEFAULT;
    }

// Activity destory的时候调用此方法,用来观察目标对象是否回收
    public void watch(Object watchedReference, final String referenceName) {
        final long watchStartNanoTime = System.nanoTime();
        String key = UUID.randomUUID().toString();
        retainedKeys.add(key);
        final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                ensureGone(reference, referenceName, watchStartNanoTime);
            }
        }, 5000);
    }

    void ensureGone(final KeyedWeakReference reference, String referenceName, final long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

        removeWeaklyReachableReferences();

        if (gone(reference)) {
            return;
        }
        gcTrigger.runGc();
        removeWeaklyReachableReferences();
        if (!gone(reference)) {
            //do HeapDump, HeapAnalyzer
        }
        return;
    }

    private boolean gone(KeyedWeakReference reference) {
        return !retainedKeys.contains(reference.key);
    }

    private void removeWeaklyReachableReferences() {
// 因为一旦一个对象只有当前类中的弱引用持有的情况下,gc的时候会回收掉这个对象并且对象会被放到queue中,如果queue中有对象说明此对象已经被回收了,从retainedKeys中移除掉。
        KeyedWeakReference ref;
        while ((ref = (KeyedWeakReference) queue.poll()) != null) {
            retainedKeys.remove(ref.key);
        }
    }
}

原理上面说的很清晰了,可以结合代码中的注释加深理解。
更加详细的代码可以查看 SimpleLeakCanary,可以直接clone下来run一下。

同样可以用来检测回收drawable代码是否有效,具体可以查看 DrawableRefWatcher 。 针对activity中的某一个drawable,在加了上面回收drawable的代码和不加的情况下看针对drawable的内存泄漏是否有效。

为什么不直接释放view

上面的解决方案是在内存泄漏发生的时候回收释放activity中所有的drawable来实现的。其实我们很容易就想到,为什么不直接释放view而要去释放drawable呢?

我们盲猜,那肯定是因为释放drawable容易,因为drawable的引用链比较单一,无非是有个view引用了这个drawable导致它无法释放。
那如果是view呢?它的引用链是什么样的呢?我们怎么能看到一个view的GC引用链呢?

MAT的使用

MAT是个很好的工具,下载链接在 Memory Analyzer (MAT),其实用过eclipse开发Android的年代,很多人应该对他都很熟悉,这里不详细介绍它的用法,更详细的用法可以来看 高爷的 Android 内存优化 (1) - MAT 使用入门 ,可以看到更多高阶的MAT的用法。

我这里只是抛砖引玉,针对使用MAT如何查看一个view的所有引用路径。

  1. 找一个目标view,我这里使用了一个自定义view叫TestLeakingView,这样可以更方便的在MAT中定位它。
  2. dump一份内存快照,可以用AndroidStudio的profiler,在左侧点Capture heap dump,等一两秒会生成一份内存快照文件。

  1. 将AndroidStudio生成的hprof文件转换成MAT使用的标准格式,可以借助Android SDK下的hprof-conv工具来做转换,我mac下目录为/Users/yocn/Library/Android/sdk/platform-tools/hprof-conv,用法为 hprof-conv in.hprof out.hprof,得到的out.hprof就是标准的hprof文件

如果找不到自己的SDK目录,可以使用where命令来查找,能看到如下的结果:

  1. 使用MAT打开转换之后的标准hprof文件。

这也不展开MAT的用法或者兼容性问题,提醒mac最新版MAT需要java-11,在 显示包内容 -> Contents -> Info.plist 文件中配置成自己的java11的路径。

<string>-vm</string><string>/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home/bin/java</string>

这里打开dominator_tree,找到我们自定义的TestLeakingView,右键找到Merge Shortest Path to GC root,选择with all references可以看到下图:

RenderNode是硬件加速相关的类,可以参考 使用Android RenderNode加速绘制

可以看到单个view起码被以上的几条路径引用,包括window中的view树,上下文context的引用。这些都是framework控制和显示view的基础,所以还是很难做剥离的。

释放view的尝试

其实我看到上面的view的引用的时候也是尝试过一些view的释放。按照上面的引用路径做一些尝试:

  • 从view树中移除
  • 移除mContext引用

效果其实不是很理想,这里不赘述尝试的方法,尤其是高版本的Android做了一些hide的API和反射方法的限制之后,其实这些尝试没有什么意义,只是作为思路上的发散,做一些可能性上的探索,如此而已。

总结:

  1. 内存泄漏下兜底方案释放drawable是可行的,可以选择性的使用此方案
  2. leakcanary借助了ReferenceQueue来做内存泄漏的检测
  3. 可以自己实现leakcanary用来做更小粒度的对象的内存泄漏的检测,比如view或者drawable
  4. 使用MAT来检测view的引用链,解释为什么释放drawable而不是view
  5. 是否可以做view的内存泄漏释放思路的发散及探索

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/518058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ssh终端工具推荐-WindTerm

什么是WindTerm 官方github https://github.com/kingToolbox/WindTerm A Quicker and better SSH/Telnet/Serial/Shell/Sftp client for DevOps. 按官方说明&#xff0c;WindTerm是一个更快更好的SSH/Telnet/Serial/Shell/Sftp的DevOps工具。 WindTerm目前对商业是免费无限制…

QML APP开发套路(二):前/后端交互概述

&#xff08;1&#xff09;QML开发简介 Qt应用框架在传统UI&#xff08;QWidget窗体&#xff09;的基础上&#xff0c;提供了Qt Quick模块&#xff0c;该模块基于 QML 语言来定义UI及交互方式。区别于 QWidget 定义UI的方式&#xff0c;QML利于将UI交互与业务逻辑处理剥离成前…

什么是智慧校园?

什么是智慧校园&#xff1f; 智慧校园平台是目前教育信息化领域的热点之一。 随着数字化转型的加速&#xff0c;越来越多的学校开始寻求解决方案&#xff0c;以提高教育管理的效率和质量。 在使用智慧校园平台的过程中&#xff0c;一些痛点问题也浮现出来。为解决这些问题&a…

基于AT89C51单片机的贪吃蛇游戏设计

点击链接获取Keil源码与Project Backups仿真图: https://download.csdn.net/download/qq_64505944/87778030 源码获取 主要内容: 设计一个贪吃蛇游戏,使其具有以下游戏规则:①当没有改变方向时,贪吃蛇沿原来路径一直前进②贪吃蛇无法回头,只能异于当前方向改变行动③蛇…

网页更新提醒是什么?如何自动监控网页并自动记录或发送通知?

网页更新提醒是什么&#xff1f; 在互联网信息资源丰富&#xff0c;且更新速度快的情况下&#xff0c;如果需要监控一些网页变化&#xff0c;实现例如热点/热搜/热评监测、商品上新/价格/库存监测、作品上新/评论/点赞监测、招标/投标/拍卖/竞价监测、公告/通知/活动监测等情况…

南京大学主办 | EIScopus检索 | 2023年人工智能与统计学前沿国际会议

2023年人工智能与统计学前沿国际会议 会议简介 Brief Introduction 2023年人工智能与统计学前沿国际会议(CFAIS 2023) 会议时间&#xff1a;2023年8月18日-20日 召开地点&#xff1a;中国南京 大会官网&#xff1a;www.cfais.org 2023年人工智能与统计学国际会议(CFAIS 2023)将…

测评,补单是什么神仙利器?能提高速卖通,国际站,newegg店铺的销量

测评&#xff0c;补单相信这个词对于大部分卖家来说&#xff0c;想必都不陌生&#xff0c;因为都知道它能够快速帮助自己的产品添加评论&#xff0c;获取排名&#xff0c;打造爆款。于是许多卖家潜意识里认为“测评其实就是刷评”。 简单点来说&#xff0c;测评就是卖家通过【评…

485测试

注意如果十六进制发送数据 数据打印时 如果是%c 打印出来的数据会十进制显示 解决方案 1.改变打印方式 %x 打印 2.因为什么也不勾选 &#xff0c;默认asii显示 利用串口助手发ltz &#xff0c;打印也是ltz&#xff08;发什么就显示什么&#xff09; 如下图所示

如何使用appuploader制作描述文件​

如何使用appuploader制作描述文件​ 承接上文我们讲述了怎么制作证书&#xff0c;本文我们来看下怎么制作描述文件吧。​制作描述文件前我们首先我们来添加一个测试设备&#xff0c;后面再制作描述文件。 1.添加测试设备​ 其中添加设备一项中&#xff0c;根据提示操作添加…

OpenGL ES特效分析 --- 跳动的心

很早之前就见过一个博主发的shader图片&#xff0c;一个跳动的心https://blog.csdn.net/Kennethdroid/article/details/104536532&#xff0c; 感觉太好玩了&#xff0c;于是想要分析一下原理&#xff0c;上面的博主也已经做了初步分析&#xff0c;但是对于我这个特效小白来说还…

VMware Aria Operations 8.12 - 自动驾驶式 IT 运维管理

VMware Aria Operations 8.12 - 自动驾驶式 IT 运维管理 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-aria-operations/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 自动驾驶式 IT 运维管理 VMware Aria Op…

YOLOv5白皮书-第Y3周:yolov5s.yaml文件解读

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客 &#x1f356; 原作者&#xff1a;K同学啊|接辅导、项目定制 ● 难度&#xff1a;夯实基础⭐⭐ ● 语言&#xff1a;Python3、Pytorch3 ● 时间&#xff1a;5月8日-5月12日 &#x1f37a;要求&#xff…

电脑永久删除文件怎么找回来?分享一种数据恢复方法

电脑对于日常生活和工作都起着重要作用&#xff0c;但是在我们日常办公中&#xff0c;有时会误删除一些文档&#xff0c;甚至永久删除&#xff0c;当我们想找回的时候却手足无措&#xff0c;不知道该怎么办。同时在很多用户的观念里&#xff0c;当我们无法从回收站恢复时&#…

Openai+Coursera: ChatGPT Prompt Engineering(一)

想和大家分享一下最近学习的Coursera和openai联合打造ChatGPT Prompt Engineering在线课程&#xff0c;下面是通过API来访问ChatGPT的主要代码&#xff1a; import openaiopenai.api_key XXXXXXXXXdef get_completion(prompt, model"gpt-3.5-turbo"):messages [{&…

【OpenCV】学习课-图像裁剪与拼接(2)!

1. 图像的裁剪 cv2.selectROI() ###可以通过鼠标选择感兴趣的矩形区域&#xff08;ROI&#xff09; import cv2img cv2.imread("xxx.png", flags1) # flags1 读取彩色图像(BGR)roi cv2.selectROI(img, showCrosshairTrue, fromCenterFalse) xmin, ymin, w, h…

2022年平均工资出炉,IT行业又是第一

根据5月9日国家统计局最新资料显示&#xff0c;2022年&#xff0c;全国城镇非私营单位就业人员年平均工资为114029元&#xff0c;比上年增长6.7%&#xff0c;扣除通胀后实际增长4.6%。其中&#xff0c;行业间的差距相当明显。根据资料显示&#xff0c;2022年无论是在私营单位还…

ESP8266(1):搭建Linux环境ESP8266_RTOS_SDK,ESP8266使用GPIO控制继电器

0&#xff09;准备工作&#xff0c;之前一直对esp8266不了解&#xff0c;现在想着给鱼缸加个定时打氧的程序&#xff0c;控制泵的工作。所以购买了一个esp8266 Relay&#xff0c;自己摸索写个简单程序。让泵工作一段时间&#xff0c;再休眠一段时间。 1)宿主机Ubuntu 20.04.5 …

Vue 项目利用 HBuilderX 打包 APP 流程

想要将 Vue 打包成 App&#xff0c;要借助一些插件工具&#xff0c;例如&#xff1a;Electron、Cordova 等&#xff0c;或者直接利用开发工具&#xff0c;例如&#xff1a;Android Studio、HBuilderX 等。本文的目的是带大家通过 HBuilder 开发工具对 Vue 项目进行打包。 一、…

MySQL-函数

1.什么是函数 函数在计算机语言的使用中贯穿始终&#xff0c;函数的作用是什么呢&#xff1f;它可以把我们经常使用的代码封装起来&#xff0c; 需要的时候直接调用即可。这样既 提高了代码效率 &#xff0c;又 提高了可维护性 。在 SQL 中我们也可以使用函数 对检索出来的数据…

7.设计模式之责任链模式

前言 责任链&#xff0c;即将能够处理同一类请求的对象连成一条链&#xff0c;所提交的请求沿着链传递&#xff0c; 链上的对象逐个判断是否有能力处理该请求&#xff0c;如果能则处理&#xff0c;如果不能则传递给链上的下一个对象。为了避免请求发送者与多个请求处理者耦合在…