1. 程式人生 > >Android記憶體優化大全(中)

Android記憶體優化大全(中)

避免使用浮點數

通常的經驗是,在Android裝置中,浮點數會比整型慢兩倍。

使用實體類比介面好

假設你有一個HashMap物件,你可以將它宣告為HashMap或者Map:

Map map1 = new HashMap();
HashMap map2 = new HashMap();

哪個更好呢?

按照傳統的觀點Map會更好些,因為這樣你可以改變他的具體實現類,只要這個類繼承自Map介面。傳統的觀點對於傳統的程式是正確的,但是它並不適合嵌入式系統。呼叫一個介面的引用會比呼叫實體類的引用多花費一倍的時間。如果HashMap完全適合你的程式,那麼使用Map就沒有什麼價值。如果有些地方你不能確定,先避免使用Map,剩下的交給IDE提供的重構功能好了。(當然公共API是一個例外:一個好的API常常會犧牲一些效能)

避免使用列舉

列舉變數非常方便,但不幸的是它會犧牲執行的速度和並大幅增加檔案體積。

使用列舉變數可以讓你的API更出色,並能提供編譯時的檢查。所以在通常的時候你毫無疑問應該為公共API選擇列舉變數。但是當效能方面有所限制的時候,你就應該避免這種做法了。

for迴圈

訪問成員變數比訪問本地變數慢得多,如下面一段程式碼:

[java] view plaincopyprint?
  1. for(int i =0; i < this.mCount; i++)  {}  

永遠不要在for的第二個條件中呼叫任何方法,如下面一段程式碼:

[java] view plain
copyprint?
  1. for(int i =0; i < this.getCount(); i++) {}  

對上面兩個例子最好改為:

[java] view plaincopyprint?
  1. int count = this.mCount; / int count = this.getCount();  
  2. for(int i =0; i < count; i++)  {}  
在java1.5中引入的for-each語法。編譯器會將對陣列的引用和陣列的長度儲存到本地變數中,這對訪問陣列元素非常好。 但是編譯器還會在每次迴圈中產生一個額外的對本地變數的儲存操作(如下面例子中的變數a)
,這樣會比普通迴圈多出4個位元組,速度要稍微慢一些: [java] view plaincopyprint?
  1. for (Foo a : mArray) {  
  2.     sum += a.mSplat;  
  3. }  

瞭解並使用類庫

選擇Library中的程式碼而非自己重寫,除了通常的那些原因外,考慮到系統空閒時會用匯編程式碼呼叫來替代library方法,這可能比JIT中生成的等價的最好的Java程式碼還要好。

當你在處理字串的時候,不要吝惜使用String.indexOf()String.lastIndexOf()等特殊實現的方法。這些方法都是使用C/C++實現的,比起Java迴圈快10到100倍。

System.arraycopy方法在有JIT的Nexus One上,自行編碼的迴圈快9倍。

android.text.format包下的Formatter類,提供了IP地址轉換、檔案大小轉換等方法;DateFormat類,提供了各種時間轉換,都是非常高效的方法。

TextUtils類,對於字串處理Android為我們提供了一個簡單實用的TextUtils類,如果處理比較簡單的內容不用去思考正則表示式不妨試試這個在android.text.TextUtils的類

高效能MemoryFile類,很多人抱怨Android處理底層I/O效能不是很理想,如果不想使用NDK則可以通過MemoryFile類實現高效能的檔案讀寫操作。MemoryFile適用於哪些地方呢?對於I/O需要頻繁操作的,主要是和外部儲存相關的I/O操作,MemoryFile通過將 NAND或SD卡上的檔案,分段對映到記憶體中進行修改處理,這樣就用高速的RAM代替了ROM或SD卡,效能自然提高不少,對於Android手機而言同時還減少了電量消耗。該類實現的功能不是很多,直接從Object上繼承,通過JNI的方式直接在C底層執行。

Reuse:

