Android 性能优化(二):LeakCanary【用于分析代码是否存在内存泄漏】程序无响应

news2024/9/17 7:10:44

目录

1)内存相关的五种常见问题
2)内存溢出和内存泄漏
3)LeakCanary是什么?
4)LeakCanary如何使用,如何分析?
5)LeakCanary监测的内容

提问:程序有时候很卡,经常会出现闪退,出现程序无响应,在项目过程中遇到主要的问题我们应该如何解决呢?这篇文章会介绍LeakCanary工具来分析和解决这些问题。

一、内存相关的五种常见问题

OOM(Out of Memory)是指内存不足,即应用程序在运行过程中申请的内存超过了系统可用的内存资源。当系统检测到内存不足时,会触发 OOM 错误并终止应用程序的运行。

OOM 通常发生在应用程序加载大量图片、缓存数据、创建大对象等场景下。如果应用程序没有有效地管理内存,持续申请内存而不释放,就容易导致内存溢出,最终触发 OOM 错误。

在这里插入图片描述为什么明明还有4M的内存空间,加载1M的图片是,会报OOM呢,因为内存空间不连续。
在这里插入图片描述

二、内存溢出和内存泄漏

内存溢出指的是应用程序在运行过程中申请的内存超过了系统可用的内存资源。当系统检测到内不足时,会触发 Out of Memory(OOM)错误并终止应用程序的运行。内存溢出通常是由于应用程序持续申请内存而没有及释放导致的,最终导致系统无法为应用程序提供足够的内存空间。

内存泄漏指的是应用程序在使用完某些对象后,没有正确地释放对这些对象的引用导致这些对象无法被垃圾回收器回收,从而占用了系统的内存资源。随着时间的推移,内存泄漏累积起来,最终导致可用内存逐渐减少,可能触发 OOM 错误或导致应用程序变得缓慢、不稳定。

80%的原因都是因为内存泄漏导致内存溢出。

2.1 常见的内存泄漏

  1. 单例造成的内存泄露
  2. 非静态内部类创建静态实例造成的內存泄露
  3. Handler造成的内存泄露
  4. 线程造成的内存泄露

(1)为什么单例会造成内存泄漏?

在这里插入图片描述

(2)非静态内部类创建静态实例造成的內存泄露

public class OuterClass {
    static SomeListener listener;

    public void createLeak() {
        // 创建非静态内部类的实例并将其赋值给静态变量
        listener = new InnerClass();
    }

    private class InnerClass implements SomeListener {
        // 实现一些监听器的方法
    }
}

