rx android 内存泄露,从一个RxJava使用实例中探究Android内存泄露的产生

本文探讨了一次项目中通过LeakCanary检测到的内存泄露案例,源于集成的自动更新SDK中单例类的生命周期管理不当。分析了如何通过RxJava的内部类持有外部引用导致MainActivity泄露,以及如何通过正确管理对象生命周期来避免此类问题。
摘要由CSDN通过智能技术生成

内存泄露的根本原因

当一个对象处于可以被回收状态时,却因为该对象被其他暂时不可被回收的对象持有引用,而导致不能被回收,如此一来,该对象所占用的内存被回收以作他用,这部分内存就算是被泄露掉了。简单来说,就是该丢掉的垃圾还占着有用的空间没有被及时丢掉

内存泄露示例

最近在项目中遇到一个有点“隐蔽”的内存泄露,是通过集成的内存泄露检测工具 LeakCanary 检测出来的。我们的项目中使用了第三方自动更新的sdk,并且写了AutoUpdateManager这个单例的类来统一管理自动更新相关功能。自动更新方法的代码如下:

public Observable checkUpdate(final Context context, final boolean bForce) {

return NetManager.getInstance().getCheckVersionUpdateObservable()

.observeOn(Schedulers.newThread())

.flatMap(new Func1>() {

@Override

public Observable call(CheckVersionUpdateRsp rsp) {

if (rsp.isNeedUpdate()) {

return getUpdateVersionInfo(context, bForce);

} else {

// 云校园后台禁止检查更新

return Observable.empty();

}

}

}).observeOn(AndroidSchedulers.mainThread());

}

private Observable getUpdateVersionInfo(final Context context, final boolean bForce) {

return Observable.create(new Observable.OnSubscribe() {

@Override

public void call(final Subscriber super UpdateVersionInfo> subscriber) {

IFlytekUpdateListener updateListener = new IFlytekUpdateListener() {

@Override

public void onResult(int updateStatus, UpdateInfo updateInfo) {

if (hasUpdate && bForce) {

// 当前缓存有更新信息且无需考虑是否wifi,则直接将缓存的更新信息返回

subscriber.onNext(updateVersionInfo);

subscriber.onCompleted();

return;

}

if (updateStatus == UpdateErrorCode.OK && updateInfo != null && !(updateInfo.getUpdateType() == UpdateType.NoNeed)) {

//弹出更新dialog

hasUpdate = true;

AutoUpdateManager.this.updateInfo = updateInfo;

AutoUpdateManager.this.updateVersionInfo = new UpdateVersionInfo(updateInfo.getUpdateDetail(), updateInfo.getUpdateVersion());

subscriber.onNext(updateVersionInfo);

subscriber.onCompleted();

} else {

Timber.d("获取更新信息失败或者当前无更新的版本");

hasUpdate = false;

subscriber.onNext(null);

subscriber.onCompleted();

}

}

};

if (bForce) {

// forceUpdate指不区分wifi和移动网络均返回更新信息, 当前只使用这种方式

updateAgent.forceUpdate(context, updateListener);

} else {

updateAgent.autoUpdate(context, updateListener);

}

}

});

}

上面代码中的checkUpdate方法在我们的MainActivity中被如下调用:

AutoUpdateManager.getInstance().checkUpdate(MainApplication.getContext(), true)

.observeOn(AndroidSchedulers.mainThread())

.compose(this.bindToLifecycle())

.subscribe(new Action1() {

@Override

public void call(AutoUpdateManager.UpdateVersionInfo updateVersionInfo) {

showUpdateDialog(updateVersionInfo);

}

}, new Action1() {

@Override

public void call(Throwable throwable) {

}

});

代码看起来比较多,另外因为使用了RxJava来写网络回调,并且有所嵌套,所以对象之间的引用不能一下子理清楚。

分析之前,我们需要理解一句话:(非静态)内部类(包括匿名内部类)天然持有自己外部类的(隐式)引用。这句话其实很容易理解,但很多时候会被忽略。在java基础中,我们肯定学过如果A类中有一个内部类C,那么C的对象可以通过new A.C()来获取,所以C天然持有A的引用。只不过我们有时通过自动导入包名后,就不用在前面加上“A.”了。

LeakCanary工具检测到MainActivity有泄露,而且打印出了堆栈信息,分析发现从updateAgent.forceUpdate(context, updateListener)方法进入sdk里面的代码,层层追踪,发现最终传进去的updateListener参数被赋值给一个c类中的c变量,而这个c变量持有MainActivity的引用导致MainActivity的泄露。

public class c implements b {

private HashMap a = new HashMap();

private com.iflytek.autoupdate.c.c.c b;

private IFlytekUpdateListener c;

private com.iflytek.autoupdate.a.a d;

private static c e = null;

public static c a(IFlytekUpdateListener var0, com.iflytek.autoupdate.a.a var1) {

if(e == null) {

e = new c(var0, var1);

}

return e;

}

……

// updateListener最终传入了这个方法

public void a(IFlytekUpdateListener var1) {

this.c = var1;

}

}

而很明显,c类是一个单例。

