Android性能优化篇——内存泄漏和OOM
Android性能优化篇——内存泄漏和OOM
老规矩,在讲解以前先提出问题:
(1)什么是内存泄漏?什么是内存溢出
(2)如何检测app的最大堆内存?
(3)如何测量内存泄漏?
(4)如何避免内存泄漏?
好了,直接进入主题。
一、内存泄漏和内存溢出
1、内存泄漏
内存泄漏是指某些对象本应该被GC回收,但是由于他们的引用被其他对象持有而导致GC回收失败,从而无法回收占用的内存,结果导致这些无用对象仍然占据着堆中的内存空间,成为内存泄漏。
内存泄露的危害:
(1)过多的内存泄露会导致内存被过多占用,容易发生OOM
(2)内存泄露可能给会导致内存不足,然后频发发生GC,可能会导致UI卡顿,线程停止等问题。
2、内存溢出
Android为每个进程会设置一个内存的阈值,如果超过这个阈值则会发生内存溢出,程序就会崩溃。
内存溢出的危害:
程序可能会崩溃,为什么是可能,因为内存溢出可以被try——catch。
二、app的堆内存阈值
这里的阈值与设备有关系,所以不是一个固定的数值。
通常我们可以通过两个参数来查看我们堆内存阈值:
heapsize 设备分给堆内存的最大堆内存
maxheapsize 用于特殊情况下(设置了清单文件中的application的属性largeHeap = true)时才有的最大堆内存,一般为heapsize的2~3倍,设置后则最大堆内存为maxheapsize、
那么如何获取这两个值:
ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int heapSize = manager.getMemoryClass();
int maxHeapSize = manager.getLargeMemoryClass(); // manafest.xml android:largeHeap="true"
三、如何检测内存泄露
可以通过As的profiler+facebook的开源库 leakcanary来分析
二、常见的内存泄露场景
1、非静态内部类的内存泄露
原因:
非静态内部类会持有外部类的this引用,因此可以访问外部类的静态和非静态变量。如果内部类的生命周期超过外部类,则会导致外部类无法被及时回收
解决方法:
创建static静态变量,并通过弱引用WeakReference来引用外部资源
2、单例模式持有Activity的引用
原因:
在单例模式中,如果传入Activity的Context,那么在Activity退出时,由于Context持有Activity的引用,因此导致Activity不会被回收
解决方法:
改为Application的Context
3、Handler造成的内存泄露
我们常见的写法是:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...
}
};
也就是我们前面提高的非静态内部类,持有外部类的this引用,那么假如说消息队列中还有没有处理完的Message,而Message又有一个属性变量Handler,也就是持有当前Handler对象的引用,而Handler又持有当前Activity的引用,这样就可能导致Activity已经结束,但是由于引用被持有而无法释放。因此我们改进的方法主要是两方面,第一,Acitvity结束时调用 mHandler.removeCallbacksAndMessages(null);清空消息队列中的消息和Runnable。第二,改为static内部类,不持有外部类的this引用。
改为如下:
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
4、线程造成的内存泄露
直接使用AsyncTask和Thread,相当于使用一个非静态内部类,因此会持有当前Activity的引用this。
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
//——————
做法是转化为静态内部了类
5、资源未关闭导致
比如数据库,文件,广播,Stream,Bitmap等