Android 内存优化:什么原因导致内存问题?通过内存工具进行分析;内存抖动和内存泄漏;MAT的使用;Profiler的使用;如何优化?

news2024/10/7 1:03:37

目录

在这里插入图片描述

一、为什么要进行内存优化呢?

我们开发一个App程序,如果不了解内存的使用情况,就是将稳定性弃之不管。因为你不知道他在什么时候会发生OOM问题,不知道为什么程序会卡顿,不知道为什么会发生问题。你也没有自信跟别人说,你可以也出一个稳定可靠的App程序,所以这一篇文章,我们来研究一下内存优化。

Android进行内存优化是为了提高应用的稳定性、流畅性和存活时间,同时降低应用占用的ROM空间。


二、有哪些问题需要我们进行内存优化

  1. 防止应用发生OOM(Out of Memory)错误:Android设备对每个应用进程分配的内存是有限的,当应用内存使用超过这个限制时,就会发生OOM错误,导致应用异常退出。还有一种是,当申请的控件在内存中无连续的空间可分配的时候,也会出现问题。

  2. 避免不合理使用内存导致GC(Garbage Collection)次数增多,导致应用卡顿:在Android系统中,GC会暂停所有线程(包括主线程)来回收内存,如果内存使用不合理,频繁触发GC,就会导致应用卡顿。这个问题用两个名词来说,就是内存抖动和内存泄漏。

只要解决了内存抖动和内存泄漏,那么OOM问题就迎刃而解了


2.1 内存抖动是什么?

  1. 内存频繁的申请和回收。
  2. 申请次数太多引发GC,同时还会引起卡顿,因为当GC发生时,应用的线程会被暂停,等待GC完成后才能继续执行。
  3. 申请速度过快。

(1)代码举例:内存频繁的申请和回收

public class MemoryChurnView extends View {  
  
    private Paint paint;  
  
    public MemoryChurnView(Context context) {  
        super(context);  
        paint = new Paint();  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        // 这里每次onDraw都会创建一个新的String和Rect对象  
        // 这些对象在onDraw结束后都会变得不可达  
        String text = "Some text that changes frequently"; // 实际上这里的text并没有改变,但只是为了示例  
        Rect bounds = new Rect();  
        paint.getTextBounds(text, 0, text.length(), bounds);  
  
        // 绘制文本(这个操作本身不会导致内存抖动,但上面的对象创建会)  
        canvas.drawText(text, bounds.left, bounds.bottom, paint);  
  
        // 注意:这里的text和bounds在onDraw结束后应该被GC回收  
        // 但如果onDraw被频繁调用,就会导致频繁的GC,即内存抖动  
    }  
}

如果onDraw中包含了创建大对象或大量对象的代码,那么就会导致内存抖动。出于性能和内存使用的考虑,我们通常不会在onDraw中做这样的事情。而是将创建对象放到外面。

public class MemoryChurnView extends View {  
  
    private Paint paint;  
  
    public MemoryChurnView(Context context) {  
        super(context);  
        paint = new Paint();  
    }  
  		 // 这里每次onDraw都会创建一个新的String和Rect对象  
        // 这些对象在onDraw结束后都会变得不可达  
        String text = "Some text that changes frequently"; // 实际上这里的text并没有改变,但只是为了示例  
        Rect bounds = new Rect();  
        
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
       
        paint.getTextBounds(text, 0, text.length(), bounds);  
  
        // 绘制文本(这个操作本身不会导致内存抖动,但上面的对象创建会)  
        canvas.drawText(text, bounds.left, bounds.bottom, paint);  
  
        // 注意:这里的text和bounds在onDraw结束后应该被GC回收  
        // 但如果onDraw被频繁调用,就会导致频繁的GC,即内存抖动  
    }  
}

(2)代码示例:申请速度过快

public void allocateMemoryTooFast() {  
    List<byte[]> largeDataList = new ArrayList<>();  
  
    // 假设我们有一个很大的数字N  
    int N = 1000000;  
  
    // 在一个循环中快速分配内存  
    for (int i = 0; i < N; i++) {  
        // 分配一个相对较大的byte数组(例如1MB)  
        byte[] largeData = new byte[1024 * 1024]; // 1MB  
        largeDataList.add(largeData); // 假设我们需要保留这些数据的引用  
  
        // 注意:这里的代码在真实应用中可能会导致内存峰值过高和OOM异常  
        // 因为我们在短时间内分配了大量的内存  
    }  
  
    // 在实际应用中,我们可能需要对largeDataList进行处理  
    // 或者在分配过程中添加一些逻辑来限制内存的使用  
}

