Heim  >  Artikel  >  Web-Frontend  >  Warum kann „response.body().string()“ nicht mehrmals aufgerufen werden?

Warum kann „response.body().string()“ nicht mehrmals aufgerufen werden?

亚连
亚连Original
2018-06-13 10:31:022504Durchsuche

Ich glaube, jeder hat OkHttp verwendet oder ist damit in Kontakt gekommen. Ich bin hier auf eine Falle gestoßen, damit jeder es umgehen kann, wenn er in Zukunft auf ähnliche Probleme stößt >

Nur ​​eine Lösung. Probleme reichen nicht aus. Dieser Artikel konzentriert sich auf die Analyse der Ursache des Problems aus der Perspektive des Quellcodes, der voller nützlicher Informationen ist.

1. Das Problem wurde gefunden

Während der Entwicklung habe ich eine Anfrage initiiert, indem ich das OkHttpClient-Objekt erstellt und es der Warteschlange hinzugefügt habe Der Server hat geantwortet: Die Callback-Schnittstelle löst die Methode onResponse() aus und verwendet dann das Response-Objekt, um die Rückgabeergebnisse zu verarbeiten und Geschäftslogik in dieser Methode zu implementieren. Der Code sieht ungefähr wie folgt aus:

//注:为聚焦问题,删除了无关代码
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());
  }
});

In onResponse() habe ich zur Vereinfachung des Debuggens den Rückgabetext gedruckt und ihn dann über die Methode parseResponseStr() analysiert (Hinweis: Response.body( ) wird hier zweimal aufgerufen string() ).

Dieser Code, der scheinbar keine Probleme hatte, ging nach der Ausführung tatsächlich schief: Über die Konsole wurde festgestellt, dass die Rückgabedaten (JSON) erfolgreich gedruckt wurden, aber dann wurde eine Ausnahme ausgelöst:

java.lang.IllegalStateException: closed

2. Lösen Sie das Problem

Nach der Überprüfung des Codes wurde festgestellt, dass das Problem beim Aufruf von parseResponseStr() und der Verwendung von Response.body( .string erneut () als Parameter. Da ich es eilig hatte, habe ich online nachgeschaut und festgestellt, dass „response.body().string()“ nur einmal aufgerufen werden kann. Daher habe ich das Problem gelöst, indem ich die Logik in der Methode „onResponse()“ geändert habe:

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. Analysieren Sie das Problem anhand des Quellcodes

Nachdem das Problem gelöst ist, muss es anschließend noch analysiert werden. Da mein bisheriges Verständnis von OkHttp auf seine Verwendung beschränkt war und ich die Details seiner internen Implementierung nicht sorgfältig analysiert hatte, habe ich mir am Wochenende die Zeit genommen, einen Blick darauf zu werfen und die Ursache des Problems herauszufinden.

Lassen Sie uns zunächst die intuitivste Frage analysieren: Warum kann Response.body().string() nur einmal aufgerufen werden?

Um es auseinanderzunehmen, holen Sie sich zuerst das ResponseBody-Objekt (es ist eine abstrakte Klasse, wir müssen uns hier nicht um die spezifische Implementierungsklasse kümmern) über Response.body() und rufen Sie dann string() auf Methode von ResponseBody, um den Inhalt des Antworttexts abzurufen.

Nach der Analyse gibt es kein Problem mit der Methode body():

public final String string() throws IOException {
 return new String(bytes(), charset().name());
}

Es ist sehr einfach, den Zeichensatz (Charset) anzugeben Die byte()-Methode gibt das Byte[ ] zurück. Das Array wird in ein String-Objekt umgewandelt. Es gibt kein Problem mit der byte()-Methode:

public final byte[] bytes() throws IOException {
 //...
 BufferedSource source = source();
 byte[] bytes;
 try {
  bytes = source.readByteArray();
 } finally {
  Util.closeQuietly(source);
 }
 //...
 return bytes;
}
//... 表示删减了无关代码,下同。

In der byte()-Methode. Lesen Sie das Byte[]-Array über das BufferedSource-Schnittstellenobjekt und geben Sie es zurück. In Kombination mit der oben erwähnten Ausnahme ist mir die Methode Util.closeQuietly() im Codeblock „finally“ aufgefallen. Verzeihung? Lautlos schließen? ? ?

Diese Methode sieht seltsam aus? Schauen Sie nach:

public static void closeQuietly(Closeable closeable) {
 if (closeable != null) {
  try {
   closeable.close();
  } catch (RuntimeException rethrown) {
   throw rethrown;
  } catch (Exception ignored) {
  }
 }
}

Es stellt sich heraus, dass die oben erwähnte BufferedSource-Schnittstelle gemäß der Codedokumentation als Ressourcenpuffer verstanden werden kann Kommentare Implementierte die Closeable-Schnittstelle und schloss und gab Ressourcen frei, indem die Methode close() überschrieben wurde. Schauen Sie dann nach unten, um zu sehen, was die Methode close() bewirkt (im aktuellen Szenario ist die BufferedSource-Implementierungsklasse RealBufferedSource):

//持有的 Source 对象
public final Source source;
@Override
public void close() throws IOException {
 if (closed) return;
 closed = true;
 source.close();
 buffer.clear();
}

Offensichtlich wird die Ressource durch source.close() geschlossen und freigegeben. Apropos: Die Funktion der Methode closeQuietly () ist offensichtlich, nämlich das von der ResponseBody-Unterklasse gehaltene BufferedSource-Schnittstellenobjekt zu schließen.

Nachdem wir dies analysiert haben, stellen wir plötzlich fest: Wenn wir Response.body().string() zum ersten Mal aufrufen, gibt OkHttp die Pufferressourcen des Antworttextes zurück und ruft die Methode closeQuietly() auf, um sie stillschweigend freizugeben die Ressourcen.

Auf diese Weise kehren wir beim erneuten Aufrufen der string()-Methode immer noch zur obigen byte()-Methode zurück. Diesmal liegt das Problem in der Codezeile bytes = source.readByteArray(). Werfen wir einen Blick auf die readByteArray()-Methode von RealBufferedSource:

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

Schauen Sie sich weiterhin die writeAll()-Methode an:

@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;
}

Das Problem liegt in der source.read() von for Schleife. Denken Sie daran, dass bei der Analyse der Methode close() oben source.close() aufgerufen wurde, um die Ressource zu schließen und freizugeben. Was passiert also, wenn die read()-Methode erneut aufgerufen wird:

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

An diesem Punkt entspricht es dem Absturz, den ich zuvor erlebt habe:

java.lang.IllegalStateException: closed

4.OkHttp Warum ist es so konzipiert?

Durch die Manipulation des Quellcodes haben wir die Ursache des Problems gefunden, aber ich habe immer noch eine Frage: Warum ist OkHttp auf diese Weise konzipiert?

Tatsächlich lässt sich dieses Problem am besten verstehen, wenn man sich die Anmerkungsdokumentation von ResponseBody ansieht, da JakeWharton in den Ausgaben geantwortet hat:

reply of JakeWharton in okhttp issues

In einem einfachen Satz: Es ist auf ResponseBody dokumentiert lief, um das Klassenanmerkungsdokument zu lesen, und fasste es schließlich wie folgt zusammen:

在实际开发中,响应主体 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组件“外”如何使用父组件

微信小程序如何实现涂鸦

Das obige ist der detaillierte Inhalt vonWarum kann „response.body().string()“ nicht mehrmals aufgerufen werden?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn