欧美一级特黄大片做受成人-亚洲成人一区二区电影-激情熟女一区二区三区-日韩专区欧美专区国产专区

如何解決android使用okhttp可能引發(fā)OOM的問(wèn)題

小編給大家分享一下如何解決android使用okhttp可能引發(fā)OOM的問(wèn)題,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!

成都創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括諸暨網(wǎng)站建設(shè)、諸暨網(wǎng)站制作、諸暨網(wǎng)頁(yè)制作以及諸暨網(wǎng)絡(luò)營(yíng)銷策劃等。多年來(lái),我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,諸暨網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到諸暨省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!

遇到一個(gè)問(wèn)題: 需要給所有的請(qǐng)求加簽名校驗(yàn)以防刷接口;傳入請(qǐng)求url及body生成一個(gè)文本串作為一個(gè)header傳給服務(wù)端;已經(jīng)有現(xiàn)成的簽名檢驗(yàn)方法String doSignature(String url, byte[] body);當(dāng)前網(wǎng)絡(luò)庫(kù)基于com.squareup.okhttp3:okhttp:3.14.2.
這很簡(jiǎn)單了,當(dāng)然是寫(xiě)一個(gè)interceptor然后將request對(duì)象的url及body傳入就好.于是有:

public class SignInterceptor implements Interceptor {
  @NonNull
  @Override
  public Response intercept(@NonNull Chain chain) throws IOException {
    Request request = chain.request();
    RequestBody body = request.body();
    byte[] bodyBytes = null;
    if (body != null) {
      final Buffer buffer = new Buffer();
      body.writeTo(buffer);
      bodyBytes = buffer.readByteArray();
    }

    Request.Builder builder = request.newBuilder();
    HttpUrl oldUrl = request.url();
    final String url = oldUrl.toString();
    final String signed = doSignature(url, bodyBytes));
    if (!TextUtils.isEmpty(signed)) {
      builder.addHeader(SIGN_KEY_NAME, signed);
    }
    return chain.proceed(builder.build());
  }
}

okhttp的ReqeustBody是一個(gè)抽象類,內(nèi)容輸出只有writeTo方法,將內(nèi)容寫(xiě)入到一個(gè)BufferedSink接口實(shí)現(xiàn)體里,然后再將數(shù)據(jù)轉(zhuǎn)成byte[]也就是內(nèi)存數(shù)組.能達(dá)到目的的類只有Buffer,它實(shí)現(xiàn)了BufferedSink接口并能提供轉(zhuǎn)成內(nèi)存數(shù)組的方法readByteArray. 這貌似沒(méi)啥問(wèn)題呀,能造成OOM?

是的,要看請(qǐng)求類型,如果是一個(gè)上傳文件的接口呢?如果這個(gè)文件比較大呢?上傳接口有可能會(huì)用到public static RequestBody create(final @Nullable MediaType contentType, final File file)方法,如果是針對(duì)文件的實(shí)現(xiàn)體它的writeTo方法是sink.writeAll(source);而我們傳給簽名方法時(shí)用到的Buffer.readByteArray是將緩沖中的所有內(nèi)容轉(zhuǎn)成了內(nèi)存數(shù)組, 這意味著文件中的所有內(nèi)容被轉(zhuǎn)成了內(nèi)存數(shù)組, 就是在這個(gè)時(shí)機(jī)容易造成OOM! RequestBody.create源碼如下:

 public static RequestBody create(final @Nullable MediaType contentType, final File file) {
  if (file == null) throw new NullPointerException("file == null");

  return new RequestBody() {
   @Override public @Nullable MediaType contentType() {
    return contentType;
   }

   @Override public long contentLength() {
    return file.length();
   }

   @Override public void writeTo(BufferedSink sink) throws IOException {
    try (Source source = Okio.source(file)) {
     sink.writeAll(source);
    }
   }
  };
 }

可以看到實(shí)現(xiàn)體持有了文件,Content-Length返回了文件的大小, 內(nèi)容全部轉(zhuǎn)給了Source對(duì)象。

