Android内存泄露

简单来说,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用,导致GC不能回收

虚拟机内存分配

简单来说分为堆和栈,具体分类如下

一般来说,都是堆上分配的内存出现泄露,导致GC无法回收。

引用的类型

开发时,为了防止内存溢出,处理一些比较占用内存并且生命周期长的对象时,可以尽量使用软引用和弱引用。

检测内存泄露的方法

常见内存泄露

内存泄露主要原因是生命周期不一致、资源没有释放。

单例

通常来说,单例的生命周期和应用一样长,所以如果使用不恰当的话,很容易造成内存泄漏。

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;
    }
}

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重。
如果是Activity的Context,就有可能造成内存泄露。正确的做法是不传或者使用Application的Context。

静态变量

如果成员变量被声明为static,那我们都知道其生命周期将与整个app进程生命周期一样,不再使用时,应该将静态变量置空。
特别要注意静态对象、全局性集合等的生命周期,尽可能的少使用静态变量。

Handler

生命周期与Activity是不一致的,导致内存泄露,同理还包括Thread、AsyncTask。

匿名内部类

使用了匿名类,并被异步线程持有了,那要小心了,如果没有任何措施这样一定会导致泄露。例如AsyncTask,Runnable,Rxjava的异步操作。

new AsyncTask<Void, Void, Void>() {
         @Override protected Void doInBackground(Void... params) {
             // TODO 耗时任务
         }
     }.execute();

资源使用完未关闭

使用以下资源时,需要特别注意:

案例分析

MVP模式Presenter内存泄露

相关代码如下

public class SortPresenter {

    private CompositeSubscription mCompositeSubscription;
    private ViewDelegate mViewDelegate;

    private SortPresenter(ViewDelegate viewDelegate) {
        this.mViewDelegate = viewDelegate;
    }

    public void getData() {
        Observable observable1 = Observable.just(1).map(new Func1() {
            @Override
            public Object call(Object o) {
                // TODO 耗时操作
                return 0;
            }
        });

        Observable observable2 = Observable.just(2).map(new Func1() {
            @Override
            public Object call(Object o) {
                // TODO 耗时操作
                return o;
            }
        });

        Subscription subscription = Observable.zip(observable1, observable2, new Func2() {
                    @Override
                    public Object call(Object o, Object o2) {
                        // TODO 数据处理
                        return o;
                    }
                }).subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Object>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        mViewDelegate.showError(e);
                    }

                    @Override
                    public void onNext(Object o) {
                        mViewDelegate.upateUI(o);

                    }
                });
        addSubscription(subscription);
    }

    protected void addSubscription(Subscription s) {
        if(s == null){
            return;
        }
        if (mCompositeSubscription == null) {
            mCompositeSubscription = new CompositeSubscription();
        }
        mCompositeSubscription.add(s);
    }

    public void unSubscribe() {
        mCompositeSubscription.clear();
    }
}

泄露原因:RxJava的Subscription取消订阅后,除了Subscriber订阅方法会不执行外,io线程会继续执行。匿名内部类持有外部Presenter类的引用,而Presenter持有mViewDelegate的引用,导致页面的实例被引用。

解决办法:在unSubscribe方法中,添加mViewDelegate = null,注意mViewDelegate判空处理。

EventBus内存泄露

泄露原因:注册EventBus后,没有反注册

解决办法:

集合类泄露

解决办法:注意反注册

案例待续

TODO

参考文献