RxJava Single - 内存泄漏,如何正确取消订阅?
RxJava Single - Getting a memory leak, how to correctly unsubscribe?
我正在使用 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
谷歌搜索 Whosebug,得到的答案是您不需要取消订阅 Single
.
但如果我不这样做,就会出现内存泄漏。
我尝试通过在我的 ViewModel 中将 Single 调用设为实例变量来取消订阅:
Disposable mDisposable;
mDisposable = Single.fromCallable(() -> {...}).subscribe(txHash -> {..});
并在 Fragment 的 onDestroy()
方法中取消订阅,因此如果用户在调用结束前退出屏幕,它将取消订阅。
@Override
public void onDestroy() {
super.onDestroy();
mViewModel.getDisposable().dispose();
}
但它还在漏水。我不确定这是否是取消订阅的正确方法,或者我可能做错了其他事情。
如何正确取消订阅单个可调用?
编辑:_________________________________
我是如何重现问题的:
屏幕一是启动屏幕二。在创建屏幕二时立即执行调用。由于我在没有网络的情况下对其进行测试,因此查询将继续在屏幕二上执行,直到超时。所以在通话结束前两次关闭屏幕会导致泄漏。
它似乎不是上下文泄漏,因为我已经通过删除 .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
在 Java 中容易犯的一个错误是在 non-static 上下文中实例化匿名 class 时忘记对外部 class 实例的隐式引用.
例如:
Single.fromCallable(() -> {
// some logic
return 5;
});
实际上等同于:
Single.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// some logic
return 5;
}
});
因此,您所做的是实例化一个实现 Callable
接口的匿名 class 的新实例。
现在,让我们把它放在一些上下文中。
假设我们在服务中有这个 class:
class SomeService {
int aNumber = 5;
Single<Integer> doLogic() {
return Single.fromCallable(() -> {
// using "aNumber" here implicates Service.this.aNumber
// e.g. writing "return 5 + aNumber" is actually the same as
return 5 + SomeService.this.aNumber;
});
}
}
大多数时候这不是问题,因为外部 class 比方法内部实例化的 short-lived 对象寿命更长。但是,如果您的实例化对象可以比外部对象长寿(在您的情况下,即使在 ViewModel 超出范围后 Single 仍然 运行 ),整个外部对象(在您的情况下,ViewModel)仍保留在内存中Callable
仍然有对它的隐式引用。
有很多方法可以摆脱这种不需要的引用 - 最简单的方法是在静态上下文中实例化对象,您只捕获真正需要的内容(而不是整个“外部 this”)。
class SomeService {
int aNumber = 5;
static Callable staticCallableThatCapturesOnlyParameters(int param) {
return () -> {
// outer this is not available here
return 5 + param; // param is captured through the function args
};
}
Single<Integer> doLogic() {
return Single.fromCallable(staticCallableThatCapturesOnlyParameters(aNumber));
}
}
另一种方法是完全避免使用匿名对象并使用静态内部 classes,但这很快会使代码膨胀。
我正在使用 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
谷歌搜索 Whosebug,得到的答案是您不需要取消订阅 Single
.
但如果我不这样做,就会出现内存泄漏。
我尝试通过在我的 ViewModel 中将 Single 调用设为实例变量来取消订阅:
Disposable mDisposable;
mDisposable = Single.fromCallable(() -> {...}).subscribe(txHash -> {..});
并在 Fragment 的 onDestroy()
方法中取消订阅,因此如果用户在调用结束前退出屏幕,它将取消订阅。
@Override
public void onDestroy() {
super.onDestroy();
mViewModel.getDisposable().dispose();
}
但它还在漏水。我不确定这是否是取消订阅的正确方法,或者我可能做错了其他事情。
如何正确取消订阅单个可调用?
编辑:_________________________________
我是如何重现问题的:
屏幕一是启动屏幕二。在创建屏幕二时立即执行调用。由于我在没有网络的情况下对其进行测试,因此查询将继续在屏幕二上执行,直到超时。所以在通话结束前两次关闭屏幕会导致泄漏。
它似乎不是上下文泄漏,因为我已经通过删除 .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
在 Java 中容易犯的一个错误是在 non-static 上下文中实例化匿名 class 时忘记对外部 class 实例的隐式引用.
例如:
Single.fromCallable(() -> {
// some logic
return 5;
});
实际上等同于:
Single.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// some logic
return 5;
}
});
因此,您所做的是实例化一个实现 Callable
接口的匿名 class 的新实例。
现在,让我们把它放在一些上下文中。
假设我们在服务中有这个 class:
class SomeService {
int aNumber = 5;
Single<Integer> doLogic() {
return Single.fromCallable(() -> {
// using "aNumber" here implicates Service.this.aNumber
// e.g. writing "return 5 + aNumber" is actually the same as
return 5 + SomeService.this.aNumber;
});
}
}
大多数时候这不是问题,因为外部 class 比方法内部实例化的 short-lived 对象寿命更长。但是,如果您的实例化对象可以比外部对象长寿(在您的情况下,即使在 ViewModel 超出范围后 Single 仍然 运行 ),整个外部对象(在您的情况下,ViewModel)仍保留在内存中Callable
仍然有对它的隐式引用。
有很多方法可以摆脱这种不需要的引用 - 最简单的方法是在静态上下文中实例化对象,您只捕获真正需要的内容(而不是整个“外部 this”)。
class SomeService {
int aNumber = 5;
static Callable staticCallableThatCapturesOnlyParameters(int param) {
return () -> {
// outer this is not available here
return 5 + param; // param is captured through the function args
};
}
Single<Integer> doLogic() {
return Single.fromCallable(staticCallableThatCapturesOnlyParameters(aNumber));
}
}
另一种方法是完全避免使用匿名对象并使用静态内部 classes,但这很快会使代码膨胀。