這確實(shí)是以前非常容易忽略的一個(gè)點(diǎn),很少有對(duì)請(qǐng)求體作額外處理的操作,而一旦這個(gè)操作變成一次性的大內(nèi)存分配, 非常容易造成OOM. 所以要如何解決呢? 簽名方法又是如何處理的呢? 原來(lái)這個(gè)簽名方法在這里偷了個(gè)懶——它只讀取傳入body的前4K內(nèi)容,然后只針對(duì)這部分內(nèi)容進(jìn)行了加密,至于傳入的這個(gè)內(nèi)存數(shù)組本身多大并不考慮,完全把風(fēng)險(xiǎn)和麻煩丟給了外部(優(yōu)秀的SDK!).

快速的方法當(dāng)然是羅列白名單,針對(duì)上傳接口服務(wù)端不進(jìn)行加簽驗(yàn)證, 但這容易掛一漏萬(wàn),而且增加維護(hù)成本, 要簽名方法sdk的人另寫(xiě)合適的接口等于要他們的命, 所以還是得從根本解決. 既然簽名方法只讀取前4K內(nèi)容,我們便只將內(nèi)容的前4K部分讀取再轉(zhuǎn)成方法所需的內(nèi)存數(shù)組不就可了? 所以我們的目的是: 期望RequestBody能夠讀取一部分而不是全部的內(nèi)容. 能否繼承RequestBody重寫(xiě)它的writeTo? 可以,但不現(xiàn)實(shí),不可能全部替代現(xiàn)有的RequestBody實(shí)現(xiàn)類, 同時(shí)ok框架也有可能創(chuàng)建私有的實(shí)現(xiàn)類. 所以只能針對(duì)writeTo的參數(shù)BufferedSink作文章, 先得了解BufferedSink又是如何被okhttp框架調(diào)用的.

BufferedSink相關(guān)的類包括Buffer, Source,都屬于okio框架,okhttp只是基于okio的一坨, okio沒(méi)有直接用java的io操作,而是另行寫(xiě)了一套io操作,具體是數(shù)據(jù)緩沖的操作.接上面的描述, Source是怎么創(chuàng)建, 同時(shí)又是如何操作BufferedSink的? 在Okio.java中:

 public static Source source(File file) throws FileNotFoundException {
  if (file == null) throw new IllegalArgumentException("file == null");
  return source(new FileInputStream(file));
 }
 
 public static Source source(InputStream in) {
  return source(in, new Timeout());
 }

 private static Source source(final InputStream in, final Timeout timeout) {
  return new Source() {
   @Override public long read(Buffer sink, long byteCount) throws IOException {
    try {
     timeout.throwIfReached();
     Segment tail = sink.writableSegment(1);
     int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
     int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
     if (bytesRead == -1) return -1;
     tail.limit += bytesRead;
     sink.size += bytesRead;
     return bytesRead;
    } catch (AssertionError e) {
     if (isAndroidGetsocknameError(e)) throw new IOException(e);
     throw e;
    }
   }

   @Override public void close() throws IOException {
    in.close();
   }

   @Override public Timeout timeout() {
    return timeout;
   }
  };
 }

Source把文件作為輸入流inputstream進(jìn)行了各種讀操作, 但是它的read方法參數(shù)卻是個(gè)Buffer實(shí)例,它又是從哪來(lái)的,又怎么和BufferedSink關(guān)聯(lián)的? 只好再繼續(xù)看BufferedSink.writeAll的實(shí)現(xiàn)體。

BufferedSink的實(shí)現(xiàn)類就是Buffer, 然后它的writeAll方法:

 @Override public long writeAll(Source source) throws IOException {
  if (source == null) throw new IllegalArgumentException("source == null");
  long totalBytesRead = 0;
  for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) {
   totalBytesRead += readCount;
  }
  return totalBytesRead;
 }

原來(lái)是顯式的調(diào)用了Source.read(Buffer,long)方法,這樣就串起來(lái)了,那個(gè)Buffer參數(shù)原來(lái)就是自身。