申请内存过快的问题通常发生在短时间内分配了大量内存的情况下。这可能会导致内存峰值过高,甚至触发OOM(Out Of Memory)异常。


2.2 内存泄漏是什么?

  1. 一个长生命周期的对象持有一个短生命周期对象的强引用
public class MainActivity extends AppCompatActivity {  
  
    private Handler handler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            // 处理消息  
        }  
    };  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
  
        // 假设在某个地方启动了一个线程,该线程使用handler发送消息  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                Message message = handler.obtainMessage();  
                // 设置消息内容等  
                handler.sendMessage(message);  
            }  
        }).start();  
    }  
  
    @Override  
    protected void onDestroy() {  
        super.onDestroy();  
        // 这里没有取消handler的消息队列或处理handler的引用,可能导致内存泄漏  
    }  
}

Handler被定义为一个匿名内部类的实例,并持有MainActivity的隐式引用(因为Handler中的handleMessage方法需要访问MainActivity的成员)。当MainActivity被销毁时,如果Handler仍然有未处理的消息或Runnable任务在消息队列中,那么这些任务会持有MainActivity的引用,从而阻止垃圾回收器回收MainActivity的内存,导致内存泄漏。

解决方法:将Handler定义为一个静态内部类,并通过弱引用持有外部类的引用。这样,当外部类(如MainActivity)被销毁时,垃圾回收器可以回收它,而不会因为Handler的引用而阻止。

public class MainActivity extends AppCompatActivity {  

    private static class MyHandler extends Handler {  
        private final WeakReference<MainActivity> activityWeakReference;  

        MyHandler(MainActivity activity) {  
            activityWeakReference = new WeakReference<>(activity);  
        }  

        @Override  
        public void handleMessage(Message msg) {  
            MainActivity activity = activityWeakReference.get();  
            if (activity != null) {  
                // 处理消息,注意这里要判断activity是否为null  
            }  
        }  
    }  

    private final MyHandler handler = new MyHandler(this);  

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

        // 启动线程并使用handler发送消息  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                Message message = handler.obtainMessage();  
                // 设置消息内容等  
                handler.sendMessage(message);  
            }  
        }).start();  
    }  

    @Override  
    protected void onDestroy() {  
        super.onDestroy();  
        // 不需要显式取消handler,因为handler是静态内部类,不会持有外部类的强引用  
    }  
}

如果Handler不是静态内部类,且你确实需要在非静态内部类中使用它,那么你应该在onDestroy方法中显式地移除所有回调和取消所有未完成的任务。然而,这种方法通常不如使用静态内部类+弱引用来得安全和优雅。

好的,到这里,大概问题我们都知道了,那么内存抖动和内存泄漏,很多时候,你可能自己都不知道,因为:

  1. 有可能你写的代码有问题,但是你没有看出来。
  2. 有可能是第三方框架,或者使用系统的代码有问题,你不知道。

那么怎么办呢?这个时候我们就需要借助工具来将其检测出来。


三、内存抖动工具使用

3.1 内存抖动工具介绍:Profiler

Android Profiler是Android Studio中的一个集成工具,它允许开发者在运行的应用上执行实时性能分析,是评估代码性能的重要工具。
这个章节,我们主要使用Memory Profiler。

Memory Profiler:帮助开发人员理解应用程序的内存使用情况,包括内存分配和回收,通过可视化图表


3.2 我们先来看看如何使用呢

(1)在Android Studio的主界面上,找到并点击“View”菜单,选择“Tool Windows”下的“Profiler”

在这里插入图片描述

(2)我们运行一个程序看看,直接点击运行。

在这里插入图片描述

我们就可以在Profiler里面看到信息。

在这里插入图片描述我们主要使用Memory Profiler,点击它

在这里插入图片描述

在这里插入图片描述


3.3 正常情况的内存

(1)正常的情况

刚启动的时候,内存有波动是正常的,启动后,我们可以看到也是一条直线,这就是正常状态。

在这里插入图片描述
我们可以点击一下Record,开始记录,看看申请的内存变量怎么样。

