ホームページ ウェブフロントエンド jsチュートリアル なぜresponse.body().string()を複数回呼び出すことができないのでしょうか?

なぜresponse.body().string()を複数回呼び出すことができないのでしょうか?

Jun 13, 2018 am 10:31 AM
string

最近 Okhttp を使用していたときに、落とし穴を踏んだことがあると思います。将来同じような問題が発生したときに回避できるように、ここで共有します。この記事は、ソース コードの観点から問題の根本を分析することに焦点を当てており、有益な情報が満載です。

1. 問題が見つかりました 開発中に、OkHttpClient オブジェクトを構築してリクエストを開始し、サーバーが応答した後、コールバック インターフェイスが 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( ) はここで 2 回呼び出されます)。

このコードは問題がないように見えましたが、実際には実行後に問題が発生しました。コンソールを通して、戻り本体データ (json) が正常に出力されたことがわかりましたが、例外がスローされました:

java.lang.IllegalStateException: closed
ログイン後にコピー

2。問題 コードを確認したところ、parseResponseStr() を呼び出すときに、response.body().string() がパラメータとして再度使用されていることが問題であることがわかりました。急いでいたのでオンラインで調べたところ、response.body().string() は 1 回しか呼び出せないことがわかったので、onResponse() メソッドのロジックを変更して問題を解決しました。 . ソースコードで問題を分析します

問題は解決しましたが、その後もまだ分析する必要があります。私はこれまで OkHttp についてその用途のみを理解していて、内部実装の詳細を注意深く分析していなかったので、週末に時間をかけて OkHttp を観察し、問題の原因を突き止めました。 最初に最も直感的な質問を分析してみましょう: なぜ response.body().string() は 1 回しか呼び出せないのでしょうか? 逆アセンブリを見ると、まず、response.body() を通じて ResponseBody オブジェクト (抽象クラスです。ここでは特定の実装クラスを気にする必要はありません) を取得し、次に ResponseBody の string() メソッドを呼び出します。応答本文の内容を取得します。

分析後、body() メソッドには問題はありません。string() メソッドを見てみましょう:

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);
  }
});
ログイン後にコピー

これは、byte() メソッドによって返された byte[] 配列を String オブジェクトに変換します。文字セット (charset) を指定し、構築します。問題ありません。引き続き byte() メソッドを見てください:

public final String string() throws IOException {
 return new String(bytes(), charset().name());
}
ログイン後にコピー

byte() メソッドでは、BufferedSource インターフェイス オブジェクトを通じて byte[] 配列を読み取り、それを返します。前述の例外と組み合わせると、finally コード ブロックに Util.closeQuietly() メソッドがあることに気付きました。すみません?静かに閉じますか? ? ?

このメソッドは奇妙に見えますね? close() メソッドをコピーして、リソースを閉じて解放するインターフェース。次に、close() メソッドが何を行うかを確認します (現在のシナリオでは、BufferedSource 実装クラスは RealBufferedSource です):

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

明らかに、リソースはsource.close() を通じて閉じられ、解放されます。そういえば、closeQuietly() メソッドの機能は自明であり、ResponseBody サブクラスが保持する BufferedSource インターフェイス オブジェクトを閉じることです。

分析のこの時点で、私たちは突然気づきました。response.body().string() を初めて呼び出すと、OkHttp は応答本文のバッファー リソースを返し、closeQuietly() メソッドを呼び出してサイレントにリソース。

このように、string() メソッドを再度呼び出すと、やはり上記の byte() メソッドに戻ります。今回の問題は、コードの bytes = source.readByteArray() 行にあります。 RealBufferedSource の readByteArray() メソッドを見てみましょう:

public static void closeQuietly(Closeable closeable) {
 if (closeable != null) {
  try {
   closeable.close();
  } catch (RuntimeException rethrown) {
   throw rethrown;
  } catch (Exception ignored) {
  }
 }
}
ログイン後にコピー

引き続き writeAll() メソッドを見てみましょう:

//持有的 Source 对象
public final Source source;
@Override
public void close() throws IOException {
 if (closed) return;
 closed = true;
 source.close();
 buffer.clear();
}
ログイン後にコピー

問題は for ループの source.read() にあります。上記の close() メソッドを分析するときに、source.close() を呼び出してリソースを閉じて解放したことを思い出してください。では、read() メソッドが再度呼び出されるとどうなるでしょうか:

@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;
}
ログイン後にコピー

4. OkHttp はなぜこのように設計されているのでしょうか。

ソース コードを修正することで、問題の根本が見つかりました。しかし、まだ疑問が残っています。なぜ OkHttp はこのように設計されているのでしょうか?

実際、この問題を理解する最良の方法は、JakeWharton が問題で答えたように、ResponseBody のアノテーション ドキュメントを参照することです。

