Android性能优化:你的一些使用细节可能会引起严重的内存泄漏!

前言

在Android中,内存泄露的现象十分常见;而内存泄露导致的后果会使得应用Crash。常见引发内存泄露原因主要有:

  1. Static关键字修饰的成员变量
  2. 非静态内部类 / 匿名类
  3. 日常使用(Context、WebView等)

今天,我将详细介绍日常的使用会导致的内存泄露,主要包括:集合类、资源对象使用后未关闭、Context、WebView和Adapter。

知识储备

a. 内存泄漏简介

即 ML (Memory Leak),指 程序在申请内存后,当该内存不需再使用 但 却无法被释放 & 归还给 程序的现象

b. 内存泄漏的影响:

a. 容易使得应用程序发生内存溢出,即 OOM
b. 内存溢出的简介:

c. 内存泄露的本质原因

结论:本该被回收的对象 因为某些原因 而不能被回收,从而继续停留在堆内存中

解释

  1. ”本该被回收“ = 该对象 已不需再被使用
  2. ”因某些原因 而 不能被回收“的原因 = 有另外1个正在使用的对象持有它的引用,即:无意识地持有对象

本质原因:持有引用者的生命周期 > 被引用者的生命周期,从而 当后者需结束生命周期被销毁时,无法被正确回收。

总结
a. 当1个对象已不需再被使用、本该被GC回收时,而因有另外1个正在使用的对象持有它的引用 从而导致它不能被程序回收 而停留在堆内存中
b. 本质原因 = 持有引用者的生命周期 > 被引用者的生命周期

特别注意
从机制上的角度来说,由于 Java存在垃圾回收机制(GC),理应不存在内存泄露;出现内存泄露的原因仅仅是外部人为原因 = 无意识地持有对象引用,使得 持有引用者的生命周期 > 被引用者的生命周期

d. Android 内存管理机制

e. 类型

日常使用中,容易发生内存泄漏常见情况有4种:

  1. 集合类
  2. 资源对象使用后未关闭
  3. Context
  4. WebView
  5. 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 示例

如:

  1. 为WebView开启另1个进程
  2. 通过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 学习,面试文档,视频收集大整理,有兴趣的伙伴们可以看看~

发布了212 篇原创文章 · 获赞 783 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/weixin_44339238/article/details/104516709
今日推荐