在这里插入图片描述
在这里插入图片描述
点击排序,可以看看哪个对象最多。


3.4 异常情况的内存

我们看看一个异常代码,比如在自定义view里面不断创建Paint。

public class MemoryJitterView extends View {
    private static final String TAG = "MemoryJitterView";
    private int width;  
    private int height;  
  
    public MemoryJitterView(Context context) {  
        super(context);  
    }  
  
    public MemoryJitterView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
    }  
  
    public MemoryJitterView(Context context, AttributeSet attrs, int defStyleAttr) {  
        super(context, attrs, defStyleAttr);  
    }  
  
    @Override  
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
        super.onSizeChanged(w, h, oldw, oldh);  
        this.width = w;  
        this.height = h;  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        // 这里故意创建新的Paint对象来模拟内存抖动  
        // 在实际应用中,应该避免这样做,而是应该重用Paint对象  
        Paint paint = new Paint();  
        paint.setColor(Color.RED);  
        paint.setStrokeWidth(10);  
  
        // 画一条对角线来演示  
        canvas.drawLine(0, 0, width, height, paint);  
  
        // 为了更频繁地触发onDraw,可以调用invalidate()来请求重绘  
        // 注意:这样做会导致CPU和GPU过度使用,并且在实际应用中是不推荐的  
        // 这里仅用于演示内存抖动  
        postInvalidateOnAnimation(); // 在下一帧动画时间请求重绘
        Log.d(TAG, "onDraw: ");
    }  
}

然后在MainActivity里面使用它

 Handler().postDelayed(object :Runnable{
            override fun run() {
                // 设置自定义View为Activity的内容视图
               setContentView(MemoryJitterView(this@MainActivity))
               setContentView(MemoryJitterView(this@MainActivity))
               setContentView(MemoryJitterView(this@MainActivity))
               setContentView(MemoryJitterView(this@MainActivity))
               setContentView(MemoryJitterView(this@MainActivity))
               setContentView(MemoryJitterView(this@MainActivity))
            }
        },10000)

接下来我们运行程序。

(1)一开始的时候,91.9MB。

在这里插入图片描述随着运行,占用不断的增大,线条也呈斜线递增。

在这里插入图片描述这个时候,我们就点击进行记录分析。

在这里插入图片描述

记录到一定量以后点击停止,就可以开始分析。

在这里插入图片描述

点击一下排序,看看哪些对象产生的最多。

在这里插入图片描述
比如我们点击Paint。

在这里插入图片描述
具体问题位置,代码也刚好是41行,就全部出来。我们就要可以开始优化了。

在这里插入图片描述


四、内存泄漏的工具使用

LeakCanary是一个用于检测Android和Java应用中的内存泄漏的开源工具。但LeakCanary只能检测Activity、Fragment、ViewModel等,比较局限。
想要使用LeakCanary也可以看看这篇文章:https://blog.csdn.net/qq_40853919/article/details/140700972

下面我们来介绍一种MAT的内存分析工具,可以到官网进行下载。


4.1 如何使用

假设一个这样的场景:

  1. A:在MainActivity1界面。
  2. B:跳转到MainActivity2界面,然后再返回。

我想看看MainActivity2关掉以后,有没有出现内存泄漏的情况。所以呢我需要记录MainActivity1的堆栈信息,然后执行完B后跳回来的时候,再记录一次MainActivity1的堆栈信息,然后进行对比,如果有多出来的地方,那么就是内存泄漏了。

(1)记录堆栈

在这里插入图片描述
点击保存。

在这里插入图片描述

就会得到一个这样的文件。

在这里插入图片描述

如果直接放过去MemoryAnalyzer软件里面是会报错的。我们需要转一下格式,需要使用的Android SDK转换工具,hprof-conv命令

在这里插入图片描述
进行类型转换。
在这里插入图片描述

在这里插入图片描述
这样,我们就得到了两个转换后的文件,直接放到MemoryAnalyzer里面。

在这里插入图片描述

接下来使用工具,找到两个文件,直接打开他们。

在这里插入图片描述

到这里,我们已经可以看到内存情况。

在这里插入图片描述

除了柱状图,我们还有其他的数据显示方式。

在这里插入图片描述
如下是直方图的方式。
在这里插入图片描述

如果要进行对比,那么我们只需要点击这个就可以实现堆栈对比。对比两个文件,看看有没有内存泄漏。

