RxJava 2 / Retrofit 2 - NetworkOnMainThreadException
RxJava 2 / Retrofit 2 - NetworkOnMainThreadException
我需要执行请求,如果我的令牌已过期,我需要刷新它并重试请求。
这就是我正在尝试的方式,目前我可以刷新令牌,但它抛出一个 NetworkOnMainThreadException。它完成了请求,更新令牌并到达日志,但那个异常让我丧命。我怎样才能避免这种情况?
public Observable<Estabelecimento> listarEstabelecimentos() {
return Observable.defer(this::getListarEstabelecimentoObservable)
.retryWhen(throwableObservable -> throwableObservable.flatMap(
throwable -> {
if (throwable instanceof UnauthorizedException) {
return mRequestManager.getTokenObservable(AutoAtendimentoApplication.getContext())
.doOnNext(response -> /* log stuff */)
.flatMap((Function<AuthResponse, ObservableSource<?>>) response2 ->
getListarEstabelecimentoObservable()
.doOnNext(estabelecimento ->
/* log stuff */)
)
);
}
return Observable.error(throwable);
}));
}
NetWorkErrorHandler:
public <T> T processError(Response<T> response ) {
switch (response.code()) {
case 401:
throw new UnauthorizedException();
default:
return response.body();
}
}
令牌:
private Observable<AuthResponse> getToken(Context context,
@GrantType.GrantTypeDef String grantType, @Nullable String refreshToken) {
SessionManager sessionManager = SessionManager.getInstance(context);
Usuario usuario = sessionManager.getUser();
AuthRequest request = new AuthRequest(usuario.getUsername(),
usuario.getPassword(), grantType, refreshToken);
return mAuthAPIService.getToken(request)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.map(response -> storeTokens(response, context));
}
编辑 1:我很确定问题发生在 flatmap 内部,而 subscribeOn 并不能解决问题
编辑 2:代码更新
您的网络在主线程上 运行,您可以通过将 observeOn(AndroidSchedulers.mainThread())
和 subscribeOn(Schedulers.io())
添加到您的 observable 来解决它。
observeOn(...)
表示结果在指定线程上发出 - 在本例中为 UI 线程。
subscribeOn(...)
调用基本上是处理请求的地方。如果省略,则计算在当前线程上完成。
好吧,让我试着用通俗易懂的英语解释一下。当您使用 AndroidSchedulers.mainThread()
时,它会将当前块包装在主线程中,这与您的 activity 中的任何方法都没有区别。主要上下文不允许执行网络请求,因此您的请求失败了。为了解决这个问题,您的可观察流程需要更改以适应执行。
为了检验这一理论,将所有 AndroidSchedulers.mainThread()
更改为 Schedulers.io()
,因此您可以注释掉对 ui 处理的所有调用,例如 toast 消息并留下简单的日志消息, 试一试,看看会发生什么。
你需要重构这个部分,因为它在主线程上调用。
if (throwable instanceof UnauthorizedException) {
return mRequestManager.getTokenObservable(AutoAtendimentoApplication.getContext()).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io())
.doOnNext(response -> Log.i("NEXT", "OK")).doOnError(throwable1 -> Log.i("ONERROR", "NOT OK"));
这是一个理论上的解决方案,不要将 getToken 包装在一个 observable 中,创建一个 class 可以独立调用 getToken 的地方,让它 return 一个您可以订阅的 observable并从中观察。真的就是这样,目前您将上面的内容包装在主要 thread.I 中,不知道还有什么方法可以使它更简单
}
问题一定出在你的getToken上。您不需要 observeOn 和 subscribeOn:
return mAuthAPIService.getToken(request)
.map(response -> storeTokens(response, context));
添加这些行会产生错误。我确实认为你需要在最终订阅者中指定它的唯一地方。
我需要执行请求,如果我的令牌已过期,我需要刷新它并重试请求。
这就是我正在尝试的方式,目前我可以刷新令牌,但它抛出一个 NetworkOnMainThreadException。它完成了请求,更新令牌并到达日志,但那个异常让我丧命。我怎样才能避免这种情况?
public Observable<Estabelecimento> listarEstabelecimentos() {
return Observable.defer(this::getListarEstabelecimentoObservable)
.retryWhen(throwableObservable -> throwableObservable.flatMap(
throwable -> {
if (throwable instanceof UnauthorizedException) {
return mRequestManager.getTokenObservable(AutoAtendimentoApplication.getContext())
.doOnNext(response -> /* log stuff */)
.flatMap((Function<AuthResponse, ObservableSource<?>>) response2 ->
getListarEstabelecimentoObservable()
.doOnNext(estabelecimento ->
/* log stuff */)
)
);
}
return Observable.error(throwable);
}));
}
NetWorkErrorHandler:
public <T> T processError(Response<T> response ) {
switch (response.code()) {
case 401:
throw new UnauthorizedException();
default:
return response.body();
}
}
令牌:
private Observable<AuthResponse> getToken(Context context,
@GrantType.GrantTypeDef String grantType, @Nullable String refreshToken) {
SessionManager sessionManager = SessionManager.getInstance(context);
Usuario usuario = sessionManager.getUser();
AuthRequest request = new AuthRequest(usuario.getUsername(),
usuario.getPassword(), grantType, refreshToken);
return mAuthAPIService.getToken(request)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.map(response -> storeTokens(response, context));
}
编辑 1:我很确定问题发生在 flatmap 内部,而 subscribeOn 并不能解决问题
编辑 2:代码更新
您的网络在主线程上 运行,您可以通过将 observeOn(AndroidSchedulers.mainThread())
和 subscribeOn(Schedulers.io())
添加到您的 observable 来解决它。
observeOn(...)
表示结果在指定线程上发出 - 在本例中为 UI 线程。
subscribeOn(...)
调用基本上是处理请求的地方。如果省略,则计算在当前线程上完成。
好吧,让我试着用通俗易懂的英语解释一下。当您使用 AndroidSchedulers.mainThread()
时,它会将当前块包装在主线程中,这与您的 activity 中的任何方法都没有区别。主要上下文不允许执行网络请求,因此您的请求失败了。为了解决这个问题,您的可观察流程需要更改以适应执行。
为了检验这一理论,将所有 AndroidSchedulers.mainThread()
更改为 Schedulers.io()
,因此您可以注释掉对 ui 处理的所有调用,例如 toast 消息并留下简单的日志消息, 试一试,看看会发生什么。
你需要重构这个部分,因为它在主线程上调用。
if (throwable instanceof UnauthorizedException) {
return mRequestManager.getTokenObservable(AutoAtendimentoApplication.getContext()).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io())
.doOnNext(response -> Log.i("NEXT", "OK")).doOnError(throwable1 -> Log.i("ONERROR", "NOT OK"));
这是一个理论上的解决方案,不要将 getToken 包装在一个 observable 中,创建一个 class 可以独立调用 getToken 的地方,让它 return 一个您可以订阅的 observable并从中观察。真的就是这样,目前您将上面的内容包装在主要 thread.I 中,不知道还有什么方法可以使它更简单 }
问题一定出在你的getToken上。您不需要 observeOn 和 subscribeOn:
return mAuthAPIService.getToken(request)
.map(response -> storeTokens(response, context));
添加这些行会产生错误。我确实认为你需要在最终订阅者中指定它的唯一地方。