版權(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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 助步器培訓(xùn)課件教學(xué)
- 口腔科醫(yī)生培訓(xùn)
- 口腔洽談師培訓(xùn)
- 口腔放射課件
- 口腔衛(wèi)生課件
- 口腔醫(yī)學(xué)課件
- 口腔前臺(tái)接待禮儀培訓(xùn)
- 口腔修復(fù)基本知識(shí)
- 制作培訓(xùn)策劃書
- 2026年企業(yè)活動(dòng)執(zhí)行部工作計(jì)劃
- (2025年)軍隊(duì)文職考試面試真題及答案
- 新版-八年級(jí)上冊(cè)數(shù)學(xué)期末復(fù)習(xí)計(jì)算題15天沖刺練習(xí)(含答案)
- 2025智慧城市低空應(yīng)用人工智能安全白皮書
- 云南師大附中2026屆高三月考試卷(七)地理
- 通信管道施工質(zhì)量控制方案
- 仁愛科普版(2024)八年級(jí)上冊(cè)英語Unit1~Unit6單元話題作文練習(xí)題(含答案+范文)
- 安徽寧馬投資有限責(zé)任公司2025年招聘派遣制工作人員考試筆試模擬試題及答案解析
- 2024-2025學(xué)年云南省昆明市五華區(qū)高一上學(xué)期期末質(zhì)量監(jiān)測(cè)歷史試題(解析版)
- 建筑坍塌應(yīng)急救援規(guī)程
- 胰腺常見囊性腫瘤的CT診斷
- 房屋尾款交付合同(標(biāo)準(zhǔn)版)
評(píng)論
0/150
提交評(píng)論