单例的内存泄露是最普遍的,因为单例对象本身是静态的,它的生命周期是跟应用程序的生命周期一样长的,所以单例中的成员变量如果持有了外部对象的引用(诸如Activity之类需要被即时回收的对象)而没有被即使释放,则一定会产生内存泄露的。

那么问题来了,为什么c变量(即updateListener)会持有MainActivity的引用呢?我们回到updateListener被new出来的地方看:

IFlytekUpdateListener updateListener = new IFlytekUpdateListener() {

@Override

public void onResult(int updateStatus, UpdateInfo updateInfo) {

if (hasUpdate && bForce) {

// 当前缓存有更新信息且无需考虑是否wifi,则直接将缓存的更新信息返回

subscriber.onNext(updateVersionInfo);

subscriber.onCompleted();

return;

}

if (updateStatus == UpdateErrorCode.OK && updateInfo != null && !(updateInfo.getUpdateType() == UpdateType.NoNeed)) {

//弹出更新dialog

hasUpdate = true;

AutoUpdateManager.this.updateInfo = updateInfo;

AutoUpdateManager.this.updateVersionInfo = new UpdateVersionInfo(updateInfo.getUpdateDetail(), updateInfo.getUpdateVersion());

subscriber.onNext(updateVersionInfo);

subscriber.onCompleted();

} else {

Timber.d("获取更新信息失败或者当前无更新的版本");

hasUpdate = false;

subscriber.onNext(null);

subscriber.onCompleted();

}

}

};

我们发现在updateListener的回调方法中,它引用了subscriber,而这个subscriber是RxJava中的观察者(Subscriber本身继成于Rx中的Observer)。在MainActivity中我们调用checkUpdate方法,订阅了此方法返回的被观察者(Observable),通过subscribe方法传进去了一个观察者Subscriber,当然这里的写法没有直接new一个Subscriber,而是new了两个Action1对象,其实在subscribe方法中,通过这两个对象,创建出了Subscriber的一个子类:

/**

* A Subscriber that forwards the onXXX method calls to callbacks.

* @param the value type

*/

public final class ActionSubscriber extends Subscriber {

final Action1 super T> onNext;

final Action1 onError;

final Action0 onCompleted;

public ActionSubscriber(Action1 super T> onNext, Action1 onError, Action0 onCompleted) {

this.onNext = onNext;

this.onError = onError;

this.onCompleted = onCompleted;

}

@Override

public void onNext(T t) {

onNext.call(t);

}

@Override

public void onError(Throwable e) {

onError.call(e);

}

@Override

public void onCompleted() {

onCompleted.call();

}

}

而这个ActionSubscriber也最终被传入Observable.OnSubscribe类的回调call方法中,即上面提到的updateListener对象的回调方法里所引用的subscriber对象。最终通过层层传递,MainActivity的引用被sdk的内部单例c类所持有了。而且sdk没有提供释放引用的方法,于是导致了MainActivity的内存泄露。

那么可能有人同我一样会产生疑问,在Activity中我们经常对各种view设置点击事件,View.OnClickListener()其实就是Activity的匿名内部类,它也持有了Activity的引用,而我们也从来没有在Activity销毁之前调用view.setOnClickListener(null)这样的代码。那为什么不会产生内存泄露呢?

view.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

// do something

}

});

那是因为Activity中的View都是附着在Activity自身的视图中的,当Activity销毁时,ActivityManager会自动销毁相关的视图,View也随之销毁,自然不会再持有Activity的引用了。

如何防止内存泄露

管理好对象的生命周期,及时释放掉无用的对象。比如我们在Activity启动时注册了一个BroadcastReceiver,但在Activity销毁时没有及时进行反注册,程序就会打印出错误的log提示。

E/ActivityThread: Activity holenzhou.com.aboutfragment.SecondActivity has leaked IntentReceiver holenzhou.com.aboutfragment.TestReceiver@7e54937 that was originally registered here. Are you missing a call to unregisterReceiver()?

还有我们最常用的Handler,有时编译器会提示我们Handler可能会造成内存泄露,建议我们将它声明为静态内部类,同时持有外部Activity的弱引用。

/**

* Instances of static inner classes do not hold an implicit

* reference to their outer class.

*/

private static class MyHandler extends Handler {

private final WeakReference mActivity;

public MyHandler(SampleActivity activity) {

mActivity = new WeakReference(activity);

}

@Override

public void handleMessage(Message msg) {

SampleActivity activity = mActivity.get();

if (activity != null) {

// ...

}

}

}

private final MyHandler mHandler = new MyHandler(this);

但其实有更简单的解决方法,就是在Activity的onStop方法中,通过下面的api将Handler相关的所有的Callbacks和Messages移除掉,只不过这种方式很容易被忘记。类似的还有Android的Timer类。

handler.removeCallbacksAndMessages(null);

有些我们自定义的对象需要我们自己手动释放相关引用。比如我们定义了一些单例对象后,持有了外部Activity的引用,就需要在适当的时候释放相关的引用。

还有我们经常见到的网络请求框架里面都会有一个cancel方法取消发送除去的网路请求,其实也是为了方便我们在Activity销毁前及时移除网络请求的异步回调,防止造成内存泄露。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值