Je crois que tout le monde a utilisé ou est entré en contact avec OkHttp lorsque j'utilisais Okhttp récemment, je vais le partager ici afin que tout le monde puisse le contourner lorsqu'il rencontrera des problèmes similaires à l'avenir. >
Juste une solution Les problèmes ne suffisent pas. Cet article se concentrera sur l'analyse de la racine du problème du point de vue du code source, qui regorge d'informations utiles.1. J'ai trouvé le problème
Pendant le développement, j'ai lancé une requête en construisant l'objet OkHttpClient et je l'ai ajouté à la file d'attente. Le serveur a répondu : l'interface de rappel déclenche la méthode onResponse(), puis utilise l'objet Response pour traiter les résultats renvoyés et implémenter la logique métier dans cette méthode. Le code est à peu près le suivant ://注:为聚焦问题,删除了无关代码 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()); } });
java.lang.IllegalStateException: closed
2. Résoudre le problème
Après avoir vérifié le code, j'ai découvert que le problème réside dans l'appel de parseResponseStr() et l'utilisation de réponse.body () encore une fois .string() comme paramètre. Parce que j'étais pressé, j'ai vérifié en ligne et découvert que réponse.body().string() ne peut être appelé qu'une seule fois, j'ai donc résolu le problème en modifiant la logique dans la méthode 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. Analysez le problème combiné avec le code source
Une fois le problème résolu, il doit encore être analysé par la suite. Comme ma compréhension précédente d'OkHttp se limitait à son utilisation et que je n'avais pas soigneusement analysé les détails de son implémentation interne, j'ai pris le temps de l'examiner de haut pendant le week-end et j'ai découvert la cause du problème. Analysons d'abord la question la plus intuitive : pourquoi réponse.body().string() ne peut-il être appelé qu'une seule fois ? En le démontant, récupérez d'abord l'objet ResponseBody (c'est une classe abstraite, nous n'avons pas besoin de nous soucier de la classe d'implémentation spécifique ici) via Response.body(), puis appelez la string() méthode de ResponseBody pour obtenir le contenu du corps de la réponse. Après analyse, il n'y a aucun problème avec la méthode body(). Regardons la méthode string() :public final String string() throws IOException { return new String(bytes(), charset().name()); }
public final byte[] bytes() throws IOException { //... BufferedSource source = source(); byte[] bytes; try { bytes = source.readByteArray(); } finally { Util.closeQuietly(source); } //... return bytes; } //... 表示删减了无关代码,下同。
public static void closeQuietly(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (RuntimeException rethrown) { throw rethrown; } catch (Exception ignored) { } } }
//持有的 Source 对象 public final Source source; @Override public void close() throws IOException { if (closed) return; closed = true; source.close(); buffer.clear(); }
@Override public byte[] readByteArray() throws IOException { buffer.writeAll(source); return buffer.readByteArray(); }
@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; }
@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.Pourquoi OkHttp est-il conçu de cette façon ?
En fouillant le code source, nous avons trouvé la racine du problème, mais j'ai encore une question : pourquoi OkHttp est-il conçu de cette façon ? En fait, la meilleure façon de comprendre ce problème est de consulter la documentation d'annotation de ResponseBody, comme JakeWharton a répondu dans les numéros :reply of JakeWharton in okhttp issues
在实际开发中,响应主体 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()
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!