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

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

亚连
亚连オリジナル
2018-06-13 10:31:022504ブラウズ

最近 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 までご連絡ください。