在上一篇部落格中主要通過對記憶體的提前申請及Bitmap載入方式,來提高網路訪問效率及本地程式穩定性,取得效率與速度上的一個平衡。近期對網路載入進行了進一步最佳化,先將主要思路及提升點一一列出,期待大家共同討論,提出更最佳化的方案。
首先,是Bitmap具體的網路載入方式及多線程訪問的最佳化。在終端應用中,機器自身的運算速度及網路速度的限制很大程度上限制了終端應用的實現方式,同時終端較高的使用者體驗需求,決定了應用要具有較高的響應速度,較流暢的體驗。這就要求在訪問網路下載圖片資源時,隨著介面的切換,必須要即時中斷前一網路請求,啟動新的網路請求。而我們知道,在終端存取層級已經做到單線程下載單張Bitmap的程度,如何做到即時中斷上一請求,快速響應新請求,就是一個必然要面對的問題。由於在終端訪問中,一般都是單線程訪問單Bitmap,從粒度角度來講不可能再小,也不可能在載入Bitmap後,根據條件判斷是否線程終止。從實現的角度來看有兩種實現方案,從宏觀角度來說直接終止請求線程或者從微觀角度即時終止Bitmap的網路訪問,線程自然結束。我們知道Thread運行後,要想控制其結束,就要使用Thread.interrupt方法,並實現想關的方法,回調該方法,釋放所分配的Bitmap空間,否則必然造成記憶體泄露。雖然比較麻煩,但並不是不可實現。但在正常情況下,為了減少線程啟動、關閉造成的線程損耗,對於網路訪問,一般採用線程池的方式。而JAVA提供的ThreadPoolExecutor,協助我們解決了線程池所要面對的絕大部分問題,但是要想做到及時關閉線程就力不從心了,由於線程池未提供直接操作具體線程的介面,我們不可以直接操作線程,只能通過ThreadPoolExecutor.shutdown方法,被動的調用Thread的interrupt方法,開始的思路是控制ThreadPoolExecutor的請求隊列,及重寫Thread,Runnable,在Thread的interrupt中回調重寫的Runnable中的onInterrupt方法,釋放Bitmap等相關資源。但在實際操作中,卻發現由於線程池的實現策略,不可能回調自己重寫的Runnable的onInterrupt方法,線程池永遠都是將提交的RUNNABLE重新封裝為Task,通過ThreadFactory申請新的Thread(worker)來執行該Task,由於其所遵循標準介面,在Thread不可能直接操作到我們提交的Runnable對象,只有重寫JAVA線程池,這樣實現的代價及成本就有些太高,同時該方案每次都需要終止線程池,這樣線程池所提供的反覆利用的優勢也蕩然無存,果斷放棄該方案,轉而從微觀角度入手,最理想的情況是隨時中斷單張Bitmap的下載,隨著中斷的發生,線程資源及時釋放,快速響應下一網路訪問請求。這就必然要求我們改變Bitmap載入方式,強化控制Bitmap的載入的能力,做到及時中斷、及時響應。之前我們採用的是BitmapFactory.decodeStream(new
URL().openStream());的方式,由於該方式是邊下載邊解碼,效率不高同時可控性不好,主要的思路是將下載與解碼完全分離,由於主要消耗時間的是下載過程,只要下載過程可被中斷,即可。思路清晰後就著手改變Bitmap的載入方式了。下載快取方式,將網狀圖片流首先緩衝到byte數組中,完成下載的過程,在解碼時直接使用BitmapFactory.decodeByteArray()來實現快速的圖片解碼。在下載過程中儲存所下載的網路流在mIs(InputStream)中,需要中斷網路訪問時直接close掉該輸入資料流,由於所有的記憶體空間都在JAVA層分配,所以這部分空間會即時釋放,不會造成記憶體泄露。同時close掉輸入資料流後網路下載立即終止,線程資源釋放,響應下一網路請求。通過這種方式,實現bitmap下載與解碼的分離,提高對耗時操作的可控性。可以大幅提高線程響應速度,減少不必要的損耗。
public static final byte[] input2byte(InputStream inStream) throws IOException { { int inx = 0; synchronized (mSwapStream) { /*擷取緩衝所在序號*/ inx = mCurInx = (++mCurInx %CORE_THREAD_SIZE); mIs[inx] = inStream; } /*減少拷貝次數*/ ByteArrayOutputStream os = new ByteArrayOutputStream(480*800*4+5); int rc = 0; try{ while ((rc = inStream.read(mBuffArr[inx], 0, 1024*4)) > 0) { os.write(mBuffArr[inx], 0, rc); } } catch( IOException io){ io.printStackTrace(); } byte[] in2b = os.toByteArray(); return in2b; } }
上面解決了網路載入即時釋放、快速響應請求的問題,由於終端網路訪問的代價實在太過昂貴,(從訪問速度來說,2G/3G/WIFI速度無法保證,下載速度緩慢,體驗不佳,從上網價格來看,拜可敬的中移動所賜,流量同樣寶貴)這樣在終端網路訪問中就一定要減少網路訪問,同時要保證良好地使用者體驗。如何?這些需求那,以空間換時間是最好的方案了,我們從網路上辛苦下載的圖片用完就釋放是很浪費空間的,即使之前使用軟連結提高記憶體緩衝圖片,實現小圖片快速存取,但記憶體畢竟有限且寶貴,更多的資源還是被不斷重複載入,重複釋放了。而目前的手機,其實SD卡空間非常充足的,與寶貴的網路、記憶體資源比起來,SD卡空間是最經濟實惠的考慮,尤其是我們在終端應用的特性,所訪問的資源相對固定,所以建立網路資源的本機快取成了最具應用價值的考慮,實際操作及效果也同樣驗證了這種方案,直接上代碼。
/** * @param name 正常連結地址 * @return 返回重新導向SDCARD地址 * */ private String getCache(String name){ /*如果快取命中,則返回其Bitmap*/ name = name.replace('/', '1'); name = name.replace(':', '1'); File bitmap = new File(CACHE_DIR+name); if(bitmap.exists()){ return "file://"+bitmap.getAbsolutePath(); } return null; } /*name 直接取其Url地址*/ private void saveBitmap(Bitmap image,String name){ name = name.replace('/', '1'); name = name.replace(':', '1');// name = "1"; File bitmapFile = new File(CACHE_DIR + name); if(!bitmapFile.exists()) { try { bitmapFile.createNewFile(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } FileOutputStream fos; try { fos = new FileOutputStream(bitmapFile); image.compress(Bitmap.CompressFormat.PNG, 100, fos); fos.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
簡單來說就是在SD卡中建立永久二級緩衝,在記憶體緩衝未命中,優先放問二級緩衝,二級緩衝未命中再去網路載入,載入完後即時儲存在SD卡中。由於SD卡具有永久儲存的特性,只要不進行手動刪除,其加速性及網路訪問是永久有效,這樣就不用進行費時費力代價高昂的網路訪問了。
——歡迎轉載,請註明出處http://blog.csdn.net/zyplus——