【移动应用开发技术】如何解决android使用okhttp可能引发OOM的问题_第1页
【移动应用开发技术】如何解决android使用okhttp可能引发OOM的问题_第2页
【移动应用开发技术】如何解决android使用okhttp可能引发OOM的问题_第3页
【移动应用开发技术】如何解决android使用okhttp可能引发OOM的问题_第4页
【移动应用开发技术】如何解决android使用okhttp可能引发OOM的问题_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

【移动应用开发技术】如何解决android使用okhttp可能引发OOM的问题

在下给大家分享一下如何解决android使用okhttp可能引发OOM的问题,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!遇到一个问题:需要给所有的请求加签名校验以防刷接口;传入请求url及body生成一个文本串作为一个header传给服务端;已经有现成的签名检验方法StringdoSignature(Stringurl,byte[]body);当前网络库基于com.squareup.okhttp3:okhttp:3.14.2.这很简单了,当然是写一个interceptor然后将request对象的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是一个抽象类,内容输出只有writeTo方法,将内容写入到一个BufferedSink接口实现体里,然后再将数据转成byte[]也就是内存数组.能达到目的的类只有Buffer,它实现了BufferedSink接口并能提供转成内存数组的方法readByteArray.这貌似没啥问题呀,能造成OOM?是的,要看请求类型,如果是一个上传文件的接口呢?如果这个文件比较大呢?上传接口有可能会用到publicstaticRequestBodycreate(final@NullableMediaTypecontentType,finalFilefile)方法,如果是针对文件的实现体它的writeTo方法是sink.writeAll(source);而我们传给签名方法时用到的Buffer.readByteArray是将缓冲中的所有内容转成了内存数组,这意味着文件中的所有内容被转成了内存数组,就是在这个时机容易造成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);

}

}

};

}可以看到实现体持有了文件,Content-Length返回了文件的大小,内容全部转给了Source对象。这确实是以前非常容易忽略的一个点,很少有对请求体作额外处理的操作,而一旦这个操作变成一次性的大内存分配,非常容易造成OOM.所以要如何解决呢?签名方法又是如何处理的呢?原来这个签名方法在这里偷了个懒——它只读取传入body的前4K内容,然后只针对这部分内容进行了加密,至于传入的这个内存数组本身多大并不考虑,完全把风险和麻烦丢给了外部(优秀的SDK!).快速的方法当然是罗列白名单,针对上传接口服务端不进行加签验证,但这容易挂一漏万,而且增加维护成本,要签名方法sdk的人另写合适的接口等于要他们的命,所以还是得从根本解决.既然签名方法只读取前4K内容,我们便只将内容的前4K部分读取再转成方法所需的内存数组不就可了?所以我们的目的是:期望RequestBody能够读取一部分而不是全部的内容.能否继承RequestBody重写它的writeTo?可以,但不现实,不可能全部替代现有的RequestBody实现类,同时ok框架也有可能创建私有的实现类.所以只能针对writeTo的参数BufferedSink作文章,先得了解BufferedSink又是如何被okhttp框架调用的.BufferedSink相关的类包括Buffer,Source,都属于okio框架,okhttp只是基于okio的一坨,okio没有直接用java的io操作,而是另行写了一套io操作,具体是数据缓冲的操作.接上面的描述,Source是怎么创建,同时又是如何操作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进行了各种读操作,但是它的read方法参数却是个Buffer实例,它又是从哪来的,又怎么和BufferedSink关联的?只好再继续看BufferedSink.writeAll的实现体。BufferedSink的实现类就是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;

}原来是显式的调用了Source.read(Buffer,long)方法,这样就串起来了,那个Buffer参数原来就是自身。基本可以确定只要实现BufferedSink接口类,然后判断读入的内容超过指定大小就停止写入就返回就可满足目的,可以名之FixedSizeSink.然而麻烦的是BufferedSink的接口非常多,将近30个方法,不知道框架会在什么时机调用哪个方法,只能全部都实现!其次是接口方法的参数有很多okio的类,这些类的用法需要了解,否则一旦用错了效果适得其反.于是对一个类的了解变成对多个类的了解,没办法只能硬着头皮写.第一个接口就有点蛋疼:Bufferbuffer();BufferedSink返回一个Buffer实例供外部调用,BufferedSink的实现体即是Buffer,然后再返回一个Buffer?!看了半天猜测BufferedSink是为了提供一个可写入的缓冲对象,但框架作者也懒的再搞接口解耦的那一套了(唉,大家都是怎么简单怎么来).于是FixedSizeSink至少需要持有一个Buffer对象,它作实际的数据缓存,同时可以在需要Source.read(Buffer,long)的地方作为参数传过去.同时可以看到RequestBody的一个实现类FormBody,用这个Buffer对象直接写入一些数据:

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;

}有这样的操作就有可能限制不了缓冲区大小变化!不过数据量应该相对小一些而且这种用法场景相对少,我们指定的大小应该能覆盖的了这种情况。接着还有一个接口BufferedSinkwrite(ByteStringbyteString),又得了解ByteString怎么使用,真是心力交瘁啊...

@Override

public

Buffer

write(ByteString

byteString)

{

byteString.write(this);

return

this;

}Buffer实现体里可以直接调用ByteString.write(Buffer)因为是包名访问,自己实现的FixedSizeSink声明在和同一包名packageokio;也可以这样使用,如果是其它包名只能先转成byte[]了,ByteString应该不大不然也不能这么搞(没有找到ByteString读取一段数据的方法):

@Override

public

BufferedSink

write(@NotNull

ByteString

byteString)

throws

IOException

{

byte[]

bytes

=

byteString.toByteArray();

this.write(bytes);

return

this;

}总之就是把这些对象转成内存数组或者Buffer能够接受的参数持有起来!重点关心的writeAll反而相对好实现一点,我们连续读取指定长度的内容直到内容长度达到我们的阈值就行.还有一个蛋疼的点是各种对象的read/write数据流方向:Caller.read(Callee)/Caller.write(Callee),有的是从Caller到Callee,有的是相反,被一个小类整的有点头疼……最后上完整代码,如果发现什么潜在的问题也可以交流下~: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等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论