Retrofit遇401跳转LoginActivity及非Activity拦截器启动页面问题
解决Retrofit Interceptor中401跳转LoginActivity的问题 & 消除重复代码
首先得先指出你原Interceptor代码里的几个关键问题:
- 线程问题:Interceptor是在OkHttp的后台线程执行的,直接调用
startActivity会因为不在主线程而报错; - 上下文标记缺失:用Application上下文启动Activity时,必须添加
Intent.FLAG_ACTIVITY_NEW_TASK,否则会抛出android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.异常; - 无效代码:
finish()是Activity类的方法,Interceptor不是Activity,这行代码完全没用; - 重复请求:你最后又调用了一次
chain.proceed(newRequest),这会导致同一个请求被执行两次,这是严重的错误!
接下来给你几个更优的解决方案,彻底消除重复代码:
方案一:用LiveData实现全局401事件通知(推荐,低耦合)
这种方式通过全局LiveData发送401事件,在所有Activity的基类中统一监听处理,完全解耦网络层和UI层。
步骤1:创建全局事件LiveData
public class UnauthorizedEvent extends MutableLiveData<Boolean> { private static UnauthorizedEvent instance; public static synchronized UnauthorizedEvent getInstance() { if (instance == null) { instance = new UnauthorizedEvent(); } return instance; } }
步骤2:修改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){ // 切换到主线程发送事件 new Handler(Looper.getMainLooper()).post(() -> UnauthorizedEvent.getInstance().setValue(true) ); } return response; // 注意:只返回一次请求结果! } }).build();
步骤3:在BaseActivity中监听事件
创建所有Activity的基类,统一处理401跳转:
public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 监听401未授权事件 UnauthorizedEvent.getInstance().observe(this, isUnauthorized -> { if (isUnauthorized) { Intent loginIntent = new Intent(this, LoginActivity.class); // 清除栈内所有Activity,避免返回后回到未授权页面 loginIntent.addFlags( Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK ); startActivity(loginIntent); finish(); // 重置事件状态,防止重复触发 UnauthorizedEvent.getInstance().setValue(false); } }); } }
之后所有业务Activity都继承这个BaseActivity即可,无需再在每个回调里写401判断。
方案二:自定义Retrofit CallAdapter统一处理响应
这种方式在Retrofit的CallAdapter层统一拦截所有响应,自动处理401情况,适合希望在网络层直接处理的场景。
步骤1:实现自定义CallAdapter
public class AuthCallAdapter<T> implements CallAdapter<T, Call<T>> { private final CallAdapter<T, Call<T>> delegate; private final Context appContext; public AuthCallAdapter(CallAdapter<T, Call<T>> delegate, Context appContext) { this.delegate = delegate; this.appContext = appContext; } @Override public Type responseType() { return delegate.responseType(); } @Override public Call<T> adapt(Call<T> call) { return new AuthWrappedCall<>(call, appContext); } private static class AuthWrappedCall<T> implements Call<T> { private final Call<T> delegate; private final Context appContext; AuthWrappedCall(Call<T> delegate, Context appContext) { this.delegate = delegate; this.appContext = appContext; } @Override public void enqueue(Callback<T> callback) { delegate.enqueue(new Callback<T>() { @Override public void onResponse(Call<T> call, Response<T> response) { if (response.code() == 401) { // 处理401跳转 Intent loginIntent = new Intent(appContext, LoginActivity.class); loginIntent.addFlags( Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK ); appContext.startActivity(loginIntent); // 如果当前上下文是Activity,直接finish if (appContext instanceof Activity) { ((Activity) appContext).finish(); } } else { // 正常回调给业务代码 callback.onResponse(call, response); } } @Override public void onFailure(Call<T> call, Throwable t) { callback.onFailure(call, t); } }); } // 其他Call接口方法直接委托给原Call实现 @Override public Response<T> execute() throws IOException { return delegate.execute(); } @Override public boolean isExecuted() { return delegate.isExecuted(); } @Override public void cancel() { delegate.cancel(); } @Override public boolean isCanceled() { return delegate.isCanceled(); } @Override public Call<T> clone() { return new AuthWrappedCall<>(delegate.clone(), appContext); } @Override public Request request() { return delegate.request(); } } }
步骤2:创建CallAdapter工厂
public class AuthCallAdapterFactory extends CallAdapter.Factory { private final Context appContext; public AuthCallAdapterFactory(Context context) { this.appContext = context.getApplicationContext(); } @Override public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { // 获取Retrofit默认的CallAdapter CallAdapter<?, ?> delegate = retrofit.nextCallAdapter(this, returnType, annotations); return new AuthCallAdapter<>((CallAdapter<Object, Call<Object>>) delegate, appContext); } }
步骤3:构建Retrofit时添加工厂
Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(new AuthCallAdapterFactory(getApplicationContext())) .build();
这样所有Retrofit的请求都会自动处理401情况,业务代码里无需再做任何判断。
方案三:直接在Interceptor中启动Activity(不推荐)
如果一定要在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){ new Handler(Looper.getMainLooper()).post(() -> { Intent intent = new Intent(SplashActivity.getContextOfApplication(), LoginActivity.class); // 必须添加NEW_TASK标记,否则会报错 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); SplashActivity.getContextOfApplication().startActivity(intent); }); } return response; } }).build();
这种方式无法直接关闭当前Activity,只能通过FLAG_ACTIVITY_CLEAR_TASK清空整个栈,使用场景有限。
内容的提问来源于stack exchange,提问作者Rick
相关产品推荐
相关产品推荐

