概念
- 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间。
对象被更长生命周期的对象持有引用,导致无法被GC回收。【无用对象无法被回收】 - 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用
影响
- 无用对象无法及时释放,占用内存
- 严重时造成内存溢出
常见情况分析
Android多发生于Activity、Fragment等对象在生命周期结束后不能及时释放
1. 单例模式
常见于单例模式的工具类需要持有Context对象,此情况下如果直接传Activity对象来进行初始化,会造成单例一直持有此Activity引用,无法释放。
public class Singleton {
private static Singleton sInstance;
private Context mContext;
private Singleton (Context context){
this.mContext = context;
}
public static Singleton newInstance(Context context){
if(sInstance == null){
sInstance = new Singleton (context);
}
return sInstance;
}
}
解决方案:
- 如果单例模式必须持有context对象,需要使用与应用生命周期一样的Application对象
- 对于工具类必须使用到activity等短生命周期对象的情况,可以选择仅在操作方法中作为参数传入,这样在方法执行完成后会可以正常释放
public class Singleton {
private static Singleton sInstance;
......
public void func(Activity activity){
//do something
}
}
2. 非静态内部类
由于非静态内部类、匿名内部类等会持有外部类的引用,尤其需要注意使用Thread、Runnable、AsyncTask等新开线程的类时,常常习惯于直接使用匿名内部类,很容易造成外部类内存泄漏。
class A{
private mB = new B();
//内部类B持有外部类A的引用
class B{
//....
}
}
解决方案:
使用静态内部类或者外部类
3. Handler
Handler 造成内存泄漏的原因也是持有内部类持有外部类的引用。
public class TestActivity extends Activity {
int mCount =0;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//收到消息计数+1
super.handleMessage(msg);
mCount++;
}
};
//使用Handler 发送消息
public void send(){
Message message = Message.obtain();
mHandler.sendMessageDelayed(message, 1000);
}
}
mHandler是匿名内部类而持有外部TestActivity引用。
解决方案:
如果消息是延时发送(sendMessageDelay),Handler 里面的消息还没发送完毕的话,Activity 的内存也不会被回收,需要移除消息。
改用静态内部类或外部类。同时因为使用Handler一般在收到消息后需要与Activity交互,此时可以使用Activity的弱引用。
public class TestActivity extends Activity {
private Handler mHandler = new MyHandler(this);
//移除消息
@Override
onDestroy() {
mHandler.removeCallbacksAndMessages(null)
}
//静态内部类
static class MyHandler extends Handler {
//弱引用持有Activity
private WeakReference mReference;
public MyHandler(Activity activity) {
mReference = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mWeakReference.get();
}
}
}
4. 集合持有子元素
集合会持有子元素引用,如果子元素属于无用对象,应该及时从集合清除
5. 资源没有及时关闭
在使用完File,Cursor,Stream,Bitmap等资源时,要及时关闭释放内存。
6. Rxjava内存泄漏
通常使用RxJava做网络加载时线程调度,很可能由于子线程未执行 完毕导致内存泄漏。
解决方案:
- 使用CompositeDisposable管理
class Presenter {
private CompositeDisposable mDispose;
//订阅事件天机到CompositeDisposable
private void addDispose(Disposable disposable) {
if(mDispose== null){
mDispose =new CompositeDisposable();
}
mDispose.add(disposable);
}
//生命周期结束前调用dispose方法
@Override
public void unbind(){
super.unbind();
mDispose.dispose();
mDispose = null;
}
//测试
public void funcTest() {
addDispose(Observable.just(1)
.subscribeWith(observer..));
}
}
7. MVP模式内存泄漏
由于P层V层相互持有引用,因此在V层(通常是Activity、Fragment等)生命周期结束前(如Activity.onDestroy时)需要解除绑定
总结
内存泄漏重点是无用的对象被生命周期较长的对象引用导致GC时无法释放。
解决的办法主要是从
-
- 短生命周期周期对象尽量避免持有长生命周期对象引用
-
- 必须持有的,及时移除对长生命周期对象的引用
-
- 利用弱引用的特性
-
- 及时关闭Cursor、File等资源