基本可以確定只要實(shí)現(xiàn)BufferedSink接口類, 然后判斷讀入的內(nèi)容超過(guò)指定大小就停止寫(xiě)入就返回就可滿足目的, 可以名之FixedSizeSink.

然而麻煩的是BufferedSink的接口非常多, 將近30個(gè)方法, 不知道框架會(huì)在什么時(shí)機(jī)調(diào)用哪個(gè)方法,只能全部都實(shí)現(xiàn)! 其次是接口方法的參數(shù)有很多okio的類, 這些類的用法需要了解, 否則一旦用錯(cuò)了效果適得其反. 于是對(duì)一個(gè)類的了解變成對(duì)多個(gè)類的了解, 沒(méi)辦法只能硬著頭皮寫(xiě).

第一個(gè)接口就有點(diǎn)蛋疼: Buffer buffer(); BufferedSink返回一個(gè)Buffer實(shí)例供外部調(diào)用, BufferedSink的實(shí)現(xiàn)體即是Buffer, 然后再返回一個(gè)Buffer?! 看了半天猜測(cè)BufferedSink是為了提供一個(gè)可寫(xiě)入的緩沖對(duì)象, 但框架作者也懶的再搞接口解耦的那一套了(唉,大家都是怎么簡(jiǎn)單怎么來(lái)). 于是FixedSizeSink至少需要持有一個(gè)Buffer對(duì)象, 它作實(shí)際的數(shù)據(jù)緩存,同時(shí)可以在需要Source.read(Buffer ,long)的地方作為參數(shù)傳過(guò)去.

同時(shí)可以看到RequestBody的一個(gè)實(shí)現(xiàn)類FormBody, 用這個(gè)Buffer對(duì)象直接寫(xiě)入一些數(shù)據(jù):

 private long writeOrCountBytes(@Nullable BufferedSink sink, boolean countBytes) {
  long byteCount = 0L;

  Buffer buffer;
  if (countBytes) {
   buffer = new Buffer();
  } else {
   buffer = sink.buffer();
  }

  for (int i = 0, size = encodedNames.size(); i < size; i++) {
   if (i > 0) buffer.writeByte('&');
   buffer.writeUtf8(encodedNames.get(i));
   buffer.writeByte('=');
   buffer.writeUtf8(encodedValues.get(i));
  }

  if (countBytes) {
   byteCount = buffer.size();
   buffer.clear();
  }

  return byteCount;
 }

有這樣的操作就有可能限制不了緩沖區(qū)大小變化!不過(guò)數(shù)據(jù)量應(yīng)該相對(duì)小一些而且這種用法場(chǎng)景相對(duì)少,我們指定的大小應(yīng)該能覆蓋的了這種情況。

接著還有一個(gè)接口BufferedSink write(ByteString byteString), 又得了解ByteString怎么使用, 真是心力交瘁啊...

 @Override public Buffer write(ByteString byteString) {
  byteString.write(this);
  return this;
 }

Buffer實(shí)現(xiàn)體里可以直接調(diào)用ByteString.write(Buffer)因?yàn)槭前L問(wèn),自己實(shí)現(xiàn)的FixedSizeSink聲明在和同一包名package okio;也可以這樣使用,如果是其它包名只能先轉(zhuǎn)成byte[]了, ByteString應(yīng)該不大不然也不能這么搞(沒(méi)有找到ByteString讀取一段數(shù)據(jù)的方法):

  @Override
  public BufferedSink write(@NotNull ByteString byteString) throws IOException {
    byte[] bytes = byteString.toByteArray();
    this.write(bytes);
    return this;
  }

總之就是把這些對(duì)象轉(zhuǎn)成內(nèi)存數(shù)組或者Buffer能夠接受的參數(shù)持有起來(lái)!

重點(diǎn)關(guān)心的writeAll反而相對(duì)好實(shí)現(xiàn)一點(diǎn), 我們連續(xù)讀取指定長(zhǎng)度的內(nèi)容直到內(nèi)容長(zhǎng)度達(dá)到我們的閾值就行.