在这里插入图片描述

如果是+,那么就是增加了多少,也就是有多少没有释放。
如果是+0,那么就是没有增加,也就是都释放了。

在这里插入图片描述
好了,MAT就介绍到这里。


五、总结

可以看到,其实大部分的原因,都是因为写代码的水平问题,写代码不规范,导致的一些内存抖动,或者内存泄漏。所以我们需要去提升自己的编码能力,去注意这些细节。

在这里插入图片描述在这里插入图片描述

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

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

相关文章

算法题之香槟塔

香槟塔 我们把玻璃杯摆成金字塔的形状&#xff0c;其中 第一层 有 1 个玻璃杯&#xff0c; 第二层 有 2 个&#xff0c;依次类推到第 100 层&#xff0c;每个玻璃杯将盛有香槟。 从顶层的第一个玻璃杯开始倾倒一些香槟&#xff0c;当顶层的杯子满了&#xff0c;任何溢出的香槟…

【TypeScript】知识点梳理(三)

#void前面提到了代表空&#xff0c;但有个特殊情况&#xff0c;是空不是空&#xff0c;细谈是取舍&#xff0c;但我们不深究hhh# 代码示例&#xff1a; type func () > voidconst f1: func function() {return true; } 定义了空&#xff0c;返回非空值&#xff0c;理论…

Windows搭建RTMP服务器

这里写自定义目录标题 1 Nginx-RTMP服务器搭建1.1 下载Nginx1.2 下载Nginx的RTMP扩展包1.3 配置Nginx1.4 启动Nginx1.5 查看Nginx状态 2 FFmpeg推流2.1 下载FFmpeg2.2 配置FFmpeg环境变量2.3 验证FFmpeg配置 3 视频推流3.1 OBS推流3.2 FFmpeg推流 4 VLC拉流4.1 VLC4.2 打开网络…

vue3+PPTXjs 在线ppt预览

- 使用PPTXjs做ppt预览&#xff0c;有完整的代码包&#xff0c;基于jquery - vue3使用iframe引入用于预览ppt的网页&#xff0c;通过url参数传递需要预览的ppt链接 - 通过网页选择文件上传也可以通过下面的函数把文件转换成链接&#xff0c;实现在文件上传到服务器前就可以预…

【深度强化学习】DDPG实现的4个细节(OUNoise等)

文章目录 前言一、论文内容简述创新点&#xff08;特点&#xff0c;与DQN的区别&#xff09;&#xff1a;可借鉴参数&#xff1a;细节补充&#xff1a; 二、细节1&#xff1a;weight_decay原理代码 三、细节2&#xff1a;OUNoise原理代码 四、细节3&#xff1a;ObsNorm原理代码…

PostgreSQL分区表,实战细节满满

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、My…

茄子病虫害数据集。四类:果肉腐烂、蛀虫、健康、黄斑病。4000张图片,已经按照8:2的比例划分好训练集、验证集 txt格式 含类别yaml文件 已经标注好

茄子病虫害数据集。可用于筛选茄子品质、质量&#xff0c;训练采摘机器人视觉算法模型……数据集大部分图片来源于真实果园拍摄的图片&#xff08;生长在果树之上的&#xff09;&#xff0c;图片分辨率高&#xff0c;数据集分为四类&#xff1a;果肉腐烂、蛀虫、健康、黄斑病。…

如何使用ssm实现基于Java的民宿预订管理系统的设计与实现

TOC ssm773基于Java的民宿预订管理系统的设计与实现jsp 绪论 1.1课题研究背景意义 随着科技的发展&#xff0c;计算机的应用&#xff0c;人们的生活方方面面都和互联网密不可分。计算机的普及使得人们的生活更加方便快捷&#xff0c;网络也遍及到我们生活的每个角落&#x…

Vue - 打包部署

vscode找到NPM脚本&#xff0c;点击build。 目录下出现dist目录则表示安装成功。 安装Nginxnginx: download 目录用途conf配置文件目录html静态资源文件目录logs日志文件目录temp临时文件目录 将刚刚打包好的文件放到html目录下。 点击nginx.exe&#xff0c;用localhost:默认…

Windows应急响应-QQ巨盗病毒

