我当前的Android应用程序正在使用Retrofit(2.4.0)
和RxJava(2.1.16)
来执行Web服务调用。
我正在使用Google登录进行用户身份验证。
我希望我的改造电话能够检测到HTTP 401(未经授权),并尝试使用Google登录静默登录 然后重试改造调用。
我的改装电话很像这样
@Headers(HEADER_ACCEPT_JSON)
@GET("resources")
Observable<Response<String>> getResources(@Header(HEADER_AUTHORIZATION) @NonNull final String authenticationToken, @QueryMap(encoded = true) @NonNull Map<String, Object> queryMap);
API_SERVICE.getResources(Login.getAuthorizationToken(), id)
.subscribeOn(Schedulers.io())
.subscribe(Network::manageResource, Network::handle));
从谷歌搜索中,我看到retry / retryWhen仅在我的RxJava链中发生错误时触发, 但是HTTP 401错误不会引发这种情况。
作为RxJava的新手,我如何检测我的HTTP 401代码和..
a)。执行Google登录静音登录
b)。静默登录完成,确定,重试我的API调用吗?
更新
我与以下代码更加接近
@Headers(HEADER_ACCEPT_JSON)
@GET("resources")
Single<Response<String>> getResources(@Header(HEADER_AUTHORIZATION) @NonNull final String authenticationToken, @QueryMap(encoded = true) @NonNull Map<String, Object> queryMap);
API_SERVICE.getResources(Login.getAuthorizationToken(), id)
.subscribeOn(Schedulers.io())
.flatMap(new Function<Response<Article>,
SingleSource<Response<Article>>>() {
@Override
public SingleSource<Response<Article>> apply(final Response<Article> response) {
Log.d(TAG, "apply() called with: response = [" + response + "]");
if (response.isSuccessful()) {
return Single.just(response);
} else {
return Single.error(new RuntimeException());
}
}
})
.retryWhen(errors -> errors.take(1).flatMap(new Function<Throwable, Publisher<?>>() {
@Override
public Publisher<?> apply(final Throwable throwable) {
Log.d(TAG, "apply() called with: throwable = [" + throwable + "]");
Login.loginSilently().subscribe();
return Flowable.just("DELAY").delay(10, TimeUnit.SECONDS);
}
}))
.subscribe(Network::manageResource, Network::handle));
我不喜欢Flowable.just(“ DELAY”)。delay()调用,即使我现在正在捕获异常并以静默方式登录也可以,但我得到了这个异常
09-10 16:39:29.878 7651-7718/research.android E/Network: handle:
java.util.NoSuchElementException
at io.reactivex.internal.operators.flowable.FlowableSingleSingle$SingleElementSubscriber.onComplete(FlowableSingleSingle.java:116)
at io.reactivex.subscribers.SerializedSubscriber.onComplete(SerializedSubscriber.java:168)
at io.reactivex.internal.operators.flowable.FlowableRepeatWhen$WhenReceiver.onComplete(FlowableRepeatWhen.java:119)
at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.drainLoop(FlowableFlatMap.java:426)
at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.drain(FlowableFlatMap.java:366)
at io.reactivex.internal.operators.flowable.FlowableFlatMap$InnerSubscriber.onComplete(FlowableFlatMap.java:673)
at io.reactivex.subscribers.SerializedSubscriber.onComplete(SerializedSubscriber.java:168)
at io.reactivex.internal.operators.flowable.FlowableDelay$DelaySubscriber$OnComplete.run(FlowableDelay.java:139)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
09-10 16:39:29.878 7651-7678/research.android D/OkHttp: <-- HTTP FAILED: java.io.IOException: Canceled
如何等待静默登录完成后重试?
和
什么导致NoSuchElementException?
答案 0 :(得分:2)
据我所记得,如果您的错误代码> 300,那么onError()
将被Throwable调用,它可以转换为HttpException
,从那里您可以获得服务器返回的错误代码,因此您可以调用其他功能以进行“无声呼叫”
答案 1 :(得分:0)
要解决401未经授权的错误,请尝试对您的OkHttpClient实施AuthInterceptor。
BasicAuthInterceptor interceptorAuth = new BasicAuthInterceptor(yourToken);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptorAuth)
.build();
builder.client(client);
如果您的authToken过期或不正确,请尝试获取新的令牌。
public class BasicAuthInterceptor implements Interceptor {
private String yourToken;
public BasicAuthInterceptor(String token) {
this.yourToken = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request authenticatedRequest = request.newBuilder()
.header("Authorization", format("token %s", yourToken)).build();
Response response = chain.proceed(authenticatedRequest);
boolean unauthorized = response.code() == 401;
if (unauthorized) {
Request modifiedRequest = request.newBuilder()
.header("Authorization", format("token %s", getNewToken())).build();
response = chain.proceed(modifiedRequest);
}
return response;
}
}
答案 2 :(得分:0)
初始化客户端时:
Retrofit.Builder()
.baseUrl(baseUrl)
.client(createClient())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(ApiHandler(Schedulers.io()))
.build()
错误处理程序:
class ApiHandler(scheduler: Scheduler) : CallAdapter.Factory() {
private val original: RxJava2CallAdapterFactory
= RxJava2CallAdapterFactory.createWithScheduler(scheduler)
override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>?
= original.get(returnType, annotations, retrofit)?.let { Wrapper(it) }
private class Wrapper<R>(private val wrapped: CallAdapter<R, *>) : CallAdapter<R, Any> {
override fun adapt(call: Call<R>?): Any? {
call ?: return null
val result = wrapped.adapt(call)
return when (result) {
is Maybe<*> -> result.onErrorResumeNext(Function { Maybe.error(wrap(it)) })
is Single<*> -> result.onErrorResumeNext { Single.error(wrap(it)) }
is Completable -> result.onErrorResumeNext { Completable.error(wrap(it)) }
is Flowable<*> -> result.onErrorResumeNext(Function { Flowable.error(wrap(it)) })
is Observable<*> -> result.onErrorResumeNext(Function { Observable.error(wrap(it)) })
else -> result
}
}
override fun responseType(): Type = wrapped.responseType()
private fun wrap(throwable: Throwable) = when (throwable) {
is HttpException -> {
val exception = ApiException.http(throwable)
toLog("ex - ${exception.message}")
exception
} // We had non-200 http error
is JsonSyntaxException -> ApiException.parse(throwable) // We had json parsing error
is SocketTimeoutException -> ApiException.timeout(throwable) // A network error happened
is IOException -> ApiException.network(throwable) // A network error happened
else -> ApiException.unknown(throwable) // We don't know what happened. We need to simply convert to an unknown error
}
}
}
Api异常类:
class ApiException internal constructor(message: String,
/** Response object containing status code, headers, body, etc. */
val response: ErrorResponse?,
/** The event kind which triggered this error. */
@ApiError val error: Int,
exception: Throwable?) : RuntimeException(message, exception) {
companion object {
fun http(exception: HttpException): ApiException {
val response = exception.response()
var errorResponse: ErrorResponse? = null
val message = if (response == null) {
if (exception.message().isEmpty()) exception.code().toString() else exception.message()
} else {
// here you can check error code and throw needed exception
val errorBody = response.errorBody()?.string().toString()
if (errorBody.isNotEmpty()) {
toLog("ApiException: $errorBody")
}
try {
errorResponse = GsonBuilder().create().fromJson(errorBody, ErrorResponse::class.java)
errorResponse?.toString() ?: errorBody
} catch (e: Exception) {
e.printStackTrace()
response.raw().message()
}
}
return ApiException(message, errorResponse, ApiError.HTTP, exception)
}
fun network(exception: IOException): ApiException {
return ApiException(exception.message ?: "network", null, ApiError.NETWORK, exception)
}
fun parse(exception: JsonSyntaxException): ApiException {
return ApiException(exception.message ?: "parse", null, ApiError.CONVERSION, exception)
}
fun unknown(exception: Throwable): ApiException {
return ApiException(exception.message ?: "unknown", null, ApiError.UNKNOWN, exception)
}
fun timeout(exception: SocketTimeoutException): ApiException {
return ApiException("Connection timed out", null, ApiError.TIMEOUT_EXCEPTION, exception)
}
}
}
调用请求时
yourRequest.compose { observable ->
observable.retryWhen { flow ->
flow.ofType(ApiException::class.java).flatMap {
when {
it.error == ApiError.TIMEOUT_EXCEPTION -> Flowable.empty<T>()
it.error == ApiError.NETWORK -> getSnackBarFlowable().flatMap { if (it) Flowable.just(it) else Flowable.empty<T>() }
else -> Flowable.error(it)
}
}
}
}.subscribe({}, {})
getSnackBarFlowable()来自片段。您可以使用其他东西
fun getSnackBarFlowable(): Flowable<Boolean> = Flowable.create({ subscriber ->
if (view == null) {
subscriber.onNext(false)
} else {
val snackBar = Snackbar.make(activity!!.currentFocus, R.string.error_connection_fail, Snackbar.LENGTH_INDEFINITE)
snackBar.setAction("Retry") { subscriber.onNext(true) }
snackBar.show()
}
}, LATEST)
我知道,足够的代码。但是这个解决方案在不同的项目中对我真的很有帮助