问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

java求助

发布网友 发布时间:2022-04-23 20:17

我来回答

3个回答

懂视网 时间:2022-04-23 00:19

想必大家都用过或接触过 OkHttp,我最近在使用 Okhttp 时,就踩到一个坑,在这儿分享出来,以后大家遇到类似问题时就可以绕过去

只是解决问题是不够的,本文将 侧重从源码角度分析下问题的根本,干货满满。

1.发现问题

在开发时,我通过构造 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
public byte[] readByteArray() throws IOException {
 buffer.writeAll(source);
 return buffer.readByteArray();
}

继续往下看 writeAll() 方法:

@Override
public long writeAll(Source source) throws IOException {
 //...
 long totalBytesRead = 0;
 for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) {
 totalBytesRead += readCount;
 }
 return totalBytesRead;
}

问题出在 for 循环的 source.read() 这儿。还记得在上面分析 close() 方法时,其调用了 source.close() 来关闭并释放资源。那么,再次调用 read() 方法会发生什么呢:

@Override
public long read(Buffer sink, long byteCount) throws IOException {
 //...
 if (closed) throw new IllegalStateException("closed");
 //...
 return buffer.read(sink, toRead);
}

至此,与我在前面遇到的崩溃对上了:

java.lang.IllegalStateException: closed

4.OkHttp 为什么要这么设计?

通过 fuc*ing the source code ,我们找到了问题的根本,但我还有一个疑问:OkHttp 为什么要这么设计?

其实,理解这个问题最好的方式就是查看 ResponseBody 的注释文档,正如 JakeWharton 在 issues 中给出的回复:

reply of JakeWharton in okhttp issues

就简单的一句话: It's documented on ResponseBody. 于是我跑去看类注释文档,最后梳理如下:

在实际开发中,响应主体 RessponseBody 持有的资源可能会很大,所以 OkHttp 并不会将其直接保存到内存中,只是持有数据流连接。只有当我们需要时,才会从服务器获取数据并返回。同时,考虑到应用重复读取数据的可能性很小,所以将其设计为 一次性流(one-shot) ,读取后即 '关闭并释放资源'。

5.总结

最后,总结以下几点注意事项,划重点了:

1.响应体只能被使用一次;

2.响应体必须关闭:值得注意的是,在下载文件等场景下,当你以 response.body().byteStream() 形式获取输入流时,务必通过 Response.close() 来手动关闭响应体。

3.获取响应体数据的方法:使用 bytes() 或 string() 将整个响应读入内存;或者使用 source() , byteStream() , charStream() 方法以流的形式传输数据。

4.以下方法会触发关闭响应体:

Response.close()
Response.body().close()
Response.body().source().close()
Response.body().charStream().close()
Response.body().byteString().close()
Response.body().bytes()
Response.body().string()

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在Javascript中如何实现网页抢红包

详细解读ES6语法中可迭代协议

详细解读在React组件“外”如何使用父组件

微信小程序如何实现涂鸦

热心网友 时间:2022-04-22 21:27

你好,我过去遇到这个问题是因为多次调用一个已经关闭的对象。
比如:同一个页面中再次调用response.sendRedirect()方法。还有可能是提交的URL错误,即不是个有效的URL。
我建议你看看是不是把response.sendRedirect()放到循环里了,或者是多次调用了。API上说调用sendRedirect()方法后,response就已经是close的了。

热心网友 时间:2022-04-22 22:45

代码如下:
String[] array = { "123456", "123456", "123456", "879687", "123456",
"689098", "879687", "689098" };
Map<String, Integer> map = new HashMap<String, Integer>();
String key = null;
int count = 0;
for (int i = 0; i < array.length; i++) {
key = array[i];
if (map.containsKey(key)) {
count = map.get(key) + 1;
} else {
count = 1;
}
map.put(key, count);
}
Iterator<String> keys = map.keySet().iterator();
while (keys.hasNext()) {
key = keys.next();
System.out.println(key + " " + map.get(key));
}
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
梦幻西游我这吸血鬼值多少钱 梦幻西游吸血鬼价格问题,这个吸血鬼能卖多少钱啊?MHB比例100元=500W 给... 诚心求解梦幻西游,我这只吸血鬼能值多少钱?我要个能卖出去的价... 梦幻西游手游,我现在69级,买个79的吸血鬼,可以带吗, 我梦幻西游69J号买了个119级吸血鬼,但是不能摆摊出售,那可以跟别人交易... 梦幻西游手游洗出变异吸血鬼不能摆摊 梦幻西游本人80级 有一只吸血鬼 系统不给摆摊 请问能否交易 和平精英 和平精英封号了能注销掉吗? 梦见想蛇一样的东西掉进下水道 梦见包掉到下水道里 JAVA错误:方法声明无效 需要返回类型 求高手检查自定义异常程序~~ 为什么支付宝提现要手续费 Not enough credit balance 是什么意思? qq钱包提醒我balance not enough什么意思 如何找回微信语音聊天记录 怎么恢复已经删除的微信聊天记录,包括语和文字? 微信中查找聊天记录怎么找 微信聊天记录删除了怎么恢复,可以找回语音和文字记录吗 怎样找回QQ聊天记录的语音消息? 求今年中秋出生的男孩女孩名字,女孩名最好出自《诗经》,男孩名最好出自... 包子是如何制作的? 素包子馅的种类和做法大全 芽菜肉馅的做法步骤图,芽菜肉馅怎么做好吃 碎米芽菜包子怎么做好吃? 大包子做法怎么做,包子调馅大全,包子馅都是怎么做 芽菜包子馅怎么做才油 芽菜酱肉包怎么做 碎芽菜肉臊包子的好吃做法? 猪肉芽菜馅怎么做好吃 香菇芽菜包子怎么做 谁能帮我把这段话翻译成英文,谢谢! 求正确的英语·日语翻译:每天坚持三件事:看脸、看秤、看馀额。如果脸不够美、秤不够标准、馀额不够多, 有一首英文歌 女的唱的 吉他 歌词里好像是有 not not not not enough 好像还有mom said什么的 这是什么歌 支付宝提现突然收手续费了怎么回事? 旅馆业对健康码异常旅客报告制度及处理流程 enough和Not enough有什么分别 enough not to do 与 not enough to do的区别 请大家帮我翻译下这篇文章,谢谢 跪求一遍120字-150字的大学英语考试作文,题目My View on a Balanced Personal Development outweigh和outbalance的用法有区别吗? in balance是总而言之的意思吗,还是有别的意思? 有道解释是 总而言之,但它的例句 Thi 在网上看到一双new balance 574,但没有具体提到什么型号,就是后缀字母,认识这款鞋子的具体型号的谢谢啦 总是出现&quot;not enough timers available&quot; 急急急。。。谁有英语句子 能帮着解决的给分、这个额、要求比较苛刻、可我也没办法,老师滴要求。。。。。 中行汇票存进英国TSB卡,多久到账?saving中显示有balance,为什么还not available?没到账吗?急! 健康码出门怎么使用 招商财富增利5号现在值得买吗 招商财富资产管理有限公司怎么样? 招商财富(深圳)数字资产有限公司怎么样? 招商银行财富账户专业版怎么安装