還有一個(gè)蛋疼的點(diǎn)是各種對(duì)象的read/write數(shù)據(jù)流方向:

Caller.read(Callee)/Caller.write(Callee), 有的是從Caller到Callee, 有的是相反,被一個(gè)小類整的有點(diǎn)頭疼……

最后上完整代碼, 如果發(fā)現(xiàn)什么潛在的問(wèn)題也可以交流下~:

public class FixedSizeSink implements BufferedSink {
  private static final int SEGMENT_SIZE = 4096;
  private final Buffer mBuffer = new Buffer();
  private final int mLimitSize;

  private FixedSizeSink(int size) {
    this.mLimitSize = size;
  }

  @Override
  public Buffer buffer() {
    return mBuffer;
  }

  @Override
  public BufferedSink write(@NotNull ByteString byteString) throws IOException {
    byte[] bytes = byteString.toByteArray();
    this.write(bytes);
    return this;
  }

  @Override
  public BufferedSink write(@NotNull byte[] source) throws IOException {
    this.write(source, 0, source.length);
    return this;
  }

  @Override
  public BufferedSink write(@NotNull byte[] source, int offset,
      int byteCount) throws IOException {
    long available = mLimitSize - mBuffer.size();
    int count = Math.min(byteCount, (int) available);
    android.util.Log.d(TAG, String.format("FixedSizeSink.offset=%d,"
             "count=%d,limit=%d,size=%d",
        offset, byteCount, mLimitSize, mBuffer.size()));
    if (count > 0) {
      mBuffer.write(source, offset, count);
    }
    return this;
  }

  @Override
  public long writeAll(@NotNull Source source) throws IOException {
    this.write(source, mLimitSize);
    return mBuffer.size();
  }

  @Override
  public BufferedSink write(@NotNull Source source, long byteCount) throws IOException {
    final long count = Math.min(byteCount, mLimitSize - mBuffer.size());
    final long BUFFER_SIZE = Math.min(count, SEGMENT_SIZE);
    android.util.Log.d(TAG, String.format("FixedSizeSink.count=%d,limit=%d"
             ",size=%d,segment=%d",
        byteCount, mLimitSize, mBuffer.size(), BUFFER_SIZE));
    long totalBytesRead = 0;
    long readCount;
    while (totalBytesRead < count && (readCount = source.read(mBuffer, BUFFER_SIZE)) != -1) {
      totalBytesRead = readCount;
    }
    return this;
  }

  @Override
  public int write(ByteBuffer src) throws IOException {
    final int available = mLimitSize - (int) mBuffer.size();
    if (available < src.remaining()) {
      byte[] bytes = new byte[available];
      src.get(bytes);
      this.write(bytes);
      return bytes.length;
    } else {
      return mBuffer.write(src);
    }
  }

  @Override
  public void write(@NotNull Buffer source, long byteCount) throws IOException {
    mBuffer.write(source, Math.min(byteCount, mLimitSize - mBuffer.size()));
  }

  @Override
  public BufferedSink writeUtf8(@NotNull String string) throws IOException {
    mBuffer.writeUtf8(string);
    return this;
  }

  @Override
  public BufferedSink writeUtf8(@NotNull String string, int beginIndex, int endIndex)
      throws IOException {
    mBuffer.writeUtf8(string, beginIndex, endIndex);
    return this;
  }

  @Override
  public BufferedSink writeUtf8CodePoint(int codePoint) throws IOException {
    mBuffer.writeUtf8CodePoint(codePoint);
    return this;
  }

  @Override
  public BufferedSink writeString(@NotNull String string,
      @NotNull Charset charset) throws IOException {
    mBuffer.writeString(string, charset);
    return this;
  }

  @Override
  public BufferedSink writeString(@NotNull String string, int beginIndex, int endIndex,
      @NotNull Charset charset) throws IOException {
    mBuffer.writeString(string, beginIndex, endIndex, charset);
    return this;
  }

