Android内存优化
文章出处:zhuazhu---原文地址
首先我们来解释下两个名词:
内存泄漏
当一个对象在程序执行过后已经不需要再使用了,但是有其他的对象还持有该对象的引用,以致该对象不能被GC回收,那么这个对象会一直占用内存,从而导致该内存不可用,这种本该被GC回收(不再需要用了)而又不能被回收(被其他对象持有引用),以致停留在堆内存中的对象就造成了内存泄露.
内存溢出
内存溢出(OutOfMeory),即我们通常所说的OOM,是指程序在申请内存时,没有足够的内存空间共其使用.
内存泄露的危害
过多的内存泄露最终会导致内存溢出(OOM)
内存泄露导致内存不足,会触发频繁GC,从而导致UI卡顿
常见内存泄露问题及解决方案
1.单例
主要原因是因为一般情况下单列都是全局的,有时候会引用一些实际生命周期比较短的变量,导致其无法释放.
解决方案:
// 使用了单例模式 // 如果Context使用的是Activity的Context,则会造成内存溢出 //单例的Context 使用Application的Context,单例的生命周期和应用的一样长,这样基本可以防止单例引起来的内存泄露内存泄漏。 public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context; } public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; } }
2.非静态内部类创建静态实例造成的内存泄漏
public class MainActivity extends AppCompatActivity { private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mResource == null){ mResource = new TestResource(); } //... } class TestResource { //... } }
非静态内部类默认会持有外部类的引用,而改非静态内部类有创建了一个静态实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Actvity的引用,从而导致Activity的内存资源不能被正常的回收.
解决方案:将改内部类设为静态内部类或者将该内部类抽取出来封装成一个单列,如果需要使用Context,就使用Application的Context.
3.Handler造成的内存泄露
public class MainActivity extends AppCompatActivity { private final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // ... } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { // ... handler.sendEmptyMessage(0x123); } }); }
原因:当MainActivity结束时,未处理的消息持有Handler的引用,而handler又持有他所属的外部类也就是MainActivity,这条引用关系会一直保持知道消息得到处理,这样阻止了MainActivity内垃圾回收器回收,从而造成内存泄漏
解决方案:将Handler类独立出来或者使用静态内部类,这样便可以避免内存泄露
4.资源未关闭
比如在Activity中register了一个BroadcastReceiver,但是Activity结束后没有unregister该BroadcastReceiver
资源性对象比如Cursor,Stream,File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时关闭内存,它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外,如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露.
对于资源性对象在不使用的时候,应该调用他的close()函数将其关闭,然后再设置为null,在我们的程序退出时,一定要确保我们的资源性对象已经关闭
Bitmap对象不再使用的时候调用recycle(),2.3以后的bitmao应该不再需要手动recycle()了,内存已经在java层了
5.使用轻量的数据结构
使用ArrayMap/SparseArray来代替HashMap,ArrayMap/SparseArray是专门为于东设备设计的高效的数据结构
HashMap
HashMap内部使用了一个默认容量为16的数组来存储数据,采用拉链法解决hash冲突(数据+链表).
HashMap就算没有数据,也需要分配默认16个元素的数组,一旦数据量达到HashMap限定容量的75%,就将按两倍扩容
SparseArray
支持int类型,避免自动装箱,但是也只支持int类型的key
内部通过两个数据来进行数据存储,一个存储key,另外一个存储value
因为key是int,在查找时,采用二分查找,效率高,SparseArray存储的元素都是按元素的key值从小到大排列好的
默认初四size为0,每次增加元素,size++
ArrayMap
跟SparseArray一样,内部两个数组,但是第一个存key的hash值,一个存value,对象按照key的hash值排序,二分查找也是按照hash
查找index时,传入key,计算出hash,通过二分查找hash数组,确定index
6.用StringDef和IntDef来代替枚举(Enum)
枚举占用的内存过大,google官方建议用注解StringDef和IntDef来替换枚举
7.Bitmap处理
对Bitmap压缩
Lru机制处理Bitmap
使用有名的图片缓存框架(我一般使用这种)
8.不要使用String进行字符串拼接
严格的讲,String拼接只能归结到内存抖动中,因为产生的String副本能够被GC,不会造成内存泄漏
频繁的字符串拼接,使用StringBuffer或者StringBuiler代替String,可以在一定程度上避免OOM和内存抖动
9.谨慎使用static对象
static对象的生命周期过程,应该谨慎使用