I'm using RxJava's Single.fromCallable()
to wrap around a third party library that makes an API call. I was testing different states on the call, success, failed, low network, no network.
But on the no network test I ran into a memory leak for the first time ever using RxJava. I spent the last hour combing through the code and trying to narrow down the leak with the LeakCanary
library.
I figured out it was coming from subscribing to the 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);
});
Once I remove the
.subscribe(txHash -> { ... });
It no longer leaks.
I've tried googling stackoverflow with RxJava Single unsubscribe
and I'm getting answers saying that you don't need to unsubscribe from Single
https://stackoverflow.com/a/43332198/11110509.
But if I don't I'll be getting memory leaks.
I've tried to unsubscribe by making the Single call an instance variable in my ViewModel:
Disposable mDisposable;
mDisposable = Single.fromCallable(() -> {...}).subscribe(txHash -> {..});
and unsubscribing it in the Fragment's onDestroy()
method so it will unsubscribe if the user exits the screen before the call is finished.
@Override
public void onDestroy() {
super.onDestroy();
mViewModel.getDisposable().dispose();
}
But it's still leaking. I'm not sure if this is the correct way to unsubscribe or maybe I'm doing something else incorrectly.
How can I correctly unsubscribe from the Single Callable?
Edit:_________________________________
How I'm recreating the issue:
Screen one is launches screen two. The call is performed instantly on the creation of screen two. Since I'm testing it with no network, the query is continuing to perform on screen two until it times out. So closing the screen two before the call finishes causes the leak.
It doesn't seem to be a context leak because I've removed tried testing it by removing all the methods inside the .subscribe()
:
Single.fromCallable(() -> {
return remoteRepository.makeTransaction(signedTransaction).execute();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(txHash-> {
//Removed all methods here.
//Still leaks.
}, err -> {
});
But when I remove:
.subscribe(txHash-> {
}, err -> {
});
it no longer leaks.
LeakCanary logs:
┬───
│ 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