对于大多数程序猿(码农)来说,一提到内存优化肯定都是比较头大,我也不例外,但是因为我们这个项目就我一个人做,出问题了也没有大牛解决,所以只能是自己硬着头皮上了。
言归正传,先交代一下事情的原因。楼主是做网盘项目的,从从未接触过分块上传、断点续传(以前一听断点续传也是头大)到勉强把分块上传下载做完,也是经历了好多痛苦的,但是,有一个事情一直是我不愿承认不想面对但却偏偏存在的问题,那就是下载的时候界面非常卡顿!是非常卡,不是一般的卡!小文件还好,感觉不出来,但是一超过100M就会非常卡甚至是ANR。没办法了,虽然我一直想逃避,但是这个总需要解决,只能硬着头皮来了!
简单介绍一下我们业务的一个“复杂”的地方,就是在下载每个数据块的时候,每个数据块之前都有一个自定义的文件头,里面包含了一些这个块的信息,已\r\n结束,所以在下载的时候不能直接每个块的直接下载,需要把这个头过滤掉。
首先我是这么做的,通过httpClient得到HttpEntity,在拷贝一份Entity,然后通过EntityUtils.toString获得数据内容(相信大多数使用HttpClient的都是这么做),然后从内容里截取出文件头,使用拷贝的entity获得输入流,跳过文件头长度再写文件,大致代码如下:
HttpEntity responseEntity = kscResponse.getResponse().getEntity();
HttpEntity responseEntityClone = responseEntity;//为什么拷贝一份?
InputStream in = responseEntityClone.getContent();
String data = EntityUtils.toString(responseEntity);
String customHeader = data.substring(0,data.indexOf("\n")+1);
in.skip(customHeader.length());
//下面从流里写文件
...
这里有一个会导致OOM的问题,就是EntityUtils.toString(responseEntity); 方法,我发现在上传叫大文件的时候会崩溃,我想是这个方法会把所有内容读取出来放到内存里,如果文件较大的话自然会崩溃,所以这个地方改成了自己写的InputStream2String方法,具体代码不写了,很常用,唯一的要点是当读取到一个size的时候(服务器限制的协议头最大长度)就跳出循环不在读取后面的数据,这样内存中最大也就是几k的数据,不会导致OOM了;但是,之前的卡顿情况还是每样解决。
然后开始各种查资料,各种“优化”之后(比如不要在循环里new 对象等),效果依然不理想。打开DDMS工具查看Allocation Tracker查看内存分配情况,发现还是下载的那个地方内存较大,其实我也知道HttpEntity responseEntityClone = responseEntity;拷贝这个地方可能会占用内存,但是也没办法啊,我需要先读取输入流把文件头取出来然后再写文件,但是读取输入流的时候就把responseEntity消耗掉了,后面也写不了文件了。后来突然想到BufferedReader 有一个readline方法,就是每次读取一行,已\r或者\n做标识,大喜之下尝试了一下,结果很令人失望,下载下来的文件和有损坏。。。绝望之下查看了下readline的源码,深受启发!修改了一下InputStream2String的代码,搞定!InputStream2String代码如下:
public static String InputStream2String(InputStream is)
throws KuaipanException {
if (is == null) {
return null;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[BUFFER_SIZE];
int len = 0;
int total = 0;
try {
while ((len = is.read(buf,0,1)) != -1) {
byte ch = (byte)buf[0];
if (ch == '\n') {
break;
}
baos.write(buf, 0, len);
// total+=len;
total++;
// if(total >=MAX_COUNT)
// break;
}
} catch (IOException e) {
throw new KuaipanException(KuaipanException.IO_ERROR_CODE,
"stream2String IOException", e);
}
String result = null;
byte[] byteArray = baos.toByteArray();
int size = MAX_COUNT>byteArray.length?byteArray.length:MAX_COUNT;
result = new String(byteArray,0,size);
return result;
}
先是一个个字符去读取,当读取到\n的时候,就不再读了,这样返回的数据刚好是文件头,后面自己也不需要自己去截取data.substring(0,data.indexOf("\n")+1);文件头了,同样,这个inputsream没有消耗完,下面可以接着写文件了,也不用in.skip(customHeader.length());了
测试了一下,下载的文件完全正确没有错误,而且卡顿现象有了很大的改善!
ps:本次优化就先告一段落,所谓优化,就是一个持续不断的过程,不是一蹴而就的。本次所有比以前有了很大改善,但是还是会感觉到有一点卡顿,慢慢来吧。。
如有错误不对的地方,敬请大牛指正!