【发布时间】:2022-01-27 11:17:10
【问题描述】:
我正在使用 RxJava 的 Single.fromCallable() 来包装进行 API 调用的第三方库。我在通话中测试了不同的状态,成功、失败、网络低、无网络。
但是在无网络测试中,我第一次使用 RxJava 遇到了内存泄漏。我花了最后一个小时梳理代码并尝试使用 LeakCanary 库缩小泄漏范围。
我发现它来自订阅Single.fromCallable()。
Single.fromCallable(() -> {
return remoteRepository.makeTransaction(signedTransaction).execute();
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(txHash -> {
Log.d(TAG, "makeTransaction: " + txHash);
}, err -> {
Log.e(TAG, "makeTransaction: ", err);
});
一旦我删除了
.subscribe(txHash -> { ... });
它不再泄漏。
我尝试使用 RxJava Single unsubscribe 搜索 stackoverflow,我得到的答案是您不需要取消订阅 Single
https://stackoverflow.com/a/43332198/11110509.
但如果我不这样做,我会遇到内存泄漏。
我尝试通过在我的 ViewModel 中将 Single 调用作为实例变量来取消订阅:
Disposable mDisposable;
mDisposable = Single.fromCallable(() -> {...}).subscribe(txHash -> {..});
并在 Fragment 的 onDestroy() 方法中取消订阅,这样如果用户在调用完成之前退出屏幕,它将取消订阅。
@Override
public void onDestroy() {
super.onDestroy();
mViewModel.getDisposable().dispose();
}
但它仍然在泄漏。我不确定这是否是正确的退订方式,或者我做错了什么。
如何正确退订 Single Callable?
编辑:___________________
我是如何重现这个问题的:
屏幕一是启动屏幕二。在创建屏幕二时立即执行调用。由于我在没有网络的情况下对其进行测试,因此查询将继续在屏幕二上执行,直到超时。所以在通话结束前关闭屏幕两个会导致泄漏。
这似乎不是上下文泄漏,因为我已经删除了尝试通过删除 .subscribe() 中的所有方法来测试它:
Single.fromCallable(() -> {
return remoteRepository.makeTransaction(signedTransaction).execute();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(txHash-> {
//Removed all methods here.
//Still leaks.
}, err -> {
});
但是当我删除时:
.subscribe(txHash-> {
}, err -> {
});
它不再泄漏。
LeakCanary 日志:
┬───
│ GC Root: Java local variable
│
├─ java.lang.Thread thread
│ Leaking: UNKNOWN
│ Retaining 2.4 kB in 81 objects
│ Thread name: 'RxCachedThreadScheduler-2'
│ ↓ Thread.<Java Local>
│ ~~~~~~~~~~~~
╰→ com.dave.testapp.ui.send.SendViewModel instance
Leaking: YES (ObjectWatcher was watching this because com.dave.testapp.ui.send.SendViewModel received
ViewModel#onCleared() callback)
Retaining 588 B in 19 objects
key = 6828ea76-a75c-448b-8278-d0e0bb0229c8
watchDurationMillis = 10324
retainedDurationMillis = 5321
baseApplication instance of com.dave.testapp.BaseApplication
METADATA
Build.VERSION.SDK_INT: 27
Build.MANUFACTURER: Google
LeakCanary version: 2.7
App process name: com.dave.testapp
【问题讨论】:
-
也许在 RxJava 代码中调试有助于理解它?
-
在 RxJava 代码中调试是什么意思?我已经尝试了几个小时,并缩小了问题的范围,但我不知道如何阻止泄漏
-
可能是因为您在每次运行时都创建了新线程
.subscribeOn(Schedulers.newThread())?考虑使用.subscribeOn(Schedulers.io()) -
对我来说,这看起来更像是泄露了对调用
Single.fromCallable的外部类的引用。请记住,lambda 通过com.dave.testapp.ui.send.SendViewModel.this隐式引用了外部类。您可以(仅用于调试)尝试将 lambda 替换为返回 lambda 的静态函数,并改为使用该函数吗? -
看我的回答。这其实是一个关于java内存泄漏的常见面试题。
标签: java android rx-java rx-java2 leakcanary