val buffer = Buffer()

val writer = OutputStreamWriter(buffer.outputStream(), UTF_8)

val jsonWriter = gson.newJsonWriter(writer)

adapter.write(jsonWriter, value)

jsonWriter.close()

return buffer.readByteString().toRequestBody(MEDIA_TYPE)

}

companion object {

private val MEDIA_TYPE = “application/json; charset=UTF-8”.toMediaType()

private val UTF_8 = Charset.forName(“UTF-8”)

}

}

class MyGsonResponseBodyConverter(

private val gson: Gson,

private val adapter: TypeAdapter

) : Converter<ResponseBody, T> {

@Throws(IOException::class)

override fun convert(value: ResponseBody): T {

// 在这里通过 value 拿到 json 字符串进行解析

// 判断状态码是失败的情况,就抛出异常

val jsonReader = gson.newJsonReader(value.charStream())

value.use {

val result = adapter.read(jsonReader)

if (jsonReader.peek() != JsonToken.END_DOCUMENT) {

throw JsonIOException(“JSON document was not fully consumed.”)

}

return result

}

}

}

上面三个类中只需要修改 GsonResponseBodyConverter 的代码,因为是在这个类解析数据。可以在上面有注释的地方加入自己的处理。到底加什么代码,看完后面的内容就知道了。

虽然得到了我们想要的效果,但总感觉并不是很优雅,因为这只是在 gson 解析之前增加一些判断,而为此多写了很多和源码重复的代码。还有这是针对 Retrofit 进行处理的,如果公司用的是自己封装的 OkHttp 请求工具,就没法用这个方案了。

观察一下发现其实只是对一个 ResponseBody 对象进行解析判断状态码,就是说只需要得到个 ResponseBody 对象而已。那么还有什么办法能在 gson 解析之前拿到 ResponseBody 呢?

自定义拦截器处理返回结果

=======================================================================

很容易会想到用拦截器,按道理来说是应该是可行的,通过拦截器处理也不局限于使用 Retrofit,用 OkHttp 的也能处理。

想法很美好,但是实际操作起来并没有想象中的简单。刚开始可能会想到用 response.body().string() 读出 json 字符串。

public abstract class ResponseBodyInterceptor implements Interceptor {

@NotNull

@Override

public Response intercept(@NotNull Chain chain) throws IOException {

Response response = chain.proceed(chain.request());

String json = response.body().string();

// 对 json 进行解析判断状态码是失败的情况就抛出异常

return response;

}

}

看着好像没问题,但是尝试后发现,状态码是失败的情况确实没毛病,然而状态码是正确的情况却有问题了。

为什么会这样子?有兴趣的可以看下这篇文章《为何 response.body().string() 只能调用一次?》。简单总结一下就是考虑到应用重复读取数据的可能性很小,所以将其设计为一次性流,读取后即关闭并释放资源。我们在拦截器里用通常的 Response 使用方法会把资源释放了,后续解析没有资源了就会有问题。

那该怎么办呢?自己对 Response 的使用又不熟悉,怎么知道该怎么读数据不影响后续的操作。可以参考源码呀,OkHttp 也是用了一些拦截器处理响应数据,它却没有释放掉资源。

这里就不用大家去看源码研究怎么写的了,我直接封装好一个工具类提供大家使用,已经把响应数据的字符串得到了,大家可以直接编写自己的业务代码,拷贝下面的类使用即可。

abstract class ResponseBodyInterceptor : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {

val request = chain.request()

val url = request.url.toString()

val response = chain.proceed(request)

response.body?.let { responseBody ->

val contentLength = responseBody.contentLength()

val source = responseBody.source()

source.request(Long.MAX_VALUE)

var buffer = source.buffer

if (“gzip”.equals(response.headers[“Content-Encoding”], ignoreCase = true)) {

GzipSource(buffer.clone()).use { gzippedResponseBody ->

buffer = Buffer()

buffer.writeAll(gzippedResponseBody)

}

}

val contentType = responseBody.contentType()

val charset: Charset =

contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8

if (contentLength != 0L) {

return intercept(response,url, buffer.clone().readString(charset))

}

}

return response

}

abstract fun intercept(response: Response, url: String, body: String): Response

}

由于 OkHttp 源码已经用 Kotlin 语言重写了,所以只有个 Kotlin 版本的。但是可能还有很多人还没有用 Kotlin 写项目,所以个人又手动翻译了一个 Java 版本的,方便大家使用,同样拷贝使用即可。

public abstract class ResponseBodyInterceptor implements Interceptor {

@NotNull

@Override

public Response intercept(@NotNull Chain chain) throws IOException {

Request request = chain.request();

String url = request.url().toString();

Response response = chain.proceed(request);

ResponseBody responseBody = response.body();

if (responseBody != null) {

long contentLength = responseBody.contentLength();

BufferedSource source = responseBody.source();

source.request(Long.MAX_VALUE);

Buffer buffer = source.getBuffer();

if (“gzip”.equals(response.headers().get(“Content-Encoding”))) {

GzipSource gzippedResponseBody = new GzipSource(buffer.clone());

buffer = new Buffer();

buffer.writeAll(gzippedResponseBody);

}

MediaType contentType = responseBody.contentType();

Charset charset;

if (contentType == null || contentType.charset(StandardCharsets.UTF_8) == null) {

charset = StandardCharsets.UTF_8;

} else {

charset = contentType.charset(StandardCharsets.UTF_8);

}

if (charset != null && contentLength != 0L) {

return intercept(response,url, buffer.clone().readString(charset));

}

}

return response;

}

abstract Response intercept(@NotNull Response response,String url, String body);

}

