前言
在Android中,内存泄露的现象十分常见;而内存泄露导致的后果会使得应用Crash。常见引发内存泄露原因主要有:
- Static关键字修饰的成员变量
- 非静态内部类 / 匿名类
- 日常使用(Context、WebView等)
今天,我将详细介绍日常的使用会导致的内存泄露,主要包括:集合类、资源对象使用后未关闭、Context、WebView和Adapter。
知识储备
a. 内存泄漏简介
即 ML (Memory Leak),指 程序在申请内存后,当该内存不需再使用 但 却无法被释放 & 归还给 程序的现象
b. 内存泄漏的影响:
a. 容易使得应用程序发生内存溢出,即 OOM
b. 内存溢出的简介:
c. 内存泄露的本质原因
结论:本该被回收的对象 因为某些原因 而不能被回收,从而继续停留在堆内存中
解释:
- ”本该被回收“ = 该对象 已不需再被使用
- ”因某些原因 而 不能被回收“的原因 = 有另外1个正在使用的对象持有它的引用,即:无意识地持有对象
本质原因:持有引用者的生命周期 > 被引用者的生命周期,从而 当后者需结束生命周期被销毁时,无法被正确回收。
总结:
a. 当1个对象已不需再被使用、本该被GC回收时,而因有另外1个正在使用的对象持有它的引用 从而导致它不能被程序回收 而停留在堆内存中
b. 本质原因 = 持有引用者的生命周期 > 被引用者的生命周期
特别注意:
从机制上的角度来说,由于 Java存在垃圾回收机制(GC),理应不存在内存泄露;出现内存泄露的原因仅仅是外部人为原因 = 无意识地持有对象引用,使得 持有引用者的生命周期 > 被引用者的生命周期
d. Android 内存管理机制
e. 类型
日常使用中,容易发生内存泄漏常见情况有4种:
- 集合类
- 资源对象使用后未关闭
- Context
- WebView
- Adapter
下面,我将上述所有情况进行逐一详细介绍。
1. 集合类
1.1 内存泄露原因
集合类 添加元素后,仍引用着 集合元素对象,导致该集合元素对象不可被回收,从而 导致内存泄漏
1.2 实例演示
// 通过 循环申请Object 对象 & 将申请的对象逐个放入到集合List
List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object o = new Object();
objectList.add(o);
o = null;
}
// 虽释放了集合元素引用的本身:o=null)
// 但集合List 仍然引用该对象,故垃圾回收器GC 依然不可回收该对象
1.3 解决方案
集合类 添加集合元素对象 后,在使用后必须从集合中删除。由于1个集合中有许多元素,故最简单的方法 = 清空集合对象 & 设置为null
// 释放objectList
objectList.clear();
objectList=null;
2. 资源对象使用后未关闭
2.1 泄露原因
对于资源的使用(如 广播BraodcastReceiver、文件流File、数据库游标Cursor、图片资源Bitmap等),若在Activity销毁时无及时关闭 / 注销这些资源,则这些资源将不会被回收,从而造成内存泄漏。
2.2 解决方案
在Activity销毁时 及时关闭 / 注销资源。
// 对于 广播BraodcastReceiver:注销注册
unregisterReceiver()
// 对于 文件流File:关闭流
InputStream / OutputStream.close()
// 对于数据库游标cursor:使用后关闭游标
cursor.close()
// 对于 图片资源Bitmap:Android分配给图片的内存只有8M,若1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null
Bitmap.recycle();
Bitmap = null;
// 对于动画(属性动画)
// 将动画设置成无限循环播放repeatCount = “infinite”后
// 在Activity退出时记得停止动画
3. Context
3.1 内存泄漏原因
a. 当 拥有该Activity Context参数对象仍在使用 、而该Activity需销毁时,该Activity则由于被保持引用而无法被回收,从而造成内存泄露
b. 即,当拥有Context参数的对象的生命周期 > 该Context参数的生命周期时,则容易出现内存泄露
3.2 解决方案
对Context的引用不要超过它本身的生命周期:
a. 如:尽量使用ApplicationContext代替ActivityContext
b. 因ApplicationContext会随着应用程序的存在而存在,而不依赖于activity的生命周期
3.3 特别注意
a. 在Android中,通常可使用Context对象有2种:Activity、Application
b. 当类 / 方法需Context对象时,常优先使用 Activity作为Context参数;此时该对象对整个Activity保持引用
4. WebView
4.1 内存泄漏原因
不再使用WebView对象后 无销毁,导致占用的内存长期无法被回收,从而造成内存泄露
4.2 解决方案
通过多线程在不使用WebView对象时进行销毁
4.3 示例
如:
- 为WebView开启另1个进程
- 通过AIDL与主线程进行通信,WebView所在的进程可根据业务的需要,选择在合适的时机销毁,从而达到内存的完整释放
5. Adapter
5.1 内存泄漏原因:
a. 在滑动ListView获取最新的View时,容易频繁生成大量对象
b. 即 每次都在getView()中重新实例化1个View对象
c. 不仅浪费资源、时间,也将使得内存占用越来越大,从而使得内存泄露
5.2 解决方案
a. 使用缓存的convertView
b. 直接使用 ViewHolder
5.3 特别注意
- 初始时,ListView会根据当前的屏幕布局 从Adapter中实例化一定数量的 View对象,同时ListView会将这些view对象缓存起来
- 当向上、下滚动ListView时,原先位于最上、下面的List Item的View对象会被回收,然后被用来构造新出现的最下面的list item
- 这个构造过程由getView()方法完成:来向ListView提供每一个item所需要的view对象
- getView()的第2个形参View convertView = 被缓存起来的list item的view对象
- 初始化时缓存中没有view对象,则convertView = null
6. 总结
本文全面总结了日常使用中的内存泄漏情况,总结如下
至此,关于日常使用中的内存泄漏情况讲解完毕。
最后我想说:对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
最后这里是关于我自己的Android 学习,面试文档,视频收集大整理,有兴趣的伙伴们可以看看~