一、项目背景
在项目原有的MVVM架构中,所有的请求都是调用一个封装好的retrofit网络请求工具类,在Repository请求的响应中进行初步解析,通过result_code和return_code同时为01进行判断,true则返回响应结果,false弹出Toast展示return_msg。
补充:在所有请求响应中,必须包含result_code和return_code其中之一,否则视为请求失败
二、项目需求
在此次的需求中,产品希望我们可以完成这样一个功能,在接收到指定的result_code时,打开一个新的页面,为了保证不留死角,产品要求所有请求甚至大部分请求都要能处理这一响应。
三、开发目标
1、满足产品的要求;
2、保护开发的头发;
3、保证代码的健壮性。
四、阴影中环伺的bug
第一套方案:
考虑到上述三个目标,我在开发中使用了拦截器的写法,即在进行网络请求时添加一个拦截器,在chain中获取请求响应,当拿到响应中的result_code,根据result_code的状态进行判断,符合要求的话就进入指定的页面。
//省略部分代码
@Singleton
class NewInterceptor @Inject constructor(
//...
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
//...
try {
//...
when (jsonObject.get("result_code").toString()) {
"100"->EventBus.getDefault().post(NewEvent(jsonObject.get("return_msg").toString() ?: ""))
}
} catch (e: Exception) {
e.printStackTrace()
}
}
return originalResponse
}
}
这个方案看起来很不错,能完美的完成上诉三个需求,也不用逐个Repository中做判断,但按照这个方案实施后,发现新打开的页面总会出现一个Toast,且这个Toast显示的信息是result_msg。
在排除了新页面可能存在的干扰后,我对这个bug进行了初步判断:
由于在代码中有些网络请求的响应参数没有接收result_code,所以初步怀疑是由于拦截器没有拦截到此类情况,在后续的响应流程中,代码没有满足开始的条件,所以弹出了return_msg。
根据这个判断,我优化了第一套方案的判断逻辑。
第二套方案
在第一套方案的基础上,我们增加一个对于return_msg的或判断(此判断方式十分不好,非必要不要使用,这里是所有请求的msg统一才使用的),当msg包含指定文本时,也要进入上述逻辑。现在我们的判断逻辑就是:
当result_code的状态符合要求或return_msg包含指定文本时时,跳转到指定页面。
//省略部分代码
@Singleton
class NewInterceptor @Inject constructor(
//...
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
//...
try {
//...
if(isResultCodeCondition(jsonObject.get("result_code").toString()) || isMsgCondition(jsonObject.get("return_msg").toString())) {
EventBus.getDefault().post(NewEvent(jsonObject.get("return_msg").toString() ?: ""))
}
} catch (e: Exception) {
e.printStackTrace()
}
}
return originalResponse
}
fun isResultCodeCondition(code: String): Boolean {
return when (code) {
"100", "101" -> true
else -> false
}
}
fun isMsgCondition(msg: String): Boolean {
return msg.contains("符合的文本")
}
}
改好再运行后,发现Toast依然弹出,说明初步判断的原因不是此次bug的关键原因,我们接着继续分析,如果你足够细心的话,你会注意到在上面的代码块中有这样一行代码:
return originalResponse
这意味着在上面的逻辑执行完后,拦截器依然返回了响应,继续往下分析,拦截器返回响应,又由于return_code不符合项目背景中的标准,导致弹出toast提示。
这一下,逻辑就通顺了,接着改吧,首先想到的是在拦截到指定条件后取消这次请求,于是第三套方案出现了:
第三套方案
if(isResultCodeCondition(jsonObject.get("result_code").toString()) || isMsgCondition(jsonObject.get("return_msg").toString())) {
EventBus.getDefault().post(NewEvent(jsonObject.get("return_msg").toString() ?: ""))
//取消请求
chain.call().cancel()
}
运行后我们发现,坏消息是:toast依然存在,好消息是:这次的toast提示的是“网络请求异常”,这个提示是在我们封装的网络请求工具类的**onError()**方法中响应的。这说明我们思路对了,果然是最后的那行代码导致的问题,那么怎么解决这个问题呢。
我们先来明确一下现在的问题:
首先,我们肯定是不能定义成没有返回的拦截器的,这样会导致正常的请求没有返回响应。
其次,直接取消请求会进入onError流程,也会弹出一个提示
比较简单的方案是,在拦截到指定条件后,抛出一个异常,再在onError()方法中判断异常,让我们定义的异常不做处理。我们这里采用这个方案。
最终方案
val isCapture = try {
//...
if(isResultCodeCondition(jsonObject.get("result_code").toString()) || isMsgCondition(jsonObject.get("return_msg").toString())) {
EventBus.getDefault().post(NewEvent(jsonObject.get("return_msg").toString() ?: ""))
true
} else {
false
}
} catch (e: Exception) {
e.printStackTrace()
false
}
if (isCapture) {
//抛出自定义异常
throw NewException()
}
总结
记录开发中遇到的bug,遇到的情形越多,开发时能考虑的情况和细节也就越多,类比于修仙,此之谓:斧凿道心。