Android 内存原理详解以及优化(二)

server/2024/9/23 4:10:08/

上一篇讲了内存原理,如果还没看可以先看上一篇:Android 内存原理详解以及优化(一)
这一篇我总结一下我们经常遇到的内存优化问题:
1.内存抖动
自定义view的ondraw是会被频繁调用的,那在这个方法里面就不能频繁的new object 对象频繁的创建和回收就会内存抖动,凡是频繁调用的方法,都禁止类似这样的操作。
2.内存泄露
内存泄露的本质是某个对象已经不需要再用了,但是它却没有被系统所回收,一直在内存中占用着空间,而导致它无法被回收的原因大多是由于它被一个生命周期更长的对象所引用,用什么引用?引用链(在上一篇,gc内存的垃圾回收机制 使用的是GCRoot 可达性分析有解释)
举个例子,如果一个 Activity 被一个单例对象所引用,那么当退出这个 Activity 时,由于单例的对象依然存在(单例对象的生命周期跟整个 App 的生命周期一致),而单例对象又持有 Activity 的引用,这就导致了此 Activity 无法被回收,从而造成内存泄漏。

知道了内存泄漏的根本原因,再分析为什么会出现内存泄漏就很简单了,下面就针对一些常见的内存泄漏进行分析。
单例造成的内存泄漏

刚才已经分析过了,假设有一个单例是这样的
public class SingleTon {

private static SingleTon singleTon;private Context context;private SingleTon(Context context) {this.context = context;
}public static SingleTon getInstance(Context context) {if (singleTon == null) {synchronized (SingleTon.class) {if (singleTon == null) {singleTon = new SingleTon(context);}}}return singleTon;
}

}

这是单例模式饿汉式的双重校验锁的写法,这里的 singleTon 持有 Context 对象,如果 Activity 中调用 getInstance 方法并传入 this 时,singleTon 就持有了此 Activity 的引用,当退出 Activity 时,Activity 就无法回收,造成内存泄漏,所以应该修改它的构造方法
private SingleTon(Context context) {
this.context = context.getApplicationContext();
}

通过 getApplicationContext 来获取 Application 的 Context,让它被单例持有,这样退出 Activity 时,Activity 对象就能正常被回收了,而 Application 的 Context 的生命周期和单例的生命周期是一致的,所有再整个 App 运行过程中都不会造成内存泄漏。
非静态内部类造成的内存泄漏

我们知道,非静态内部类会持有外部类的引用,如果这个非静态的内部类的生命周期比它的外部类的生命周期长,那么当销毁外部类的时候,它无法被回收,就会造成内存泄漏。
外部类中持有非静态内部类的静态对象

假设 Activity 的代码是这样的
public class MainActivity extends AppCompatActivity {

private static Test test;@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (test == null) {test = new Test();}}private class Test {}

}

这个其实和单例的原理是一样的,由于静态对象 test 的生命周期和整个应用的生命周期一致,而非静态内部类 Test 持有外部类 MainActivity 的引用,导致 MainActivity 退出的时候不能被回收,从而造成内存泄漏,解决的方法也很简单,把 test 改成非静态,这样 test 的生命周期和 MainActivity 是一样的了,就避免了内存泄漏。或者也可以把 Test 改成静态内部类,让 test 不持有 MainActivity 的引用,不过一般没有这种操作。

Handler 或 Runnable 作为非静态内部类

handler 和 runnable 都有定时器的功能,当它们作为非静态内部类的时候,同样会持有外部类的引用,如果它们的内部有延迟操作,在延迟操作还没有发生的时候,销毁了外部类,那么外部类对象无法回收,从而造成内存泄漏,假设 Activity 的代码如下
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);new Handler().postDelayed(new Runnable() {@Overridepublic void run() {}}, 10 * 1000);
}

}

上面的代码中,Handler 和 Runnable 作为匿名内部类,都会持有 MainActivity 的引用,而它们内部有一个 10 秒钟的定时器,如果在打开 MainActivity 的 10 秒内关闭了 MainActivity,那么由于 Handler 和 Runnable 的生命周期比 MainActivity 长,会导致 MainActivity 无法被回收,从而造成内存泄漏。

