記一次Android下載過程的內存優化

        對於大多數程序猿(碼農)來說,一提到內存優化肯定都是比較頭大,我也不例外,但是因爲我們這個項目就我一個人做,出問題了也沒有大牛解決,所以只能是自己硬着頭皮上了。

       言歸正傳,先交代一下事情的原因。樓主是做網盤項目的,從從未接觸過分塊上傳、斷點續傳(以前一聽斷點續傳也是頭大)到勉強把分塊上傳下載做完,也是經歷了好多痛苦的,但是,有一個事情一直是我不願承認不想面對但卻偏偏存在的問題,那就是下載的時候界面非常卡頓!是非常卡,不是一般的卡!小文件還好,感覺不出來,但是一超過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:本次優化就先告一段落,所謂優化,就是一個持續不斷的過程,不是一蹴而就的。本次所有比以前有了很大改善,但是還是會感覺到有一點卡頓,慢慢來吧。。

如有錯誤不對的地方,敬請大牛指正!

 

  

 

發佈了60 篇原創文章 · 獲贊 7 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章