Reuse重用,減少記憶體消耗的重要手段之一。 核心思路就是將已經存在的記憶體資源重新使用而避免去建立新的,最典型的使用就是快取(Cache池(Pool) Bitmap快取:

Bitmap快取分為兩種:

一種是記憶體快取,一種是硬碟快取。

記憶體快取(LruCache):

以犧牲寶貴的應用記憶體為代價,記憶體快取提供了快速的Bitmap訪問方式。系統提供的LruCache類是非常適合用作快取Bitmap任務的,它將最近被引用到的物件儲存在一個強引用的LinkedHashMap中,並且在快取超過了指定大小之後將最近不常使用的物件釋放掉。

注意以前有一個非常流行的記憶體快取實現是SoftReference(軟引用)或者WeakReference(弱引用)的Bitmap快取方案,然而現在已經不推薦使用了。自Android2.3版本(API Level 9)開始,垃圾回收器更著重於對軟/弱引用的回收,這使得上述的方案相當無效。

硬碟快取(DiskLruCache):

一個記憶體快取對加速訪問最近瀏覽過的Bitmap非常有幫助,但是你不能侷限於記憶體中的可用圖片。GridView這樣有著更大的資料集的元件可以很輕易消耗掉記憶體快取。你的應用有可能在執行其他任務(如打電話)的時候被打斷,並且在後臺的任務有可能被殺死或者快取被釋放。一旦使用者重新聚焦(resume)到你的應用,你得再次處理每一張圖片。

在這種情況下,硬碟快取可以用來儲存Bitmap並在圖片被記憶體快取釋放後減小圖片載入的時間(次數)。當然,從硬碟載入圖片比記憶體要慢,並且應該在後臺執行緒進行,因為硬碟讀取的時間是不可預知的。

注意:如果訪問圖片的次數非常頻繁,那麼ContentProvider可能更適合用來儲存快取圖片,例如Image Gallery這樣的應用程式。

更多關於記憶體快取和硬碟快取的內容請看Google官方教程https://developer.android.com/develop/index.html
圖片快取的開源專案: 對於圖片的快取現在都傾向於使用開源專案,這裡我列出幾個我搜到的:

1. Android-Universal-Image-Loader 圖片快取

目前使用最廣泛的圖片快取,支援主流圖片快取的絕大多數特性。
專案地址:https://github.com/nostra13/Android-Universal-Image-Loader

2. picasso square開源的圖片快取
專案地址:https://github.com/square/picasso
特點:(1)可以自動檢測adapter的重用並取消之前的下載
(2)圖片變換
(3)可以載入本地資源
(4)可以設定佔位資源
(5)支援debug模式

3. ImageCache 圖片快取,包含記憶體和Sdcard快取
專案地址:https://github.com/Trinea/AndroidCommon
特點:

(1)支援預取新圖片,支援等待佇列
(2)包含二級快取,可自定義檔名儲存規則
(3)可選擇多種快取演算法(FIFO、LIFO、LRU、MRU、LFU、MFU等13種)或自定義快取演算法
(4)可方便的儲存及初始化恢復資料
(5)支援不同型別網路處理
(6)可根據系統配置初始化快取等


4. Android 網路通訊框架Volley 專案地址:https://android.googlesource.com/platform/frameworks/volley 我們在程式中需要和網路通訊的時候,大體使用的東西莫過於AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient(Apache)等,在2013年的Google I/O釋出了Volley。Volley是Android平臺上的網路通訊庫,能使網路通訊更快,更簡單,更健壯。 特點: (1)JSON,影象等的非同步下載; (2)網路請求的排序(scheduling) (3)網路請求的優先順序處理 (4)快取 (5)多級別取消請求 (6)和Activity和生命週期的聯動(Activity結束時同時取消所有網路請求) Adapter介面卡 在Android中Adapter使用十分廣泛,特別是在list中。所以adapter是資料的 “集散地” ,所以對其進行記憶體優化是很有必要的。 下面算是一個標準的使用模版: 主要使用convertView和ViewHolder來進行快取處理 [java] view plaincopyprint?
  1. @Override
  2. public View getView(int position, View convertView, ViewGroup parent) {  
  3.     ViewHolder vHolder = null;  
  4.     //如果convertView物件為空則建立新物件,不為空則複用  
  5.     if (convertView == null) {  
  6.         convertView = inflater.inflate(..., null);  
  7.         // 建立 ViewHodler 物件  
  8.         vHolder = new ViewHolder();  
  9.         vHolder.img= (ImageView) convertView.findViewById(...);  
  10.         vHolder.tv= (TextView) convertView.findViewById(...);  
  11.         // 將ViewHodler儲存到Tag中(Tag可以接收Object型別物件,所以任何東西都可以儲存在其中)
  12.         convertView.setTag(vHolder);  
  13.     } else {  
  14.         //當convertView不為空時,通過getTag()得到View  
  15.         vHolder = (ViewHolder) convertView.getTag();  
  16.     }  
  17.     // 給物件賦值,修改顯示的值  
  18.     vHolder.img.setImageBitmap(...);  
  19.     vHolder.tv.setText(...);  
  20.     return convertView;  
  21. }  
  22. //將顯示的View 包裝成類  
  23. staticclass ViewHolder {  
  24.     TextView tv;  
  25.     ImageView img;  
  26. }  

池(PooL)

物件池:

物件池使用的基本思路是:將用過的物件儲存起來,等下一次需要這種物件的時候,再拿出來重複使用,從而在一定程度上減少頻繁建立物件所造成的開銷。 並非所有物件都適合拿來池化――因為維護物件池也要造成一定開銷。對生成時開銷不大的物件進行池化,反而可能會出現“維護物件池的開銷”大於“生成新物件的開銷”,從而使效能降低的情況。但是對於生成時開銷可觀的物件,池化技術就是提高效能的有效策略了。

執行緒池:

執行緒池的基本思想還是一種物件池的思想,開闢一塊記憶體空間,裡面存放了眾多(未死亡)的執行緒,池中執行緒執行排程由池管理器來處理。當有執行緒任務時,從池中取一個,執行完成後執行緒物件歸池,這樣可以避免反覆建立執行緒物件所帶來的效能開銷,節省了系統的資源。

比如:一個應用要和網路打交道,有很多步驟需要訪問網路,為了不阻塞主執行緒,每個步驟都建立個執行緒,線上程中和網路互動,用執行緒池就變的簡單,執行緒池是對執行緒的一種封裝,讓執行緒用起來更加簡便,只需要創一個執行緒池,把這些步驟像任務一樣放進執行緒池,在程式銷燬時只要呼叫執行緒池的銷燬函式即可。

java提供了ExecutorServiceExecutors類,我們可以應用它去建立執行緒池。

通常可以建立如下4種:

[java] view plaincopyprint?
  1. /** 每次只執行一個任務的執行緒池 */
  2. ExecutorService singleTaskExecutor =  Executors.newSingleThreadExecutor();  
  3. /** 每次執行限定個數個任務的執行緒池 */
  4. ExecutorService limitedTaskExecutor = Executors.newFixedThreadPool(3);  
  5. /** 所有任務都一次性開始的執行緒池 */
  6. ExecutorService allTaskExecutor = Executors.newCachedThreadPool();  
  7. /** 建立一個可在指定時間裡執行任務的執行緒池,亦可重複執行 */
  8. ExecutorService scheduledTaskExecutor = Executors.newScheduledThreadPool(3);  
更多關於執行緒池的內容我推薦這篇文章:http://www.xuanyusong.com/archives/2439

注意:

要根據情況適度使用快取,因為記憶體有限。

能儲存路徑地址的就不要存放圖片資料,不經常使用的儘量不要快取,不用時就清空。

寫在最後:

我準備將文章分為上、中、下三部分。

在最後一篇文章中會將剩餘的Recycle,Review總結完。

因為記憶體知識很零散,而且我也是現學現賣,所以為了儘可能的蒐集更多的資料和保證內容的準確性更新速度可能慢點。因為中間還要寫點別的爛七八糟的。

寫這篇文章的目的就是想弄一個大彙總,將零散的記憶體知識點總結一下,如果有錯誤、不足或建議都希望告訴我。

參考文章:

解析Android開發優化之:軟引用與弱引用的應用(http://www.jb51.net/article/36627.htm)
android記憶體洩露優化總結(http://blog.csdn.net/imain/article/details/8560986)
Android 記憶體優化(http://blog.csdn.net/awangyunke/article/details/20380719)
Android開發優化之——對Bitmap的記憶體優化(http://blog.csdn.net/arui319/article/details/7953690)
關於android效能,記憶體優化(http://www.cnblogs.com/zyw-205520/archive/2013/02/17/2914190.html)

Android研究院之應用開發執行緒池的經典使用(http://www.xuanyusong.com/archives/2439)