那么应该如何避免内存泄漏呢?这里的一般套路就是把 Handler 和 Runnable 定义为静态内部类,这样它们就不再持有 MainActivity 的引用了,从而避免了内存泄漏
public class MainActivity extends AppCompatActivity {

private Handler handler;private Runnable runnable;@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);handler = new TestHandler();runnable = new TestRunnable();handler.postDelayed(runnable, 10 * 1000);
}private static class TestHandler extends Handler {}private static class TestRunnable implements Runnable {@Overridepublic void run() {Log.d(TAG, "run: ");}
}private static final String TAG = "MainActivity";

}

还要在 onDestory 调用 handler 的 removeCallbacks 方法来移除 Message,因为如果在退出 Activity 关闭后,正好触发执行 run 方法,就也会造成message持有activity的引用,内存泄露
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacks(runnable);
}

还有一种特殊情况,如果 Handler 或者 Runnable 中持有 Context 对象,那么即使使用静态内部类,还是会发生内存泄漏
public class MainActivity extends AppCompatActivity {

private Handler handler;private Runnable runnable;@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);handler = new TestHandler(this);runnable = new TestRunnable();handler.postDelayed(runnable, 10 * 1000);
}private static class TestHandler extends Handler {private Context context;private TestHandler(Context context) {this.context = context;}
}private static class TestRunnable implements Runnable {@Overridepublic void run() {Log.d(TAG, "run: ");}
}private static final String TAG = "MainActivity";

}

上面的代码,使用 leakcanary 工具会发现依然会发生内存泄漏,而且造成内存泄漏的原因和之前用非静态内部类是一样的,那么为什么会出现这种情况呢?

这是由于在 Handler 中持有 Context 对象,而这个 Context 对象是通过 TestHandler 的构造方法传入的,它是一个 MainActivity 对象,也就是说,虽然 TestHandler 作为静态内部类不会持有外部类 MainActivity 的引用,但是我们在调用它的构造方法时,自己传入了 MainActivity 的对象,从而 handler 对象持有了 MainActivity 的引用,handler 的生命周期比 MainActivity 的生命周期长,因此会造成内存泄漏,这种情况可以使用弱引用的方式来引用 Context 来避免内存泄漏,代码如下
public class MainActivity extends AppCompatActivity {

private Handler handler;private Runnable runnable;@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);handler = new TestHandler(new WeakReference<Context>(this));runnable = new TestRunnable();handler.postDelayed(runnable, 10 * 1000);
}private static class TestHandler extends Handler {private Context context;private TestHandler(WeakReference<Context> weakContext) {context = weakContext.get();}
}private static class TestRunnable implements Runnable {@Overridepublic void run() {Log.d(TAG, "run: ");}
}private static final String TAG = "MainActivity";

}

其他的内存泄漏情况

还有一些其他的会导致内存泄漏的情况,比如 BraodcastReceiver 未取消注册,InputStream 未关闭等,这类内存泄漏非常简单,只要在平时写代码时多多注意即可避免。
处理内存泄露当然不能没有 leakCanary这个工具,它的原理,我也分三篇来分析了,敢兴趣可以看看:leakcanary源码详解

3.内存溢出
3.1 对于大图片加载的时候,会内存溢出。这是我开发中遇到的,如果泛指的话,可以归结于为打对象分配内存 加载大图片bitmap内存溢出
3.2 内存泄露逐渐积累也会内存溢出
上文中说了内存泄露的处理,在此不再赘述。
3.3 jni native 内存地址分配的,这个是听说,待验证。印象中是有的。

4.扩大内存的手段
4.1一个应用如果使用了largeHeap,会请求系统为Dalvik虚拟机分配更大的内存空间。使用起来也很方便,只需在manifest文件application节点加入android:largeHeap=“true” 默认128M ,设置后能分配的多大,取决于系统限制和设备硬件情况。注意不用用这个方法去解决oom,你走偏了,走正道。
4.2 应用开多进程,这样一个进程分配128M,多个能翻倍使用。
在 AndroidManifest.xml 中配置 android:process:

