【移動(dòng)應(yīng)用開發(fā)技術(shù)】如何解決android使用okhttp可能引發(fā)OOM的問題_第1頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】如何解決android使用okhttp可能引發(fā)OOM的問題_第2頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】如何解決android使用okhttp可能引發(fā)OOM的問題_第3頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】如何解決android使用okhttp可能引發(fā)OOM的問題_第4頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】如何解決android使用okhttp可能引發(fā)OOM的問題_第5頁
已閱讀5頁,還剩9頁未讀, 繼續(xù)免費(fèi)閱讀

付費(fèi)下載

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

【移動(dòng)應(yīng)用開發(fā)技術(shù)】如何解決android使用okhttp可能引發(fā)OOM的問題

在下給大家分享一下如何解決android使用okhttp可能引發(fā)OOM的問題,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!遇到一個(gè)問題:需要給所有的請(qǐng)求加簽名校驗(yàn)以防刷接口;傳入請(qǐng)求url及body生成一個(gè)文本串作為一個(gè)header傳給服務(wù)端;已經(jīng)有現(xiàn)成的簽名檢驗(yàn)方法StringdoSignature(Stringurl,byte[]body);當(dāng)前網(wǎng)絡(luò)庫基于com.squareup.okhttp3:okhttp:3.14.2.這很簡單了,當(dāng)然是寫一個(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

ceed(builder.build());

}

}okhttp的ReqeustBody是一個(gè)抽象類,內(nèi)容輸出只有writeTo方法,將內(nèi)容寫入到一個(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.這貌似沒啥問題呀,能造成OOM?是的,要看請(qǐng)求類型,如果是一個(gè)上傳文件的接口呢?如果這個(gè)文件比較大呢?上傳接口有可能會(huì)用到publicstaticRequestBodycreate(final@NullableMediaTypecontentType,finalFilefile)方法,如果是針對(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.所以要如何解決呢?簽名方法又是如何處理的呢?原來這個(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)證,但這容易掛一漏萬,而且增加維護(hù)成本,要簽名方法sdk的人另寫合適的接口等于要他們的命,所以還是得從根本解決.既然簽名方法只讀取前4K內(nèi)容,我們便只將內(nèi)容的前4K部分讀取再轉(zhuǎn)成方法所需的內(nèi)存數(shù)組不就可了?所以我們的目的是:期望RequestBody能夠讀取一部分而不是全部的內(nèi)容.能否繼承RequestBody重寫它的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沒有直接用java的io操作,而是另行寫了一套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í)例,它又是從哪來的,又怎么和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;

}原來是顯式的調(diào)用了Source.read(Buffer,long)方法,這樣就串起來了,那個(gè)Buffer參數(shù)原來就是自身?;究梢源_定只要實(shí)現(xiàn)BufferedSink接口類,然后判斷讀入的內(nèi)容超過指定大小就停止寫入就返回就可滿足目的,可以名之FixedSizeSink.然而麻煩的是BufferedSink的接口非常多,將近30個(gè)方法,不知道框架會(huì)在什么時(shí)機(jī)調(diào)用哪個(gè)方法,只能全部都實(shí)現(xiàn)!其次是接口方法的參數(shù)有很多okio的類,這些類的用法需要了解,否則一旦用錯(cuò)了效果適得其反.于是對(duì)一個(gè)類的了解變成對(duì)多個(gè)類的了解,沒辦法只能硬著頭皮寫.第一個(gè)接口就有點(diǎn)蛋疼:Bufferbuffer();BufferedSink返回一個(gè)Buffer實(shí)例供外部調(diào)用,BufferedSink的實(shí)現(xiàn)體即是Buffer,然后再返回一個(gè)Buffer?!看了半天猜測(cè)BufferedSink是為了提供一個(gè)可寫入的緩沖對(duì)象,但框架作者也懶的再搞接口解耦的那一套了(唉,大家都是怎么簡單怎么來).于是FixedSizeSink至少需要持有一個(gè)Buffer對(duì)象,它作實(shí)際的數(shù)據(jù)緩存,同時(shí)可以在需要Source.read(Buffer,long)的地方作為參數(shù)傳過去.同時(shí)可以看到RequestBody的一個(gè)實(shí)現(xiàn)類FormBody,用這個(gè)Buffer對(duì)象直接寫入一些數(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ū)大小變化!不過數(shù)據(jù)量應(yīng)該相對(duì)小一些而且這種用法場景相對(duì)少,我們指定的大小應(yīng)該能覆蓋的了這種情況。接著還有一個(gè)接口BufferedSinkwrite(ByteStringbyteString),又得了解ByteString怎么使用,真是心力交瘁啊...

@Override

public

Buffer

write(ByteString

byteString)

{

byteString.write(this);

return

this;

}Buffer實(shí)現(xiàn)體里可以直接調(diào)用ByteString.write(Buffer)因?yàn)槭前L問,自己實(shí)現(xiàn)的FixedSizeSink聲明在和同一包名packageokio;也可以這樣使用,如果是其它包名只能先轉(zhuǎn)成byte[]了,ByteString應(yīng)該不大不然也不能這么搞(沒有找到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ù)持有起來!重點(diǎn)關(guān)心的writeAll反而相對(duì)好實(shí)現(xiàn)一點(diǎn),我們連續(xù)讀取指定長度的內(nèi)容直到內(nèi)容長度達(dá)到我們的閾值就行.還有一個(gè)蛋疼的點(diǎn)是各種對(duì)象的read/write數(shù)據(jù)流方向:Caller.read(Callee)/Caller.write(Callee),有的是從Caller到Callee,有的是相反,被一個(gè)小類整的有點(diǎn)頭疼……最后上完整代碼,如果發(fā)現(xià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

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論