在开发时,我通过构造 OkHttpClient
对象发起一次请求并加入队列,待服务端响应后,回调 Callback
接口触发 onResponse()
方法,然后在该方法中通过 Response
对象处理返回结果、实现业务逻辑。代码大致如下:
//注:为聚焦问题,删除了无关代码
getHttpClient().newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onResponse: " + response.body().toString());
}
//解析请求体
parseResponseStr(response.body().string());
}
});
在 onResponse()
中,为便于调试,我打印了返回体,然后通过 parseResponseStr()
方法解析返回体(注意:这儿两次调用了 response.body().string()
)。
这段看起来没有任何问题的代码,实际运行后却出了问题:通过控制台看到成功打印了返回体数据(json),但紧接着抛出了异常:
java.lang.IllegalStateException: closed
2.解决问题
检查代码后,发现问题出在调用 parseResponseStr()
时,再次使用了 response.body().string()
作为参数。由于当时赶时间,上网查阅后发现 response.body().string()
只能调用一次,于是修改 onResponse()
方法中的逻辑后解决了问题:
getHttpClient().newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}
@Override
public void onResponse(Call call, Response response) throws IOException {
//此处,先将响应体保存到内存中
String responseStr = response.body().string();
if (BuildConfig.DEBUG) {
Log.d(TAG, "onResponse: " + responseStr);
}
//解析请求体
parseReponseStr(responseStr);
}
});
3.结合源码分析问题
问题解决了,事后还是要分析的。由于之前对 OkHttp
的了解仅限于使用,没有仔细分析过其内部实现的细节,周末抽时间往下看了看,算是弄明白了问题发生的原因。
先分析最直观的问题:为何 response.body().string()
只能调用一次?
拆解来看,先通过 response.body()
得到 ResponseBody
对象(其是一个抽象类,在此我们不需要关心具体的实现类),然后调用 ResponseBody
的 string()
方法得到响应体的内容。
分析后 body()
方法没有问题,我们往下看 string()
方法:
public final String string() throws IOException {
return new String(bytes(), charset().name());
}
很简单,通过指定字符集(charset)将 byte()
方法返回的 byte[]
数组转为 String
对象,构造没有问题,继续往下看 byte()
方法:
public final byte[] bytes() throws IOException {
//…
BufferedSource source = source();
byte[] bytes;
try {
bytes = source.readByteArray();
} finally {
Util.closeQuietly(source);
}
//…
return bytes;
}
//...
表示删减了无关代码,下同。
在 byte()
方法中,通过 BufferedSource
接口对象读取 byte[]
数组并返回。结合上面提到的异常,我注意到 finally
代码块中的 Util.closeQuietly()
方法。excuse me?默默地关闭???
这个方法看起来很诡异有木有,跟进去看看:
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
原来,上面提到的 BufferedSource
接口,根据代码文档注释,可以理解为 资源缓冲区,其实现了 Closeable
接口,通过复写 close()
方法来 关闭并释放资源。接着往下看 close()
方法做了什么(在当前场景下,BufferedSource
实现类为 RealBufferedSource
):
//持有的 Source 对象
public final Source source;
@Override
public void close() throws IOException {
if (closed) return;
closed = true;
source.close();
buffer.clear();
}
很明显,通过 source.close()
关闭并释放资源。说到这儿, closeQuietly()
方法的作用就不言而喻了,就是关闭 ResponseBody
子类所持有的 BufferedSource
接口对象。
分析至此,我们恍然大悟:当我们第一次调用 response.body().string()
时,OkHttp 将响应体的缓冲资源返回的同时,调用 closeQuietly()
方法默默释放了资源。
如此一来,当我们再次调用 string()
方法时,依然回到上面的 byte()
方法,这一次问题就出在了 bytes = source.readByteArray()
这行代码。一起来看看 RealBufferedSource
的 readByteArray()
方法:
@Override
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
总而言之,Android开发行业变化太快,作为技术人员就要保持终生学习的态度,让学习力成为核心竞争力,所谓“活到老学到老”只有不断的学习,不断的提升自己,才能跟紧行业的步伐,才能不被时代所淘汰。
在这里我分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
roid开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。