版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
【移动应用开发技术】如何解决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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二四年度劳动合同的工资待遇与工作内容4篇
- 《我国自行辩护权存在的问题及完善》
- 公司高管聘用合同标准版范本
- 作书面检讨模板
- 住建部工程项目管理合同
- 2024餐饮连锁加盟终止协议
- 交通安全观后感800字
- 2024版工程质量保证合同范本3篇
- 《完善湖北省旅游管理体制研究》
- 买卖合同判决模板
- 福建厦门廉租房申请条件一览2022(条件+程序+材料)
- (完整PPT)干眼的诊治课件
- 内部术语---大众
- (完整版)居家养老服务项目收费标准一览表
- 加强工程分包管控,提高企业管理水平
- 48个英语音标课件共48张PPT.ppt
- 工业大类由39个变更为41个
- 三年级上册科学素材-复习资料青岛版(六年制)(2019新版)
- LightGuideing导光柱设计指南
- 海康威视枪机摄像机检测报告精编版
- 强化沸腾传热的方法
评论
0/150
提交评论