Maison interface Web js tutoriel Pourquoi réponse.body().string() ne peut-il pas être appelé plusieurs fois ?

Pourquoi réponse.body().string() ne peut-il pas être appelé plusieurs fois ?

Jun 13, 2018 am 10:31 AM
string

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());
  }
});
Copier après la connexion
Dans onResponse(), pour faciliter le débogage, j'ai imprimé le corps de retour, puis j'ai analysé le corps de retour via la méthode parseResponseStr() (remarque : réponse. body() a été appelé deux fois ici .string() ).

Ce code, qui semblait n'avoir aucun problème, s'est en fait trompé après son exécution : il a été vu via la console que les données du corps de retour (json) avaient été imprimées avec succès, mais une exception a ensuite été levée :

java.lang.IllegalStateException: closed
Copier après la connexion
Copier après la connexion

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);
  }
});
Copier après la connexion

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());
}
Copier après la connexion
C'est très simple. la méthode byte() en spécifiant le jeu de caractères (charset). Le tableau byte[] est converti en un objet String. Il n'y a aucun problème avec la construction. Continuez à regarder la méthode byte() :

.
public final byte[] bytes() throws IOException {
 //...
 BufferedSource source = source();
 byte[] bytes;
 try {
  bytes = source.readByteArray();
 } finally {
  Util.closeQuietly(source);
 }
 //...
 return bytes;
}
//... 表示删减了无关代码,下同。
Copier après la connexion
Dans la méthode byte(), lisez le tableau byte[] via l'objet d'interface BufferedSource et renvoyez-le. Combiné avec l'exception mentionnée ci-dessus, j'ai remarqué la méthode Util.closeQuietly() dans le bloc de code final. excusez-moi? Fermer en silence ? ? ?

Cette méthode semble bizarre. Est-ce vrai ? Suivez et jetez un œil :

public static void closeQuietly(Closeable closeable) {
 if (closeable != null) {
  try {
   closeable.close();
  } catch (RuntimeException rethrown) {
   throw rethrown;
  } catch (Exception ignored) {
  }
 }
}
Copier après la connexion
Il s'avère que l'interface BufferedSource mentionnée ci-dessus peut être comprise comme un tampon de ressources selon le commentaires de la documentation du code. , qui implémente l'interface Closeable et ferme et libère les ressources en remplaçant la méthode close(). Regardez ensuite en bas pour voir ce que fait la méthode close() (dans le scénario actuel, la classe d'implémentation BufferedSource est RealBufferedSource) :

//持有的 Source 对象
public final Source source;
@Override
public void close() throws IOException {
 if (closed) return;
 closed = true;
 source.close();
 buffer.clear();
}
Copier après la connexion
Évidemment, la ressource est fermée et libérée via source.close(). En parlant de cela, la fonction de la méthode closeQuietly() va de soi, qui consiste à fermer l'objet d'interface BufferedSource détenu par la sous-classe ResponseBody.

Après avoir analysé cela, on se rend compte soudain : lorsque nous appelons Response.body().string() pour la première fois, OkHttp renvoie les ressources tampon du corps de la réponse et appelle la méthode closeQuietly() pour libérer silencieusement les ressources.

De cette façon, lorsque nous appelons à nouveau la méthode string(), nous revenons toujours à la méthode byte() ci-dessus. Cette fois, le problème réside dans la ligne de code bytes = source.readByteArray(). Jetons un coup d'œil à la méthode readByteArray() de RealBufferedSource :

@Override
public byte[] readByteArray() throws IOException {
 buffer.writeAll(source);
 return buffer.readByteArray();
}
Copier après la connexion
Continuez à regarder la méthode 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;
}
Copier après la connexion
Le problème réside dans la source.read( ) de la boucle for . N'oubliez pas que lors de l'analyse de la méthode close() ci-dessus, elle a appelé source.close() pour fermer et libérer la ressource. Alors, que se passe-t-il lorsque la méthode read() est à nouveau appelée :

@Override
public long read(Buffer sink, long byteCount) throws IOException {
  //...
  if (closed) throw new IllegalStateException("closed");
  //...
  return buffer.read(sink, toRead);
}
Copier après la connexion
À ce stade, elle rencontre le crash que j'ai rencontré plus tôt :

java.lang.IllegalStateException: closed
Copier après la connexion
Copier après la connexion

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
Copier après la connexion
En une phrase simple : C'est documenté sur ResponseBody. J'ai donc couru lire le document d'annotation de classe, et je l'ai finalement résumé ainsi :

在实际开发中,响应主体 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()
Copier après la connexion

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

相关文章:

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

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

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

微信小程序如何实现涂鸦

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!

Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

AI Hentai Generator

AI Hentai Generator

Générez AI Hentai gratuitement.

Article chaud

R.E.P.O. Crystals d'énergie expliqués et ce qu'ils font (cristal jaune)
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Meilleurs paramètres graphiques
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Comment réparer l'audio si vous n'entendez personne
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: Comment déverrouiller tout dans Myrise
4 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌

Outils chauds

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise

SublimeText3 version chinoise

Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

Dreamweaver CS6

Dreamweaver CS6

Outils de développement Web visuel

SublimeText3 version Mac