  @Override
  public BufferedSink writeByte(int b) throws IOException {
    mBuffer.writeByte(b);
    return this;
  }

  @Override
  public BufferedSink writeShort(int s) throws IOException {
    mBuffer.writeShort(s);
    return this;
  }

  @Override
  public BufferedSink writeShortLe(int s) throws IOException {
    mBuffer.writeShortLe(s);
    return this;
  }

  @Override
  public BufferedSink writeInt(int i) throws IOException {
    mBuffer.writeInt(i);
    return this;
  }

  @Override
  public BufferedSink writeIntLe(int i) throws IOException {
    mBuffer.writeIntLe(i);
    return this;
  }

  @Override
  public BufferedSink writeLong(long v) throws IOException {
    mBuffer.writeLong(v);
    return this;
  }

  @Override
  public BufferedSink writeLongLe(long v) throws IOException {
    mBuffer.writeLongLe(v);
    return this;
  }

  @Override
  public BufferedSink writeDecimalLong(long v) throws IOException {
    mBuffer.writeDecimalLong(v);
    return this;
  }

  @Override
  public BufferedSink writeHexadecimalUnsignedLong(long v) throws IOException {
    mBuffer.writeHexadecimalUnsignedLong(v);
    return this;
  }

  @Override
  public void flush() throws IOException {
    mBuffer.flush();
  }

  @Override
  public BufferedSink emit() throws IOException {
    mBuffer.emit();
    return this;
  }

  @Override
  public BufferedSink emitCompleteSegments() throws IOException {
    mBuffer.emitCompleteSegments();
    return this;
  }

  @Override
  public OutputStream outputStream() {
    return mBuffer.outputStream();
  }

  @Override
  public boolean isOpen() {
    return mBuffer.isOpen();
  }

  @Override
  public Timeout timeout() {
    return mBuffer.timeout();
  }

  @Override
  public void close() throws IOException {
    mBuffer.close();
  }
}

看完了這篇文章,相信你對(duì)“如何解決android使用okhttp可能引發(fā)OOM的問(wèn)題”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

分享題目:如何解決android使用okhttp可能引發(fā)OOM的問(wèn)題
轉(zhuǎn)載來(lái)源:http://aaarwkj.com/article22/jejejc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供關(guān)鍵詞優(yōu)化、定制網(wǎng)站、品牌網(wǎng)站設(shè)計(jì)、標(biāo)簽優(yōu)化、網(wǎng)站導(dǎo)航、外貿(mào)網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都app開(kāi)發(fā)公司
加勒比人妻一区二区三区| 麻豆人妻少妇精品毛片| 成年女人毛片免费观看不卡| 亚洲成在人线免费观看| 欧美日韩国产免费电影| 中文字幕一区免费视频| 精品国产综合一区二区三区| 欧美精品中出一区二区三区| 国产一级黄色性生活片| 后入动漫视频在线观看| 四虎在线观看精品一区| 国产成人综合欧美日韩另类| 尹人大香蕉在线视频| 日韩中文字幕在线首页| 国产深夜福利在线观看| 麻豆午夜福利在线播放| 日日躁夜夜躁久久狠狠躁| 一本色道久久88综合日韩| 久久精品色一情一乱一伦| 久久香蕉国产线看观看亚洲| 天天天干夜夜添狠操美女| 日韩国产欧美色资源在线| 亚洲不卡高清一区二区三区| 91伊人激情综合久久| 九九热这里只有免费精品| 亚洲国产精品高清久久| 这里只有精品国产999| 国产精品国产亚洲精品| 高清偷自拍亚洲精品三区| 日韩在线视频这里只有精品| 国产欧美日韩在线高清| 大香蕉一区二区亚洲欧美| 日韩欧美国产精品自拍| 手机蜜臀av在线播放| 欧美日韩另类国产综合| 91白丝视频在线观看| 欧美国产日韩激情在线| 中文精品字幕人妻熟女小妇| 深夜三级福利在线观看| 亚洲综合中文字幕精品| 亚洲国产视频中文字幕|