主要是拿到 source 再获得 buffer,然后通过 buffer 去读出字符串。说下其中的一段 gzip 相关的代码,为什么需要有这段代码的处理,自己看源码的话可能会漏掉。这是因为 OkHttp 请求时会添加支持 gzip 压缩的预处理,所以如果响应的数据是 gzip 编码的,需要对 gzip 压缩数据解包再去读数据。

好了废话不多说,到底这个工具类怎么用,其实和拦截器一样使用,继承我封装好的 ResponseBodyInterceptor 类,在重写方法里加上自己需要的业务处理代码,body 参数就是我们想要的 json 字符串数据,可以进行解析判断状态码是失败情况并抛出异常。下面给一个简单的解析例子参考,json 结构是文章开头给出的例子,这里假设状态码不是 200 都抛出一个自定义异常。

class HandleErrorInterceptor : ResponseBodyInterceptor() {

override fun intercept(response: Response, body: String): Response {

var jsonObject: JSONObject? = null

try {

jsonObject = JSONObject(body)

} catch (e: Exception) {

e.printStackTrace()

}

if (jsonObject != null) {

if (jsonObject.optInt(“code”, -1) != 200 && jsonObject.has(“msg”)) {

throw ApiException(jsonObject.getString(“msg”))

}

}

return response

}

}

然后在 OkHttpClient 中添加该拦截器就可以了。

val okHttpClient = OkHttpClient.Builder()

// 其它配置

.addInterceptor(HandleErrorInterceptor())

.build()

万一后台返回的是更骚的数据呢?

==========================================================================

本人目前只遇到过失败时 data 类型不一致的情况,下面是一些小伙伴反馈的,如果大家有遇到类似或更骚的,都建议和后台沟通改成返回方便自己写业务逻辑代码的数据。实在沟通无果,再参考下面的案例看下是否有帮助。其实就是手动通过拦截器转成方便自己写逻辑的数据,这只是缓兵之策,太复杂的情况还是想办法和后台沟通吧。

数据需要去 msg 里取

=======================================================================

有位小伙伴提到的:骚的时候数据还会去 msg 取。(大家都经历过了什么…)

还是强调一下建议让后台改,实在没办法必须要这么做的话,再往下看。

假设返回的数据是下面这样的:

{

“code”: 200,

“msg”: {

“userId”: 123456,

“userName”: “admin”

}

}

通常 msg 返回的是个字符串,但这次居然是个对象,而且是我们需要得到的数据。我们解析的实体类已经定义了 msg 是字符串,当然不可能因为一个接口把 msg 改成泛型,所以我们需要偷偷地把数据改成我们想要得到的形式。

{

“code”: 200,

“msg”: “登录成功”

“data”: {

“userId”: 123456,

“userName”: “张三”

}

}

那么该怎么操作呢?代码比较简单,就不啰嗦了,记得要把该拦截器配置了。

class HandleLoginInterceptor: ResponseBodyInterceptor() {

override fun intercept(response: Response, url: String, body: String): Response {

var jsonObject: JSONObject? = null

try {

jsonObject = JSONObject(body)

if (url.contains(“/login”)) { // 当请求的是登录接口才处理

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

那我们该怎么做才能做到年薪60万+呢,对于程序员来说,只有不断学习,不断提升自己的实力。我之前有篇文章提到过,感兴趣的可以看看,到底要学习哪些知识才能达到年薪60万+。

通过职友集数据可以查看,以北京 Android 相关岗位为例,其中 【20k-30k】 薪酬的 Android 工程师,占到了整体从业者的 30.8%!

北京 Android 工程师「工资收入水平 」

今天重点内容是怎么去学,怎么提高自己的技术。

1.合理安排时间

2.找对好的系统的学习资料

3.有老师带,可以随时解决问题

4.有明确的学习路线

当然图中有什么需要补充的或者是需要改善的,可以在评论区写下来,一起交流学习。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img
,感兴趣的可以看看,到底要学习哪些知识才能达到年薪60万+。

通过职友集数据可以查看,以北京 Android 相关岗位为例,其中 【20k-30k】 薪酬的 Android 工程师,占到了整体从业者的 30.8%!

北京 Android 工程师「工资收入水平 」

[外链图片转存中…(img-vUqfTBFQ-1712762207419)]

今天重点内容是怎么去学,怎么提高自己的技术。

1.合理安排时间

2.找对好的系统的学习资料

3.有老师带,可以随时解决问题

4.有明确的学习路线

当然图中有什么需要补充的或者是需要改善的,可以在评论区写下来,一起交流学习。

[外链图片转存中…(img-NyKXkyJn-1712762207420)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-4748o09i-1712762207420)]

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