在Android项目开发中,我们经常遇到需要统一处理某些特定状态码的场景。
本文分享一个项目中遇到的 4406状态码(实名认证) 处理不统一问题,从问题分析到完整解决方案,提供一套可复用的架构设计模式。
在项目开发过程中,我发现4406状态码(实名认证)的处理存在以下核心问题:
问题类型 | 具体表现 | 影响程度 |
---|---|---|
处理逻辑分散 | 在多个地方需要重复添加相同的处理逻辑 | 高 |
容易遗漏 | 新增接口时容易忘记添加4406处理 | 高 |
代码冗余 | 相同的处理逻辑在多处重复 | 中 |
网络框架复杂 | 项目中使用了多种网络请求方式,难以统一处理 | 高 |
循环依赖 | 网络层需要调用UI层,形成模块间循环依赖 | 高 |
原使用的解决方案:
针对不同的网络请求方式,单独添加不同框架的回调处理机制。
而项目经过了约10年的漫长历史,已集成了多套网络框架,导致处理逻辑分散、容易遗漏。
包括:传统HTTP请求、Retrofit回调、OkHttp实例、MVP模式、MVVM模式、特定业务组件独立封装网络请求等。
比如部分框架的回调处理逻辑,如:
// MVVM模式
fun ViewModel.request(
block: suspend () -> ResultModel,
success: (T) -> Unit = {},
error: (AppException) -> Unit = {}, // ⚠️ 需要在这里处理4406
complete: () -> Unit = {}
)
// Retrofit传统回调
public abstract class BaseRetrofitResponseCallback {
public abstract void onSuccess(T response);
public abstract void onFailure(AppException exception); // ⚠️ 需要在这里处理4406
}
// 原生OkHttp
public interface OnHttpRequestListener {
void onSuccess(String response); // ⚠️ 需要解析JSON检查4406
void onFailure(String error);
}
// MVP模式
public class YSPresenter {
protected void onNetworkError(AppException exception) {
// ⚠️ 需要在这里处理4406
}
}
经过彻底排查,还定位到有4个接口绕过了标准的ResultModel解析流程,导致即便在统一处理逻辑中处理了4406,但实际请求中仍遗漏处理4406。
采用响应拦截器的解决方案,通过采用添加OkHttp拦截器,有以下几点优点:
RealNameAuthInterceptor
统一处理所有网络请求的4406状态码而完整实施这套方案,有以下几个技术细节:
(终于进入正文了)
分几步走:
OkHttpClient.Builder.addInterceptor()
是OkHttp框架中的核心方法,用于添加应用拦截器:
核心代码:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
// 1. 添加日志拦截器(调试时使用)
if (BuildConfig.DEBUG) {
builder.addInterceptor(logInterceptor);
}
// 2. 添加签名拦截器(请求参数加密)
builder.addInterceptor(new Interceptor() {
// 为POST请求添加签名头信息
});
// 3. 添加实名认证拦截器(统一处理4406状态码)
builder.addInterceptor(new RealNameAuthInterceptor());
// 4. 构建OkHttpClient实例
OkHttpClient mClient = builder.build();
然后通过HttpsHelper.getInstance().getCustomOkHttpClient()
为所有Service提供统一的OkHttpClient实例。
以此,确保所有网络请求都会经过RealNameAuthInterceptor,网络配置修改只需在一个地方进行。
在Android项目架构中,遇到模块间循环依赖的问题:
架构依赖关系:
app模块 → common模块 → network模块
↑ ↑
└─────────┘ (不能形成循环依赖)
通过在基建模块中定义回调接口,提供给上层模块自定义实现的方式,解决循环依赖问题:
/**
* 位于base模块定义,并在base模块使用
*/
interface RealNameAuthHandler {
fun handleRealNameAuth()
}
/**
* 全局4406处理器
*/
private var realNameAuthHandler: RealNameAuthHandler? = null
/**
* 注册4406处理器 - 在Application中调用
*/
fun setRealNameAuthHandler(handler: RealNameAuthHandler) {
realNameAuthHandler = handler
}
/**
* 获取4406处理器
*/
fun getRealNameAuthHandler(): RealNameAuthHandler? {
return realNameAuthHandler
}
// 在app模块中实现
override fun onCreate() {
super.onCreate()
// 注册4406处理器
setRealNameAuthHandler(object : RealNameAuthHandler {
override fun handleRealNameAuth() {
// 处理实名认证逻辑
val currentActivity = AppManager.getCurrentActivity()
if (currentActivity is Activity && !currentActivity.isFinishing) {
val decorView = currentActivity.window.decorView
val extra = BannerAndModelBean().apply {
extraParam = ""
}
// 在主线程中显示弹窗
Handler(Looper.getMainLooper()).post {
RealAuthenticationPop.showRealAuthenticationPop(
currentActivity,
decorView,
extra
)
}
}
}
})
}
当调用 response.body().string()
后原response再次调用,会抛出异常:
java.lang.IllegalStateException: closed
在OkHttp的拦截器中,response.body().string()
方法只能调用一次,这是因为:
ResponseBody
的string()
方法内部使用了BufferedSource
流。public final String string() throws IOException {
return new String(bytes(), charset().name());
}
public final byte[] bytes() throws IOException {
// ...
BufferedSource source = source();
byte[] bytes;
try {
bytes = source.readByteArray();
} finally {
Util.closeQuietly(source); // 关键:默默关闭资源
}
// ...
return bytes;
}
Util.closeQuietly()
方法:
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
@Override
public void close() throws IOException {
if (closed) return;
closed = true;
source.close();
buffer.clear();
}
当再次调用 string()
时,会执行到 RealBufferedSource.readByteArray()
:
@Override
public byte[] readByteArray() throws IOException {
buffer.writeAll(source);
return buffer.readByteArray();
}
在 writeAll()
方法中:
@Override
public long writeAll(Source source) throws IOException {
// ...
for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) {
totalBytesRead += readCount;
}
return totalBytesRead;
}
最终在 source.read()
方法中检查到资源已关闭:
@Override
public long read(Buffer sink, long byteCount) throws IOException {
// ...
if (closed) throw new IllegalStateException("closed");
// ...
return buffer.read(sink, toRead);
}
OkHttp 将 ResponseBody 设计为**一次性流(one-shot)**的原因:
解决方案:重新构建ResponseBody
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
if (response.isSuccessful) {
try {
val responseBody = response.body
if (responseBody != null) {
// 读取原始响应体内容
val originalContent = responseBody.string()
// 解析JSON检查4406状态码
val jsonObject = JSONObject(originalContent)
val code = jsonObject.optInt("code", -1)
if (code == 4406) {
...
return response.newBuilder()
.body(newResponseBody)
.build()
} else {
// 重新构建原始响应体
val newResponseBody = ResponseBody.create(
responseBody.contentType(),
originalContent
)
return response.newBuilder()
.body(newResponseBody)
.build()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
return response
}
当拦截器处理了4406状态码后,如果不修改响应体内容,后续的业务逻辑仍然会检测到code != 200
,导致:
/**
* 修改4406响应,避免后续错误处理
* @param originalContent 原始响应内容
* @return 修改后的响应内容
*/
private fun modifyResponse4406(originalContent: String): String {
return try {
val jsonObject = JSONObject(originalContent)
// msg 改为空,就不会弹出toast提示了
jsonObject.put("msg", "")
jsonObject.toString()
} catch (e: JSONException) {
// 如果JSON解析失败,返回原始字符串
originalContent
}
}
基于回调接口模式的解决方案,我们可以将其扩展到其他类似的统一处理场景:
/**
* 登录状态处理接口
* 用于处理token过期、登录失效等场景
*/
interface LoginStateHandler {
fun handleTokenExpired()
fun handleLoginRequired()
}
/**
* 网络异常处理接口
* 用于处理网络错误、服务器维护等场景
*/
interface NetworkErrorHandler {
fun handleNetworkError(errorCode: Int, message: String)
fun handleServerMaintenance()
}
这种设计模式的核心优势在于:
在实施统一错误处理方案时,应遵循以下架构设计原则:
总结出以下基建规范,遵循这些规范,可提高代码质量、可扩展性、可维护性:
通过HttpsHelper.getInstance().getCustomOkHttpClient()
为所有Service提供统一的OkHttpClient实例,这不仅仅是技术实现,更是架构设计的重要体现:
架构层面的价值:
OkHttp的拦截器机制采用了责任链模式(Chain of Responsibility Pattern),这是一种行为型设计模式。在该模式中,多个处理器对象组成一条链,请求沿着链传递,直到被某个处理器处理。
// 拦截器执行顺序的设计思考
builder.addInterceptor(logInterceptor) // 1. 日志记录
.addInterceptor(signInterceptor) // 2. 签名加密
.addInterceptor(realNameAuthInterceptor) // 3. 实名认证处理
官方文档
A call to chain.proceed(request) is a critical part of each interceptor’s implementation. This simple-looking method is where all the HTTP work happens, producing a response to satisfy the request. If chain.proceed(request) is being called more than once previous response bodies must be closed.
Interceptors can be chained. Suppose you have both a compressing interceptor and a checksumming interceptor: you’ll need to decide whether data is compressed and then checksummed, or checksummed and then compressed. OkHttp uses lists to track interceptors, and interceptors are called in order.
对 chain.proceed(request) 的调用是每个拦截器实现的关键部分。这个看起来简单的方法是所有 HTTP 工作发生的地方,生成响应以满足请求。如果 chain.proceed(request) 被多次调用,则必须关闭之前的响应正文。
拦截器都可以被链接。假设您同时有一个压缩拦截器和一个校验和拦截器:您需要决定是压缩数据然后进行校验和计算,还是校验和计算然后压缩数据。OkHttp 使用列表来跟踪拦截器,拦截器是按顺序调用的
1. Interceptor 接口
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
}
2. Chain 接口
public interface Chain {
Request request();
Response proceed(Request request) throws IOException;
}
拦截器执行顺序
拦截器的执行顺序遵循**先进后出(FILO)**的原则:
请求发送:Interceptor1 → Interceptor2 → Interceptor3 → 网络层
响应接收:Interceptor3 ← Interceptor2 ← Interceptor1 ← 网络层
chain.proceed() 方法
这是责任链模式的核心,每个拦截器通过调用chain.proceed()
将请求传递给下一个拦截器:
// 伪代码展示责任链的执行流程
public Response intercept(Chain chain) throws IOException {
// 前置处理
Request request = chain.request();
// 可以修改请求
// 关键:调用下一个拦截器
Response response = chain.proceed(request);
// 后置处理
// 可以修改响应
return response;
}
请求和响应的传递
// 请求传递:每个拦截器都可以修改请求
Request modifiedRequest = request.newBuilder()
.addHeader("Authorization", "Bearer token")
.build();
// 响应传递:每个拦截器都可以修改响应
Response modifiedResponse = response.newBuilder()
.body(newResponseBody)
.build();
请求阶段
1. 应用拦截器1(日志记录)
↓
2. 应用拦截器2(添加签名)
↓
3. 应用拦截器3(实名认证检查)
↓
4. 网络拦截器(缓存处理)
↓
5. 实际网络请求
响应阶段
5. 实际网络响应
↑
4. 网络拦截器(缓存处理)
↑
3. 应用拦截器3(实名认证处理)
↑
2. 应用拦截器2(响应处理)
↑
1. 应用拦截器1(日志记录)
1. 解耦合
2. 可扩展性
3. 灵活性
4. 可测试性