Java 改装:如果响应代码为401,则重定向到LoginActivity

Java 改装:如果响应代码为401,则重定向到LoginActivity,java,android,retrofit,retrofit2,okhttp,Java,Android,Retrofit,Retrofit2,Okhttp,如何从拦截器(非活动类)启动LoginActivity?我尝试了下面的代码(拦截器),但不适用于我 拦截器 OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { R

如何从拦截器(非活动类)启动
LoginActivity
?我尝试了下面的代码(
拦截器
),但不适用于我

拦截器

OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {

                Request newRequest = chain.request().newBuilder()
                        .addHeader("Authorization", "Bearer " + auth_token_string)
                        .build();

                Response response =  chain.proceed(newRequest);
                Log.d("MyApp", "Code : "+response.code());
                if (response.code() == 401){
                    Intent intent = new Intent(SplashActivity.getContextOfApplication(), LoginActivity.class);
                    startActivity(intent);
                    finish();  //Not working
                    return response;
                }

                return chain.proceed(newRequest);
            }
        }).build();
OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new UnauthorizedInterceptor())
        .build();
class HeaderInterceptor(private val token: String?) : Interceptor {

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val newRequest =  request.newBuilder()

        Log.d(TAG, "token: $token")

        if (token != null && token.isNotBlank()) {
            newRequest.addHeader("Authorization", "Bearer $token")
        }

        return chain.proceed(newRequest.build())
    }

    companion object {
        private val TAG = HeaderInterceptor::class.java.toString()
    }

} 
这是我目前使用的解决方案,还有比这更好的解决方案吗?此解决方案必须在每次api调用时重复执行

主要活动

call.enqueue(new Callback<Token>() {
            @Override
            public void onResponse(Call<Token> call, Response<Token> response) {
                if(response.isSuccessful())
                {
                    //success
                }
                else
                {
                    Intent intent = new Intent(MainActivity.this.getApplicationContext(), LoginActivity.class);
                    startActivity(intent);
                    finish();
                }
            }
            @Override
            public void onFailure(Call<Token> call, Throwable t) {

            }
        });
call.enqueue(新回调(){
@凌驾
公共void onResponse(调用、响应){
if(response.issusccessful())
{
//成功
}
其他的
{
Intent Intent=new Intent(MainActivity.this.getApplicationContext(),LoginActivity.class);
星触觉(意向);
完成();
}
}
@凌驾
失败时公共无效(调用调用,可丢弃的t){
}
});
使用OkHttpClient.Builder的autheticator()方法

当响应为401时,将自动重试未授权的请求