第一种:如 android:process = “:remote”,以 : 开始,后面的字符串是可以随意指定的。如果包名是 com.cah.androidtest,所以实际进程名是 
com.cah.androidtest:remote。这种设置形式表示该进程为当前应用的私有进程,其他应用的组件不可以和它跑在同一进程中第二种:如 android:process = “com.cah.androidtest.remote”,以小写字母开头,表示运行在一个以这个名字命名的全局进程中,其他应用的组件
可以和它跑在同一进程中(使用 SharedUID,且签名一致),从而减少资源的占用。

http://www.ppmy.cn/server/55792.html

相关文章

【IO】文件操作

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. 文件1.1 认识文件1.2 分清操作的是内存还是硬盘1.3 路径1.3.1 目录结构1.3.2 相对和绝对路径 1.4 文本文件…

科普文:linux I/O原理、监控、和调优思路

Linux 文件系统 磁盘和文件系统的关系&#xff1a; 磁盘为系统提供了最基本的持久化存储。 文件系统则在磁盘的基础上&#xff0c;提供了一个用来管理文件的树状结构。 文件系统工作原理 索引节点和目录项 文件系统&#xff0c;本身是对存储设备上的文件&#xff0c;进行…

深圳晶彩智能ESP32-2432S028R实时观察LVGL9效果

深圳晶彩智能ESP32-2432S028R概述&#xff1a; 深圳晶彩智能出品ESP32-32432S028R为2.8寸彩色屏采用分辨率320x240彩色液晶屏&#xff0c;驱动芯片是ILI9431。板载乐鑫公司出品ESP-WROOM-32&#xff0c;Flash 4M。型号尾部“R”标识电阻膜的感压式触摸屏&#xff0c;驱动芯片是…

[FreeRTOS 功能应用] 事件组 功能应用

文章目录 一、基础知识点二、代码讲解三、结果演示四、代码下载 一、基础知识点 [FreeRTOS 基础知识] 事件组 概念 [FreeRTOS 内部实现] 事件组 本实验是基于STM32F103开发移植FreeRTOS实时操作系统&#xff0c;事件组实战操作。(当task1和task2同时完成&#xff0c;才执行ta…

【数据分享】全国乡村旅游重点镇(乡)数据(Excel/Shp格式/免费获取)

之前我们分享过从我国文化和旅游部官网整理的2018-2023年我国50个重点旅游城市星级饭店季度经营状况数据&#xff08;可查看之前发布的文章&#xff09;&#xff01;文化和旅游部官网上也分享有很多与旅游相关的常用数据&#xff0c;我们基于官网发布的名单文件整理得到全国乡村…

基于SpringBoot+Vue的招生管理系统(带1w+文档)

基于SpringBootVue的招生管理系统(带1w文档&#xff09; 通过招生管理系统的研究可以更好地理解系统开发的意义&#xff0c;而且也有利于发展更多的智能系统&#xff0c;解决了人才的供给和需求的平衡问题&#xff0c;招生管理系统的开发建设&#xff0c;由于其开发周期短&…

Kafka 进阶指南

Kafka 进阶指南 引言 在掌握了 Kafka 的基本概念和操作后&#xff0c;我们可以进一步探索 Kafka 的高级特性和使用技巧&#xff0c;以提高其性能、可扩展性和可靠性。本指南将介绍 Kafka 的进阶主题&#xff0c;包括性能调优、扩展策略、数据复制、日志压缩、流处理和安全性。…

价格预言机的使用总结(一):Chainlink篇

文章首发于公众号&#xff1a;Keegan小钢 前言 价格预言机已经成为了 DeFi 中不可获取的基础设施&#xff0c;很多 DeFi 应用都需要从价格预言机来获取稳定可信的价格数据&#xff0c;包括借贷协议 Compound、AAVE、Liquity &#xff0c;也包括衍生品交易所 dYdX、PERP 等等。…