一、前期基础知识储备
感兴趣的读者,可以参考笔者之前的文章《Android中内存泄漏超级精炼详解》,里面有对内存泄漏是什么、内存泄漏的常见原因、内存泄漏和内存溢出的联系与区别等相关内容的详解说明。
Android内存泄漏优化是Android性能优化中很重要的一部分,今天本节文章作为Android性能优化的第二节,将和读者一起探究内存优化的一些方法。
内存泄漏在开发中是一个需要重视的问题,但是由于内存泄漏问题对开发人员的经验和开发意识有较高的需求,因此这也是开发人员最常犯的错误之一。内存泄漏优化分为两个方面:
①在开发过程中避免写出有内存泄漏的代码,这需要我们熟悉常见内存泄漏发生地;
②通过一些分析工具来找出潜在的内存泄漏继而解决它。
二、常见的内存泄漏问题和对应的优化方案
(1)静态变量导致的内存泄漏
首先先回顾一下静态变量相关知识:
①final关键字——表示一个值在初始化的时候就不能再改变了;表示方法不能被重写或者一个类不能有子类;
②static关键字——表示在类级别定义,所有实例共享的。
③静态变量,以关键字static声明;
④静态变量,除了被声明为常量之外,很少单独使用;
⑤静态变量在程序开始是创建,在程序结束时销毁;
⑥常量是指声明为public/private,final和static类型的静态变量,常量名需大写,常量初始化后不可改变。
//定义了一个静态变量,该静态变量持有了Activity的引用
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private static Context sContext;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sContext = this;
}
}
以上是一种最简单的内存泄漏,上面的代码将导致Activity无法正常销毁,因为静态变量sContext引用了它。
优化方案,将Activity的context改为Application的context。
(2)单例模式导致的内存泄漏
参见笔者《Android单例模式详解》,文章详细介绍了Android单例模式的相关内容。
//运用了内部类Holder式的方式创建了一个线程安全的懒汉式的单例模式
public class TestManager {
private static class SingletonHolder {
public static final TestManager INSTANCE = new TestManager();
}
private TestManager() {
}
public static TestManager getInstance() {
return SingletonHolder.INSTANCE;
}
}
//Activity代码中,该Activity的对象被单例模式所持有,造成内存泄漏
TestManager.getInstance().xxx(this)
单例模式带来的内存泄漏比较容易忽视。因为单例模式的特点是其生命周期和Application保持一致,所以若是引用不当,单例模式的实例对象持有了一个生命周期短的对象的引用,那么该引用的实例对象是得不到释放的,从而引起了内存泄漏。
优化方案:将Activity的context改为Application的context。
(3)外部类中持有非静态内部类的静态对象导致的内存泄漏
首先,我们要知道,非静态内部类 / 匿名类 默认持有外部类的引用;而静态内部类则不会。
public class TestActivity extends AppCompatActivity {
// 非静态内部类的实例的引用
// 注:设置为静态
public static InnerClass innerClass = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 保证非静态内部类的实例只有1个
if (innerClass == null)
innerClass = new InnerClass();
}
// 非静态内部类的定义
private class InnerClass {
//...
}
}
上面代码中,当TestActivity销毁时,因非静态内部类单例的引用(innerClass)的生命周期 = 应用App的生命周期且其持有外部类TestActivity的引用,故 TestActivity无法被GC回收,从而导致内存泄漏。
优化方案:将非静态内部类设置为:静态内部类(静态内部类默认不持有外部类的引用)。
(4)集合类导致的内存泄漏
集合类添加元素后,仍引用着集合元素对象,导致该集合元素对象不可被回收,从而导致内存泄漏。
// 通过 循环申请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个集合中有许多元素,故最简单的方法 = 清空集合对象 & 设置为null。代码如下:
// 释放objectList
objectList.clear();
objectList=null;
(5)资源对象使用后未关闭导致的内存泄漏
对于资源的使用(如 广播BraodcastReceiver、文件流File、数据库游标Cursor、图片资源Bitmap等),若在Activity销毁时无及时关闭 / 注销这些资源,则这些资源将不会被回收,从而造成内存泄漏
优化方案:
①在Activity销毁时 及时关闭/ 注销资源;
②对于广播BraodcastReceiver:注销注册;
unregisterReceiver()
③对于文件流File:关闭流;
InputStream/ OutputStream.close()
④对于数据库游标cursor:使用后关闭游标;
cursor.close()
⑤对于图片资源Bitmap:Android分配给图片的内存只有8M,若1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null
Bitmap.recycle();
Bitmap = null;
⑥对于动画(属性动画):将动画设置成无限循环播放repeatCount = “infinite”后,在Activity退出时记得停止动画
Animator.cancel();
⑦对于Service,使用之后应该关闭,推荐使用集开启线程和自动停止的IntentService。
总结:写到这太累了,就不写总结了,如果有不理解的读者建议可以先看笔者之前那篇关于内存泄漏的文章《Android中内存泄漏超级精炼详解》,再来看这篇内存泄漏优化相关的文章,会便于理解很多。