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

时间:2018-01-19 11:57:12

标签: java android retrofit retrofit2 okhttp

如何从拦截器(非活动类)启动LoginActivity?我已经尝试了下面的代码(Interceptor),但没有为我工作。

拦截

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();

这是我正在使用的当前解决方案,有没有比这更好的解决方案?这个解决方案必须在每次api呼叫时保持重复。

MainActivity

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) {

            }
        });

7 个答案:

答案 0 :(得分:7)

考虑引入SELECT * FROM v$open_cursor oc, v$session s WHERE oc.sid = s.sid order by 3,2; 接口的自定义实现,例如retrofit2.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); } new Callback<Token>

new BaseCallback<Token>

虽然这种方法不能满足您的以下声明:

  

所以我不必为每次api调用再次重复相同的代码

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

答案 1 :(得分:3)

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

  1. 创建活动模型

    public class UnauthorizedEvent {
    
        private static final UnauthorizedEvent INSTANCE = new UnauthorizedEvent();
    
        public static UnauthorizedEvent instance() {
            return INSTANCE;
        }
    
        private UnauthorizedEvent() {
        }
    }
    
  2. 实施自定义Interceptor,以解除有关未经授权的请求的事件

    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;
        }
    }
    
  3. 创建处理BaseActivity

    UnauthorizedEvent
    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);
        }
    }
    
  4. 阻止从LoginActivity

    启动LoginActivity
    public class LoginActivty extends BaseActivity {
    
        @Override
        protected void handleUnauthorizedEvent() {
            //Don't handle unauthorized event
        }
    }
    

    另一种方法是不在此处扩展BaseActivity

  5. 注册你的拦截器

    OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(new UnauthorizedInterceptor())
            .build();
    
  6. 优点:

    • 组件之间的松散耦合
    • 通过覆盖handleUnauthorizedEvent
    • 轻松扩展逻辑
    • 无需重写代码即可使用新类型的回调
    • 减少犯错误的人为因素(使用Callback代替BaseCallback

    缺点:

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

    此外,请注意,此示例并未涵盖多线程问题。它解决了处理未经授权的请求的问题。因此,如果两个请求收到401,则可能会启动2个LoginActivity实例。

答案 2 :(得分:2)

广义解决方案: 您可以通过概括错误处理来解决它。您可以将自定义CallAdapterFactory用于“Retrofit”构建器。请参考以下课程:

RxErrorHandlingCallAdapterFactory:

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);
        }
    }
}

RetrofitException:

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();

您可以通过401处理RxErrorHandlingCallAdapterFactory中的Throwable和其他错误。

答案 3 :(得分:1)

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

https://github.com/stephanenicolas/toothpick

附近的大部分代码都在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)
}

你的重定向拦截器:

public class RedirectInterceptor implements Interceptor {

    private final Context context;

    @Inject
    public RedirectInterceptor(Activity context) {
        this.context = context;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request newRequest = chain.request().newBuilder()
                .build();

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

        return chain.proceed(newRequest);
    }
}

哦,是的。对于Authorization标头,最好创建另一个拦截器的新实例

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()
    }

} 

和你的OkhttpProvder

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
}

多数民众赞成!现在,您只需要将模块放在正确的范围内。

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.init_view)      

        Toothpick.openScopes("activity scope").apply {
            installModules(ActivityModule(this@YourActivity))
            Toothpick.inject(this@YourActivity, this)
        }

        Toothpick.openScopes("activity scope", "network scope").apply {
            installModules(NetworkModule())
        }

        // your activity code
    }

答案 4 :(得分:0)

这就是拦截器在全局处理 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
        }

答案 5 :(得分:0)

一个简单的解决方案。您可能会通过一点同步来改进它。

class ExpiredTokenDetectionInterceptor() : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val request: Request = chain.request()
        val response = chain.proceed(request)
        if (response.code == 401) {
            // do something
        }
        return response
    }
}

答案 6 :(得分:-2)

使用OkHttpClient.Builder的authenticticator()方法。

当响应为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;
        }
}