OkHttpClient client = new OkHttpClient.Builder().authenticator(new Authenticator() {
    @Override
    public Request authenticate(Route route, Response response) throws IOException {
    // Handle 401 error code 
    if (response.code() == 401) {

            Intent intent = new Intent(MainActivity.this.getApplicationContext(), LoginActivity.class);
            startActivity(intent);
            finish();
            return null;
        }

        return null;
        }
}

考虑引入
2.Callback
接口的自定义实现,例如
BaseCallback

public abstract class BaseCallback<T> implements Callback<T> {

    private final Context context;

    public BaseCallback(Context context) {
        this.context = context;
    }

    @Override
    public void onResponse(Call<T> call, Response<T> response) {
        if (response.code() == 401) {
            // launch login activity using `this.context`
        } else {
            onSuccess(response.body());
        }
    }

    @Override
    public void onFailure(Call<T> call, Throwable t) {

    }

    abstract void onSuccess(T response);

}
公共抽象类BaseCallback实现回调{
私人最终语境;
公共基回调(上下文){
this.context=上下文;
}
@凌驾
公共void onResponse(调用、响应){
if(response.code()==401){
//使用“this.context”启动登录活动`
}否则{
onSuccess(response.body());
}
}
@凌驾
失败时公共无效(调用调用,可丢弃的t){
}
成功时的抽象无效(T响应);
}
现在,从调用方站点,您应该使用
newbasecallback
更改
newcallback

public abstract class BaseCallback<T> implements Callback<T> {

    private final Context context;

    public BaseCallback(Context context) {
        this.context = context;
    }

    @Override
    public void onResponse(Call<T> call, Response<T> response) {
        if (response.code() == 401) {
            // launch login activity using `this.context`
        } else {
            onSuccess(response.body());
        }
    }

    @Override
    public void onFailure(Call<T> call, Throwable t) {

    }

    abstract void onSuccess(T response);

}
call.enqueue(新的BaseCallback(上下文){
@凌驾
成功时无效(令牌响应){
//用代币做点什么
}
});
尽管如此,这种方法并不能满足您的以下陈述:

因此,我不必为每个api调用重复相同的代码


然而,我想不出更好的方法。

就我个人而言,我建议在这里使用事件总线模式。您可以使用或任何您想要的,因为它更多地是关于架构方法,而不是具体的实现

  • 创建事件模型

    public class UnauthorizedEvent {
    
        private static final UnauthorizedEvent INSTANCE = new UnauthorizedEvent();
    
        public static UnauthorizedEvent instance() {
            return INSTANCE;
        }
    
        private UnauthorizedEvent() {
        }
    }
    
  • 实现自定义
    拦截器
    ,该拦截器分派有关未经授权请求的事件

    class UnauthorizedInterceptor implements Interceptor {
    
        @Override
        public Response intercept(@NonNull Chain chain) throws IOException {
            Response response = chain.proceed(chain.request());
            if (response.code() == 401) {
                EventBus.getDefault().post(UnauthorizedEvent.instance());
            }
            return response;
        }
    }
    
  • 创建
    BaseActivity
    类,该类处理
    未经授权的事件

    public class BaseActivity extends Activity {
    
        @Override
        public void onStart() {
            super.onStart();
            EventBus.getDefault().register(this);
        }
    
        @Override
        public void onStop() {
            super.onStop();
            EventBus.getDefault().unregister(this);
        }
    
        @Subscribe
        public final void onUnauthorizedEvent(UnauthorizedEvent e) {
            handleUnauthorizedEvent();
        }
    
        protected void handleUnauthorizedEvent() {
            Intent intent = new Intent(this, LoginActivity.class);
            startActivity(intent);
        }
    }
    
  • 阻止从
    LoginActivity

    public class LoginActivty extends BaseActivity {
    
        @Override
        protected void handleUnauthorizedEvent() {
            //Don't handle unauthorized event
        }
    }
    
    另一种方法是在此处不扩展
    BaseActivity

  • 注册你的拦截器

    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
    
                    Request newRequest = chain.request().newBuilder()
                            .addHeader("Authorization", "Bearer " + auth_token_string)
                            .build();
    
                    Response response =  chain.proceed(newRequest);
                    Log.d("MyApp", "Code : "+response.code());
                    if (response.code() == 401){
                        Intent intent = new Intent(SplashActivity.getContextOfApplication(), LoginActivity.class);
                        startActivity(intent);
                        finish();  //Not working
                        return response;
                    }
    
                    return chain.proceed(newRequest);
                }
            }).build();
    
    OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(new UnauthorizedInterceptor())
            .build();
    
    class HeaderInterceptor(private val token: String?) : Interceptor {
    
        @Throws(IOException::class)
        override fun intercept(chain: Interceptor.Chain): Response {
            val request = chain.request()
            val newRequest =  request.newBuilder()
    
            Log.d(TAG, "token: $token")
    
            if (token != null && token.isNotBlank()) {
                newRequest.addHeader("Authorization", "Bearer $token")
            }
    
            return chain.proceed(newRequest.build())
        }
    
        companion object {
            private val TAG = HeaderInterceptor::class.java.toString()
        }
    
    } 
    
  • 优点:

    • 组件之间的松耦合
    • 通过覆盖
      handleUnauthorizedEvent
    • 无需重写代码即可使用新类型的回调
    • 减少犯错误的人为因素(使用
      Callback
      而不是
      BaseCallback
    缺点:

    • EventBus模式使调试更加复杂
    • 一个以上的依赖项或自己的实现,为项目带来新的代码

    另外,请注意,这个例子没有涉及多线程问题。它解决了您处理未授权请求的问题。因此,如果两个请求收到401,则有可能启动两个
    LoginActivity
    实例。

    最简单的方法是在拦截器实例中注入活动上下文。 如果您正在使用一些DI工具,如Dagger2或牙签,它将非常简单。我建议使用牙签)

    最接近的代码将在kotlin中,因为这是我的样板代码。那些人认为,你需要解决你的问题,我将用Java编写

    解决方案如下:

    @Qualifier
    annotation class BackendUrl
    
    
    class ActivityModule(activity: BaseActivity): Module() {
    
        init {
            bind(Activity::class.java).toInstance(activity)
        }
    
    }
    
    class NetworkModule: Module() {
    
        init {
            bind(String::class.java).withName(BackendUrl::class.java).toInstance(Constants.URL)
            bind(Gson::class.java).toInstance(GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create())
            bind(CacheHolder::class.java).toProvider(CacheProvider::class.java).singletonInScope()
            bind(OkHttpClient::class.java).toProvider(OkHttpProvider::class.java).instancesInScope()
            bind(BackendApi::class.java).toProvider(BackendApiProvider::class.java).instancesInScope()
            bind(RedirectInterceptor::class.java).to(RedirectInterceptor::class.java)
        }
    
    }
    
    您需要为注入依赖项创建提供程序

    class BackendApiProvider @Inject constructor(
            private val okHttpClient: OkHttpClient,
            private val gson: Gson,
            @BackendUrl private val serverPath: String
    ) : Provider<BackendApi> {
    
        override fun get() =
                Retrofit.Builder()
                        .addConverterFactory(GsonConverterFactory.create(gson))
                        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                        .client(okHttpClient)
                        .baseUrl(serverPath)
                        .build()
                        .create(BackendApi::class.java)
    }
    
    哦,是的。对于授权标头,最好创建另一个拦截器的新实例

    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
    
                    Request newRequest = chain.request().newBuilder()
                            .addHeader("Authorization", "Bearer " + auth_token_string)
                            .build();
    
                    Response response =  chain.proceed(newRequest);
                    Log.d("MyApp", "Code : "+response.code());
                    if (response.code() == 401){
                        Intent intent = new Intent(SplashActivity.getContextOfApplication(), LoginActivity.class);
                        startActivity(intent);
                        finish();  //Not working
                        return response;
                    }
    
                    return chain.proceed(newRequest);
                }
            }).build();
    
    OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(new UnauthorizedInterceptor())
            .build();
    
    class HeaderInterceptor(private val token: String?) : Interceptor {
    
        @Throws(IOException::class)
        override fun intercept(chain: Interceptor.Chain): Response {
            val request = chain.request()
            val newRequest =  request.newBuilder()
    
            Log.d(TAG, "token: $token")
    
            if (token != null && token.isNotBlank()) {
                newRequest.addHeader("Authorization", "Bearer $token")
            }
    
            return chain.proceed(newRequest.build())
        }
    
        companion object {
            private val TAG = HeaderInterceptor::class.java.toString()
        }
    
    } 
    
    还有你的OkhttpProvider

    class OkHttpProvider @Inject constructor(cacheHolder: CacheHolder, prefs: IPreferences, redirectInterceptor: RedirectInterceptor) : Provider<OkHttpClient> {
    
        private val client: OkHttpClient
    
        init {
    
            val builder = OkHttpClient.Builder()
            builder
                    .addNetworkInterceptor(redirectInterceptor)
                    .addNetworkInterceptor(HeaderInterceptor(prefs.getAuthToken()))
                    .readTimeout(30, TimeUnit.SECONDS)
                    .cache(cacheHolder.okHttpCache)
    
            client = builder.build()
        }
    
        override fun get() = client
    }
    

    广义解: 您可以通过概括错误处理来解决它。您可以将自定义CallAdapterFactory用于改装生成器。请参阅以下课程:

    RXErrorHandlingCallAdapter工厂:

    public class RxErrorHandlingCallAdapterFactory extends CallAdapter.Factory {
        private static Context mContext = null;
        private final RxJava2CallAdapterFactory original;
    
        private RxErrorHandlingCallAdapterFactory() {
            original = RxJava2CallAdapterFactory.create();
        }
    
        public static CallAdapter.Factory create(Context context) {
            mContext = context;
            return new RxErrorHandlingCallAdapterFactory();
        }
    
        @Override
        public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
            return new RxCallAdapterWrapper(retrofit, original.get(returnType, annotations, retrofit));
        }
    
        private static class RxCallAdapterWrapper<R> implements CallAdapter<R, Object> {
            private final Retrofit retrofit;
            private final CallAdapter<R,
                    Object> wrapped;
    
            public RxCallAdapterWrapper(Retrofit retrofit, CallAdapter<R, Object> wrapped) {
                this.retrofit = retrofit;
                this.wrapped = wrapped;
            }
    
            @Override
            public Type responseType() {
                return wrapped.responseType();
            }
    
            @Override
            public Object adapt(Call<R> call) {
                Object result = wrapped.adapt(call);
                if (result instanceof Single) {
                    return ((Single) result).onErrorResumeNext(new Function<Throwable, SingleSource>() {
                        @Override
                        public SingleSource apply(@NonNull Throwable throwable) throws Exception {
                            return Single.error(asRetrofitException(throwable));
                        }
                    });
                }
                if (result instanceof Observable) {
                    return ((Observable) result).onErrorResumeNext(new Function<Throwable, ObservableSource>() {
                        @Override
                        public ObservableSource apply(@NonNull Throwable throwable) throws Exception {
                            return Observable.error(asRetrofitException(throwable));
                        }
                    });
                }
                if (result instanceof Completable) {
                    return ((Completable) result).onErrorResumeNext(new Function<Throwable, CompletableSource>() {
                        @Override
                        public CompletableSource apply(@NonNull Throwable throwable) throws Exception {
                            return Completable.error(asRetrofitException(throwable));
                        }
                    });
                }
                return result;
            }
    
            private RetrofitException asRetrofitException(Throwable throwable) {
                // We had non-200 http error
                Log.v("log", "eror");
                throwable.printStackTrace();
                if (throwable instanceof HttpException) {
                    HttpException httpException = (HttpException) throwable;
                    final Response response = httpException.response();
    
    
                    //if ((mContext instanceof Activity)) {
    
                    String s = "Something went wrong."; //mContext.getString(R.string.something_went_wrong);
                    try {
                        s = new JSONObject(response.errorBody().string()).getString("message");
                        if (response.code() == 401) { // 401 Unauthorized
                            Intent intent = new Intent(mContext, LoginActivity.class);
                            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                            mContext.startActivity(intent);
                        }
                    } catch (JSONException | IOException e) {
                        e.printStackTrace();
                    }
    
                    return RetrofitException.unexpectedError(s, response, retrofit);
    
                    //showErrorDialog(mContext, response);
                    //}
    
    //                return RetrofitException.httpError(response.errorBody().toString(), response, retrofit);
                }
                // A network error happened
                if (throwable instanceof IOException) {
                    return RetrofitException.networkError((IOException) throwable);
                }
                // We don't know what happened. We need to simply convert to an unknown error
                return RetrofitException.unexpectedError(throwable);
            }
        }
    }
    
    public class RetrofitException extends RuntimeException {
        private final String url;
        private final Response response;
        private final Kind kind;
        private final Retrofit retrofit;
    
        RetrofitException(String message, String url, Response response, Kind kind, Throwable exception, Retrofit retrofit) {
            super(message, exception);
            this.url = url;
            this.response = response;
            this.kind = kind;
            this.retrofit = retrofit;
        }
    
        public static RetrofitException httpError(String url, Response response, Retrofit retrofit) {
            String message = response.code() + " " + response.message();
            return new RetrofitException(message, url, response, Kind.HTTP, null, retrofit);
        }
    
        public static RetrofitException networkError(IOException exception) {
            return new RetrofitException(exception.getMessage(), null, null, Kind.NETWORK, exception, null);
        }
    
        public static RetrofitException unexpectedError(Throwable exception) {
            return new RetrofitException(exception.getMessage(), null, null, Kind.UNEXPECTED, exception, null);
        }
    
        public static RetrofitException unexpectedError(String s, Response response, Retrofit retrofit) {
            return new RetrofitException(s, null, null, Kind.UNEXPECTED, null, null);
        }
    
        /**
         * The request URL which produced the error.
         */
        public String getUrl() {
            return url;
        }
    
        /**
         * Response object containing status code, headers, body, etc.
         */
        public Response getResponse() {
            return response;
        }
    
        /**
         * The event kind which triggered this error.
         */
        public Kind getKind() {
            return kind;
        }
    
        /**
         * The Retrofit this request was executed on
         */
        public Retrofit getRetrofit() {
            return retrofit;
        }
    
        /**
         * HTTP response body converted to specified {@code type}. {@code null} if there is no
         * response.
         *
         * @throws IOException if unable to convert the body to the specified {@code type}.
         */
        public <T> T getErrorBodyAs(Class<T> type) throws IOException {
            if (response == null || response.errorBody() == null) {
                return null;
            }
            Converter<ResponseBody, T> converter = retrofit.responseBodyConverter(type, new Annotation[0]);
            return converter.convert(response.errorBody());
        }
    
        /**
         * Identifies the event kind which triggered a {@link RetrofitException}.
         */
        public enum Kind {
            /**
             * An {@link IOException} occurred while communicating to the server.
             */
            NETWORK,
            /**
             * A non-200 HTTP status code was received from the server.
             */
            HTTP,
            /**
             * An internal error occurred while attempting to execute a request. It is best practice to
             * re-throw this exception so your application crashes.
             */
            UNEXPECTED
        }
    }
    
    Retrofit retrofit = new Retrofit.Builder()
                .addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create(context))
                    .addConverterFactory(GsonConverterFactory.create())
                    .baseUrl(API_URL)
                    .client(client)
                    .build();
    

    您可以在
    RXerorHandlingCallAdapterFactory
    中处理
    401
    ,并通过
    Throwable
    处理其他错误。这就是拦截器处理401全局错误的方式

    public class ResponseHeaderInterceptor implements Interceptor {
    private final Context context;
    
    public ResponseHeaderInterceptor(Context context) {
        this.context = context;
    }
    
    @NotNull
    @Override
    public Response intercept(@NotNull Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());
        if(response.code() == 401){
            SharedPreferences pref = context.getSharedPreferences(Constants.PREFERENCES, 0);
            String userName = pref.getString("key_user_email", "");
            //clear shared preferences
            pref.edit().clear().apply();
            Bundle params = new Bundle();
            params.putString("user", userName);
            FirebaseAnalytics.getInstance(context).logEvent(Constants.USER_UNAUTHORIZED_EVENT, params);
            Intent intent = new Intent(this.context, IntroActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            this.context.startActivity(intent);
        }
        return response;
    }
    
    }

    添加到改装的okhttp客户端

    var okHttpClient: OkHttpClient = OkHttpClient()
                .newBuilder()
                .addInterceptor(ResponseHeaderInterceptor(MyApplication.getMyApplicationContext()))//Header interceptor for logging network responses
                .build()
        private var retrofit: Retrofit? = null
        val client: Retrofit?
            get() {
                if (retrofit == null) {
                    retrofit = Retrofit.Builder()
                            .client(okHttpClient)
                            .baseUrl(BuildConfig.SERVER)
                            .addConverterFactory(GsonConverterFactory.create())
                            .build()
                }
                return retrofit
            }
    

    为什么要从
    拦截器
    启动
    活动
    <代码>拦截器s仅用于读取/修改/阻止网络请求。如果响应401,我希望将用户重定向到LoginActivity,因此我不必为每个api调用重复相同的代码。有更好的主意吗?您可以创建一个
    回调
    的实现,该实现对响应状态代码执行检查,如果是401,则触发一些逻辑来启动LoginActivity。您在哪里进行此改装类调用?它是在活动中还是在某种演示者中?如果网络呼叫是在活动的上下文中启动的,您可能可以使用Intent来启动活动(如果我不是太生疏的话)我已经更新了答案。请查收。如果您有任何疑问,请告诉我。他专门要求回复代码401,但不是每个4XX回复代码。虽然这可能有效,但这不是一个好的解决方案。回调不适合启动activities@TimCastelijns,有更好的建议吗,先生?@TimCasteljins,这不是OP试图避免的方法吗?
    ,但我看不出这有什么更好的办法