内存泄漏:常见原因及措施
原因
- 资源对象没关闭造成的内存泄漏
- 变量的作用域不一样导致
- 内存压力过大
常见解决方法思路
尽量使用Application的Context而不是Activity的
因为很多对象的生命周期不是和Activity同步的,而是与Application一致
使用弱引用和软引用
更多的使用是在内部类里面
- 手动设置null,解除引用关系
- 将内部类设置为static, 不隐式的持有外部的对象
- 注册与反注册成对出现,在对象适合的生命周期进行反注册操作
- 如果没有修改的权限,比如系统或第三方的SDK,可以使用反射来操作持有的关系
Handler 引起的内存泄漏
在一个类中(Activity), handler在进行异步的操作并处理返回结果经常被使用,
android 应用启动时:
启动时,会自动创建一个供应用主线程使用的
Looper
实例,Looper
的主要工作是一个一个的处理消息队列中的消息对象。
在android中所有Android框架事件(比如activity的生命周期调用和按钮点击等)都是放在
消息中,然后加入到Looper要处理的消息队列中,由Looper负责一条一条地进行处理。
主线程中的Looper生命周期和当前应用一样长。Handler在主线程进行初始化后
会发送一个target为这个Handler的消息到Looper处理的消息队列中,实际上已经发送的消息
包含一个Handler实例的引用,只有这样Looper在处理这条消息时才可以调用
Handler.handleMessage(message);在java中:
非静态的内部类和匿名内部类都会隐式的持有其外部类的引用。静态的内部类不会持有外部类的引引用。
在activity中,如果activity被finish掉,但是Handler未执行完毕,就会持有一个activity的对象,导致activity无法回收,而activity持有的资源也无法得到回收,,造成内存泄漏。
解决这个问题,避免使用非静态内部类,继承Handler时,要么放在单独的类文件下,要么就是使用静态内部类。同样也可以使用弱引用。
还有一个问题
当activity被finish后,handler对象没有必要继续处理消息了,所以需要对消息处理:
在Activity onStop() 或者onDestroy()的时候,取消掉该Handler
对象的Message
和Runnable
.
通过查看Handler的API,它有几个方法,: removeCallBack(Runnable r)
和removeMessage(int what)
等
@Override
public void onDestroy() {
mHandler.removeMessages(MESSAGE_1);
mHandler.removeMessages(MESSAGE_2);
mHandler.removeMessage(MESSAGE_3);
...
mHandler.removeCallbacks(mRunnable);
...
//如果觉着太麻烦,可这样操作
//mHandler.removeCallbackAndMessage(null);
}
Thread 引起的内存泄漏
很大部分的原因和Handler
是一样的,
解决办法:
- 将线程的内部类,改为静态内部类
在线程内部采用弱引用保存Context引用。
public class MainActivity extends Activity {
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new MyThread(this).start(); } private void doSomethings() { // } ... private static class MyThread extends Thread { WeakReference<MainActivity> mThread; public MyThread(MainActivity activity) { mThread = new WeakReference<MainActivity>(activity); } @Override public void run() { super.run(); if (mThread == null) { return; } if (mThread.get() != null) { mThread.get().doSomething(); } } }
}
上面其实是切换两个对象的双向强引用链接
- 静态内部类:切断Activity对于MyThread的强引用
- 弱引用:切断MyThread对于Activity的强引用
AsyncTask内部类会如何
事实上AsyncTask
的问题更加严重,Thread只有在run函数不结束才会出现内存泄漏,而AsyncTask
内部的实现机制是运用了ThreadPollExcutor
该类产生的Thread对象的生命周期是不确定的,是程序无法控制的,所有,更容易出现内存泄漏的问题。
如果让使用非静态内部类的时候, 要格外注意,如果其实例的持有对象的生命周期大于外部类对象,那么就可能导致内存泄漏。
Android App 资源内存泄漏
资源内存泄漏主要是资源申请未释放,资源没有重复使用
- 申请资源后能保证及时释放资源
- 利用复用机制,如池的概念
1. 引用资源没有释放
private final class SettingObserver implements Observer {
public void update(Observable 0, Object arg) {
//todo
}
}
...
ContentQueryMap.getInstance().addObserver(new SettingObserver());
这段代码存在问题,存在的问题是没有办法注销观察者(SettingObserver),
这个对象将伴随整个单例生命周期,无形中就泄漏一个SettingsObserver
的内存。
1.1 注册未取消造成的内存泄漏
由于四大组件的生命周期不一致引起的,
例如:BroadcastReceiver
对象的注册,Observer
对象,
被观察对象生命周期和观察者的生命周期不一致,不观察的时候需要注销。
1.2 集合中对象没清理造成的内存泄露
把一些对象的引用加入到集合中,当不需要的时候,如果没有把它的引用清除掉,
会使这个集合越来越大。如果是static的话,那情况更严重了。
在add()
后,要记得remove()
.
2. 资源对象没关闭造成的内存泄漏
资源性对象,比如(Cusor File
文件等)往往都用了一些缓冲,不使用的时候,需要及时关闭。
它们的缓冲不仅存在与java虚拟机内,还存在与java虚拟机外.
如果把它们的引用设置为Null,而不关闭它们,往往会造成内存泄漏。
因为有些资源性对象,比如SQLiteCursor,如果没关闭,系统回收时会关闭,但效率太低。
因此对于资源性对象在不使用的时候,应该调用它的
close()`函数。将其关闭掉,然后才置为null.
//不合理的写法
try {
Cursor c = queryCursor();
int a = c.getInt(1);
...
//如果执行出错, 后面的cursor.close()将不会执行
...
c.close();
} catch (Exception e) {
...
}
这样的写法存在问题,合理的写法为:
Cursor c;
try {
c = queryCursor();
int a = c.getInt();
...
} catch (Exception e) {
} finally {
if (c != null) {
c.close();
}
}
3. 一些不良代码造成的内存压力
有些代码并不造成内存泄露,但是对没使用的内存没进行有效及时的释放,
或是没有有效的利用已有的对象而是频繁的申请新内存,造成了不必要的内存开支。
常见优化方式
- 频繁的申请内存对象和释放对象,可以考虑pool池
- 多线程可以考虑线程池
- 频繁的申请对象释放对象。可以考虑对象池,
- 频繁的链接资源和释放资源,可以考虑链接资源池
Bitmap 没调用recycle()
Bitmap对象不使用时,先调用recycle()释放内存,然后才设置为null.
chenzhao@hustunique.com