写点什么

OkHttp 踩坑记:为何 response,androidui 设计

用户头像
Android架构
关注
发布于: 刚刚

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() {@Overridepublic void onFailure(Call call, IOException e) {}


@Overridepublic 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 对象(其是一个抽象类,在此我们不需要关心具体的实现类),然后调用 ResponseBodystring() 方法得到响应体的内容。


分析后 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 {Ut


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


il.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;


@Overridepublic 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() 这行代码。一起来看看 RealBufferedSourcereadByteArray() 方法:


@Overridepublic byte[] readByteArray() throws IOException {buffer.writeAll(source);return buffer.readByteArray();}


继续往下看 writeAll() 方法:

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
OkHttp踩坑记:为何 response,androidui设计