OuterClass 是外部类,InnerClass是非静态内部类。当调用createLeak()方法时,会创建InnerClass的实例并将其赋值给静态变量listener。由于 InnerClass是非静态内部类,它隐式地持有对外部类OuterClass引用。因此,即使 createLeak() 方法执行完毕后,InnerClass` 的实例仍然存在,并且无法被垃圾回收,导致内存泄漏。

为了解决这个问题,内部类改为静态内部类:将 InnerClass 改为静态内部类,这样它就不再隐式地持有外部类的引用

public class OuterClass {
    private static SomeListener listener;

    public void createLeak() {
 // 创建静态内部类的实例并将其赋值给静态变量
        listener = new InnerClass();
    }

    private static class InnerClass implements SomeListener {
 // 实现一些监听器的方法
    }
}

(3)Handler造成的内存泄漏

public class MainActivity extends AppCompatActivity {
    private static final int_CODE = 1;
    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 创建 Handler 实例,并在 handleMessage() 方法中
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == MESSAGE_CODE) {
 // 处理消息。这里可能会发生内存泄漏,因为发送的延迟消息。
                }
            }
        };

        // 发送延迟消息
        mHandler.sendEmptyMessageDelayed(MESSAGE_CODE, 1000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // 方法一:移除所有未处理的消息和回调,避免内存泄漏
        mHandler.removeCallbacksAndMessages(null);
    }
}
方法二

可以使用弱引用来避免Handler持有对外部类的引用


import android.os.Handler;
import android.os.Message;
import java.lang.ref.WeakReference;

public class MyActivity extends Activity {
    private static class MyHandler extends Handler {
        private WeakReference<MyActivity> mActivityRef;

        public MyHandler(MyActivity activity) {
            mActivityRef = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MyActivity activity = mActivityRef.get();
            if (activity != null) {
                // 处理消息
            }
        }
    }

    private MyHandler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mHandler = new MyHandler(this);
        // 发送延迟消息
        mHandler.sendEmptyMessageDelayed(1, 1000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 移除所有未处理的消息和回调
        mHandler.removeCallbacksAndMessages(null);
    }
}

(4)线程

public class MainActivity extends AppCompatActivity {
    private Thread mThread;

    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 创建一个线程并启动
        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                // 模拟耗时操作
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        mThread.start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // 销毁Activity时,没有正确停止线程,导致线程持有Activity的引用而无法被回收,造成内存泄露
    }
}
public class MainActivity extends AppCompatActivity {
    private Handler mHandler;
    private Runnable mRunnable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mHandler = new Handler();
        mRunnable = new Runnable() {
            @Override
            public void run() {
                // 模拟耗时操作
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        mHandler.post(mRunnable);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // 停止线程
        mHandler.removeCallbacks(mRunnable);
    }
}

可以看到种类还是比较多,如果我们想完全去避免,还是比较难的,可能一不小心,就会写错了,所以我们要介绍一下今天的主角"LeakCanary"。

三、LeakCanary是什么?

LeakCanary是一个用于检测Android应用中内存泄漏的开源库。它由Square公司开发并维护,旨在帮助开发者及时发现和修复应用中的内存泄漏问题。

四、LeakCanary如何使用,如何分析?

(1)引入依赖
在这里插入图片描述(2)运行debug

  1. 当出现内存溢出的时候会提示这个。
    在这里插入图片描述
    然后我们就可以根据代码进行分析。
    在这里插入图片描述

为什么没有位置显示呢?如何去分析呢?这个其实可以定位到类,定位到某个变量,可能有的人看到这个信息就会很懵,但是学过我们”内存泄漏的常见几种情况“后,那么我们就可以进行分析优化。

五、LeakCanary监测的内容

LeakCanary是一个用于检Android应用程序中内存泄漏的工具。它能够监测和报告以下内容:

  1. 活动泄漏:当一个Activity被销毁后,仍然存在对的引用,导致无法被垃圾回收。

  2. Fragment泄漏:当一个Fragment被销毁后,仍然存在对它的引用,导致无法被垃圾回收。

  3. 对话框泄漏:当对话框被关闭后,仍然存在对它的引用,导致无法被垃圾回收。

  4. 广播接收器泄漏:当一个广播接收器未被正确注销,导致持续接收广播,从而引起内存泄漏。

  5. 单例对象泄漏:当一个单例对象中持有对Activity或Fragment的引用,导致无法释放这些对象。

  6. 视图泄漏:当一个视图对象被销毁后,仍然存在对它的引用,导致无法被垃圾回收。

当LeakCanary检测到以上情况时,它会生成一个详细的报告,包含泄漏对象的类型、引用链、及内存泄漏的原因等信息,以帮助开发者定位和修复内存泄漏问题。

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

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

相关文章

再论pg归档日志的设置

用过ORACLE的朋友&#xff0c;第一次设置 PG的归档参数&#xff0c;如下&#xff1a; 。。。 wal_level replica archive_mode on archive_command test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f 。。。 对于归档&#xff0c;还要用…

ConvGRU原理与开源代码

ConvGRU 1. 算法简介与应用场景2. 算法原理2.1 GRU基础2.2 ConvGRU原理2.2.1 ConvGRU的结构2.2.2 卷积操作的优点 2.3 GRU与ConvGRU的对比分析2.4 ConvGRU的应用 3. PyTorch代码 仅需要网络源码的可以直接跳到末尾即可 需要ConvLSTM的可以参考我的另外一篇博客&#xff1a;小白…

Halcon Blob分析

斑点分析的思路&#xff1a;在图像中&#xff0c;相关对象的像素可以通过其灰度值来识别。例如下图的组织颗粒。这些颗粒是凉的&#xff0c;而液体是暗的&#xff0c;通过选择明亮像素(阈值)&#xff0c;可以很容易地检测到颗粒。在需要应用中&#xff0c;这种简单的暗像素和亮…

成像光学:LCD的工作原理与结构图解

一、主流显示面板技术&#xff1a;LCD&#xff0c;OLED&#xff0c;MicroLED 二、主流显示屏的发展趋势 三、LCD堆叠结构&#xff08;以比较流行的TFT-LCD为例&#xff09; 沿光路方向介绍&#xff1a;背光&#xff0c;下偏光片&#xff08;polarizer&#xff09;&#xff0c;…

python实现图像分割算法2

python实现随机步行算法 随机步行算法数学模型Python 实现详细解释优缺点应用领域随机步行算法是一种常用于图像分割和图像分析的算法。它通过模拟随机游走来确定图像中每个像素的标签或类别。随机步行算法特别适合用于解决有种子标记的图像分割问题,其中用户提供一些初始标记…

【Python】基础语法(上)

本篇文章讲解以下知识&#xff1a; &#xff08;1&#xff09;初始编码 &#xff08;2&#xff09;输出 &#xff08;3&#xff09;初识数据类型 一&#xff1a;初识编码 在计算机中所有的数据本质上都是以0和1的组合来存储。 比如&#xff1a;在一个文件中有以下内容&am…

力扣SQL50 上级经理已离职的公司员工 一题双解

Problem: 1978. 上级经理已离职的公司员工 Code -- 方法 1 -- select e1.employee_id -- from employees e1 -- left join employees e2 -- on e1.manager_id e2.employee_id -- where e1.salary < 30000 -- and e1.manager_id is not null -- and e2.employee_id is…

SpringBoot 整合 Redis 实现验证码登录功能

一、整合Redis 在pom.xml中添加Redis相关依赖&#xff1b; <!--Spring Data Redis依赖配置--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency>…

103.qt qml-最全Table新增下拉复制功能

在上篇文章102.qt qml-最全Table交互之多列固定、行列拖拽、自定义委托、标题交互使用教程_qt 表格控件 拖动列-CSDN博客 我们实现了大部分功能,所以本章实现下拉复制功能。 demo截图如下所示: 支持跨界复制,如果下拉的位置大于Table则会动画向下移动,具体可以参考视频链接…

颠覆未来计算!CRAM技术摒弃冯·诺依曼模型,20年研究终迎突破

未来科技&#xff1a;AI计算需求激增&#xff0c;数据中心耗电量堪比派对狂饮&#xff01;明尼苏达大学研究团队或携革命性设备&#xff0c;以惊人能效解决AI能耗难题&#xff01; 研究人员设计了一种新型的"计算随机存取存储器"&#xff08;CRAM&#xff09;原型芯…

查看路由表 netstat -r

“Kernel IP routing table” 是Linux系统中用于展示和配置IP路由的表。它告诉操作系统如何将数据包从一个网络接口发送到另一个网络或主机。下面是对您给出的路由表条目的解释&#xff1a; Destination&#xff1a;目的地地址&#xff0c;可以是具体的IP地址&#xff0c;也可…

Codeforces 962 div3 A-F

A 题目分析 签到 C代码 #include<iostream> using namespace std; int main(){int t;cin>>t;while(t--){int n;cin>>n;cout<<n/4n%4/2<<endl;} } B 题目分析 将n*n的方格分成若干个k*k的方格&#xff0c;每个k*k的方格中所有的数都相同 遍历…

小主机SSD固态硬盘选购攻略,希捷酷鱼 530 SSD固态硬盘表现优秀【附系统无损迁移教程】

小主机SSD固态硬盘选购攻略&#xff0c;希捷酷鱼 530 SSD固态硬盘表现优秀【附系统无损迁移教程】 哈喽小伙伴们好&#xff0c;我是Stark-C~ 这几年随着以零刻为首的小主机市场的兴起&#xff0c;小主机相关的配置周边需求也是越来越大&#xff0c;就比如说SSD固态硬盘就是其…

爬虫程序在采集亚马逊站点数据时如何绕过验证码限制?

引言 在电商数据分析中&#xff0c;爬虫技术的应用日益广泛。通过爬虫技术&#xff0c;我们可以高效地获取大量的电商平台数据&#xff0c;这些数据对于市场分析、竞争情报、价格监控等有着极其重要的意义。亚马逊作为全球最大的电商平台之一&#xff0c;是数据采集的重要目标…

Nacos-微服务注册中⼼(Nacos简介 Nacos配置管理)

目录 一、 微服务的注册中⼼ 1. 注册中⼼的主要作⽤ 2. 常⻅的注册中⼼ 二、Nacos简介 nacos实战⼊⻔ 1. 搭建nacos环境 2.将订单微服务注册到nacos 2.1 在pom.xml中添加nacos的依赖 2.2 在主类上添加EnableDiscoveryClient注解 2.3 在application.yml中添加nacos服…

如何在Linux上构建Raspberry Pi虚拟环境

目录 前置环境需求 Older Version 新版本启动 下面我们来讲讲如何使用QEMU来仿照树莓派环境。这里首先先分成两大类。第一类是跑比较老的&#xff0c;安全性较低的老树莓派&#xff0c;主要指代的是22年4月份发布之前的版本&#xff0c;这个版本当中&#xff0c;树莓派镜像自…

Layui表格合并、表格折叠树

1、核心代码&#xff1a; let tableMerge layui.tableMerge; // 引入合并的插件&#xff0c;插件源文件在最后let tableData [{pid: 0,cid: 111,sortNum: 1, // 序号pName: 数据父元素1,name: 数据1,val: 20,open: true, // 子树是否展开hasChild: true, // 有子数据opt: 数据…

昇思25天学习打卡营第1天 | 快速入门教程

昇思大模型平台&#xff0c;就像是AI学习者和开发者的超级基地&#xff0c;这里不仅提供丰富的项目、模型和大模型体验&#xff0c;还有一大堆经典数据集任你挑。 AI学习有时候就像找不到高质量数据集的捉迷藏游戏&#xff0c;而且本地跑大数据集训练模型简直是个折磨&#xf…

react css module 不生效问题记录

背景&#xff1a;自己使用webpackreactcssless配置的项目框架&#xff0c;在使用过程中发现css module引入不生效。 import React from react import styles from ./index.module.less console.log(styles)//输出 undefinedwebpack配置了css-loader,less-loader,webpack默认cs…

Linux系统之dns服务配置

要求&#xff1a;DNS服务器域解析 www. 11zzj.com为192.168.11.1; ftp.11zzj.com 为192.168.11.2; mail.11zzj.com 为172.16.11.20; 1.打开Linux6&#xff08;服务器&#xff09;和Linux5&#xff08;客户端&#xff09; 配置IP地址和DNS 地址&#xff0c;并ping通。…