文章目录 病毒背景样本分析开启监控感染病毒分析病毒行为C盘文件监控D盘文件监控进程监控排查服务排查启动项排查 查杀1.杀掉进程2.异常服务3.映像劫持处理4.hosts文件处理5.D盘文件删除6.其他异常排查 重启排查 病毒背景 简介&#xff1a;Win32.PSWTroj.QQPass&#xff0c;名…

模拟退火算法简介

什么是模拟退火算法&#xff1f; 模拟退火算法&#xff08;Simulated Annealing&#xff0c;SA&#xff09;是一种基于随机化搜索的优化算法&#xff0c;灵感来源于金属退火过程。在金属制造中&#xff0c;金属被加热到高温并缓慢冷却&#xff0c;这一过程可以减少内部缺陷&am…

【前端】-音乐播放器(源代码和结构讲解,大家可以将自己喜欢的歌曲添加到数据当中,js实现页面动态显示音乐)

前言&#xff1a;音乐播放器是前端开发中的一个经典项目&#xff0c;通过它可以掌握很多核心技术&#xff0c;如音频处理、DOM操作、事件监听、动画效果等。这个项目不仅能提升前端开发的技能&#xff0c;还能让开发者深入理解JavaScript与HTML的协同作用。 页面展示&#xff1…

精准选择大模型:消费品行业的营销与体验创新之路

在消费品行业&#xff0c;大模型技术的引入正逐渐从一个新兴趋势转变为行业标配。随着人工智能的快速发展&#xff0c;特别是OpenAI等领军企业推出的创新技术&#xff0c;如Sora&#xff0c;大模型在市场营销、消费者行为分析、个性化推荐等方面展现出巨大潜力。然而&#xff0…

基础算法(5)——位运算

1. 常见位运算总结 1) 基础位运算 2) 给一个数 n&#xff0c;确定它的二进制表示中的第 x 位是 0 还是 1 3) 将一个数 n 的二进制表示的第 x 位修改成 1 4) 将一个数 n 的二进制表示的第 x 位修改成 0 5) 位图的思想 位图的本质就是 哈希表 6) 提取一个数 n 二进制表示中最右…

如 有 任 何 问 题 ,请 及 时 联 系 我 们 反 馈 !

如有任何问题&#xff0c; 请及时联系我们反馈 !https://support.qq.com/products/671606 如有任何问题&#xff0c; 请及时联系我们反馈 !

Bluetooth Channel Sounding中关于CS Procedure的详细介绍

目录 BLE CS 过程定义&#xff1a; BLE CS 过程的组成部分 开始一个BLE CS 过程 与BLE CS过程相关的参数设置 BLE CS 过程定义&#xff1a; BLE 的CS特性包含一组LL层和空口协议的组合过程&#xff0c;该过程可以使得两个BLE 设备以紧密互锁的方式&#xff0c;在多个信道上…

Ubuntu 上安装 MySQL 并且实现远程登录

目录 1. 安装MySQL 2. 安全配置MySQL 3. 配置MySQL远程登录 3.1. 允许远程连接 3.2. 重启MySQL服务 3.3. 为用户分配远程访问权限 进入MySQL后&#xff0c;执行以下命令&#xff1a; 3.4. 创建新用户 3.5. 授予权限 3.6. 刷新权限 3.7. 退出 MySQL 控制台 4. 配置防火…

2024.9月29日~10月6日 SSM框架项目-《电信资费管理系统》

一、数据库介绍&#xff1a; 1、account&#xff1a;帐务信息表 2、admin_info&#xff1a;管理员信息表 3、admin_role&#xff1a;管理员角色信息表 4、cost&#xff1a;资费信息表 5、privilege_info&#xff1a;权限信息表 6、role_info&#xff1a;角色信息表 7、role_pri…

在CentOS7上安装mysql

目录 1.下载安装文件 2.上传到CentOS7上 3.解压MySQL文件 4.清理系统中残留的MySQL 5.安装MySQL 6.启动MySQL 并 设置开机自启动 7.查找临时密码&#xff0c;并修改密码 注意&#xff1a; 教程&#xff1a;在Linux上安装mysql&#xff08;超详细版&#xff09;_哔哩哔哩…

人工智能新闻和发展 (24001)- By 10/4/2024

1. 谷歌增强了搜索中的人工智能&#xff0c;允许对图像进行语音提问。 Google adding AI to answer voiced questions about images | AP NewsGoogle is pumping more artificial intelligence into its search engine. New features will enable people to voice questions a…