@Override
public long read(Buffer sink, long byteCount) throws IOException {
  //...
  if (closed) throw new IllegalStateException("closed");
  //...
  return buffer.read(sink, toRead);
}
ログイン後にコピー
簡単な文で言うと、次のようになります。 、最終的な要約は次のとおりです:

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

微信小程序如何实现涂鸦

以上がなぜresponse.body().string()を複数回呼び出すことができないのでしょうか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Java の String.valueOf() 関数を使用して基本データ型を文字列に変換する Java の String.valueOf() 関数を使用して基本データ型を文字列に変換する Jul 24, 2023 pm 07:55 PM

Java の String.valueOf() 関数を使用して基本データ型を文字列に変換する Java 開発で基本データ型を文字列に変換する必要がある場合、一般的な方法は String クラスの valueOf() 関数を使用することです。この関数は、基本データ型のパラメータを受け入れ、対応する文字列表現を返すことができます。この記事では、基本的なデータ型変換に String.valueOf() 関数を使用する方法を検討し、いくつかのコード例を提供します。

char配列を文字列に変換する方法 char配列を文字列に変換する方法 Jun 09, 2023 am 10:04 AM

char配列をstringに変換する方法:代入によって実現できます {char a[]=" abc d\0efg ";string s=a;} 構文を使用して、char配列にstringに値を直接代入させて実行します変換を完了するためのコード。

Java の String.replace() 関数を使用して、文字列内の文字 (文字列) を置換します。 Java の String.replace() 関数を使用して、文字列内の文字 (文字列) を置換します。 Jul 25, 2023 pm 05:16 PM

Java の String.replace() 関数を使用して文字列内の文字 (文字列) を置換する Java では、文字列は不変オブジェクトです。つまり、文字列オブジェクトが作成されると、その値は変更できません。ただし、文字列内の特定の文字または文字列を置換する必要がある状況が発生する場合があります。現時点では、Java の String クラスの replace() メソッドを使用して文字列置換を実装できます。 String クラスの replace() メソッドには 2 つのタイプがあります。

2wワード詳細説明文字列、yyds 2wワード詳細説明文字列、yyds Aug 24, 2023 pm 03:56 PM

皆さんこんにちは。今日は Java の基本知識である String についてお話します。 String クラスの重要性は言うまでもなく、バックエンド開発で最もよく使用されるクラスであるため、説明する必要があります。

JavaのString.length()関数を使用して文字列の長さを取得します。 JavaのString.length()関数を使用して文字列の長さを取得します。 Jul 25, 2023 am 09:09 AM

文字列の長さを取得するには、Java の String.length() 関数を使用します。Java プログラミングでは、文字列は非常に一般的なデータ型です。多くの場合、文字列の長さ、つまり文字列内の文字数を取得する必要があります。 Java では、String クラスの length() 関数を使用して文字列の長さを取得できます。簡単なコード例を次に示します。 publicclassStringLengthExample{publ

JavaのStringクラスの使い方 JavaのStringクラスの使い方 Apr 19, 2023 pm 01:19 PM

1. JDK の String1. String を理解する まず、JDK の String クラスのソース コードを見てみましょう. これには多くのインターフェイスが実装されています. String クラスは Final によって変更されていることがわかります. これは、String クラスができないことを意味しますString. クラスのサブクラスは継承されず、String. クラスのサブクラスは存在しないため、JDK を使用するすべての人が同じ String クラスを使用します。String の継承が許可されている場合、誰もが String を拡張できます。全員が異なるバージョンの String を使用し、2 人の異なる人が String. クラスを使用します。同じメソッドでも異なる結果が表示されるため、コードの開発が不可能になります。継承とメソッドのオーバーライドは柔軟性をもたらすだけでなく、多くのサブクラスの動作が異なる原因になります。

Golang関数のバイト、ルーン、文字列型変換スキル Golang関数のバイト、ルーン、文字列型変換スキル May 17, 2023 am 08:21 AM

Golang プログラミングでは、バイト、ルーン、文字列型は非常に基本的で一般的なデータ型です。これらは、文字列やファイル ストリームなどのデータ操作の処理において重要な役割を果たします。これらのデータ操作を実行するときは、通常、データを相互に変換する必要があるため、変換スキルを習得する必要があります。この記事では、読者がこれらのデータ型をより深く理解し、プログラミングの実践に上手に適用できるようにすることを目的として、Golang 関数のバイト、ルーン、および文字列の型変換テクニックを紹介します。

Java String での Split メソッドの使用方法 Java String での Split メソッドの使用方法 May 02, 2023 am 09:37 AM

String の Split メソッドは、String の Split() メソッドを使用して、受信する文字または文字列に従って String を分割し、分割された配列を返します。 1. 一般的な使用法 @ や区切り文字などの一般的な文字を使用する場合: 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