SublimeText3 version Mac

Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Convertissez les types de données de base en chaînes à l'aide de la fonction String.valueOf() de Java Convertissez les types de données de base en chaînes à l'aide de la fonction String.valueOf() de Java Jul 24, 2023 pm 07:55 PM

Convertir les types de données de base en chaînes à l'aide de la fonction String.valueOf() de Java Dans le développement Java, lorsque nous devons convertir les types de données de base en chaînes, une méthode courante consiste à utiliser la fonction valueOf() de la classe String. Cette fonction peut accepter les paramètres des types de données de base et renvoyer la représentation sous forme de chaîne correspondante. Dans cet article, nous explorerons comment utiliser la fonction String.valueOf() pour les conversions de types de données de base et fournirons quelques exemples de code pour

Comment convertir un tableau de caractères en chaîne Comment convertir un tableau de caractères en chaîne Jun 09, 2023 am 10:04 AM

Méthode de conversion d'un tableau de caractères en chaîne : cela peut être réalisé par affectation. Utilisez la syntaxe {char a[]=" abc d\0efg ";string s=a;} pour laisser le tableau de caractères attribuer directement une valeur à la chaîne et l'exécuter. le code pour terminer la conversion.

Utilisez la fonction String.replace() de Java pour remplacer des caractères (chaînes) dans une chaîne Utilisez la fonction String.replace() de Java pour remplacer des caractères (chaînes) dans une chaîne Jul 25, 2023 pm 05:16 PM

Remplacez les caractères (chaînes) dans une chaîne à l'aide de la fonction String.replace() de Java. En Java, les chaînes sont des objets immuables, ce qui signifie qu'une fois qu'un objet chaîne est créé, sa valeur ne peut pas être modifiée. Cependant, vous pouvez rencontrer des situations dans lesquelles vous devez remplacer certains caractères ou chaînes dans une chaîne. À l'heure actuelle, nous pouvons utiliser la méthode replace() dans la classe String de Java pour implémenter le remplacement de chaîne. La méthode replace() de la classe String a deux types :

Explication détaillée de 2 mots en chaîne, yyds Explication détaillée de 2 mots en chaîne, yyds Aug 24, 2023 pm 03:56 PM

Bonjour à tous, aujourd'hui je vais partager avec vous les connaissances de base de Java : String. Inutile de dire l'importance de la classe String, on peut dire que c'est la classe la plus utilisée dans notre développement back-end, il est donc nécessaire d'en parler.

Utilisez la fonction String.length() de Java pour obtenir la longueur d'une chaîne Utilisez la fonction String.length() de Java pour obtenir la longueur d'une chaîne Jul 25, 2023 am 09:09 AM

Utilisez la fonction String.length() de Java pour obtenir la longueur d'une chaîne. En programmation Java, la chaîne est un type de données très courant. Nous avons souvent besoin d'obtenir la longueur d'une chaîne, c'est-à-dire le nombre de caractères qu'elle contient. En Java, nous pouvons utiliser la fonction length() de la classe String pour obtenir la longueur d'une chaîne. Voici un exemple de code simple : publicclassStringLengthExample{publ

Compétences en matière de conversion d'octets, de runes et de types de chaînes Golang Compétences en matière de conversion d'octets, de runes et de types de chaînes Golang May 17, 2023 am 08:21 AM

Dans la programmation Golang, les types octet, rune et chaîne sont des types de données très basiques et courants. Ils jouent un rôle important dans le traitement des opérations de données telles que les chaînes et les flux de fichiers. Lors de l'exécution de ces opérations de données, nous devons généralement les convertir les unes aux autres, ce qui nécessite la maîtrise de certaines compétences de conversion. Cet article présentera les techniques de conversion de types d'octets, de runes et de chaînes des fonctions Golang, dans le but d'aider les lecteurs à mieux comprendre ces types de données et à être capables de les appliquer habilement dans la pratique de la programmation.

Comment utiliser la classe String de Java Comment utiliser la classe String de Java Apr 19, 2023 pm 01:19 PM

1. Comprendre String1. String dans le JDK Tout d'abord, jetons un coup d'œil au code source de la classe String dans le JDK. Il implémente de nombreuses interfaces. Vous pouvez voir que la classe String est modifiée par final. être hérité et il n'y a pas de sous-classe de la classe String, de sorte que toutes les personnes utilisant JDK utilisent la même classe String. Si String peut être hérité, tout le monde peut étendre String. Tout le monde utilise différentes versions de String. la même méthode montre des résultats différents, ce qui rend impossible le développement du code. L'héritage et le remplacement de méthode apportent non seulement de la flexibilité, mais entraînent également un comportement différent de nombreuses sous-classes.

Comment utiliser la méthode split dans Java String Comment utiliser la méthode split dans Java String May 02, 2023 am 09:37 AM

La méthode split dans String utilise la méthode split() de String pour diviser la chaîne en fonction des caractères ou des chaînes entrants et renvoyer le tableau divisé. 1. Utilisation générale Lors de l'utilisation de caractères généraux, tels que @ ou, comme séparateurs : Stringaddress="Shanghai@Shanghai City@Minhang District@Wuzhong Road";String[]splitAddr=address.split("@");System .out. println(splitAddr[0]+splitAddr[1]+splitAddr[2]+splitAddr[3

See all articles