天天看點

常見記憶體洩漏和優化方式 ......

轉載請注明本文出自大苞米的部落格(http://blog.csdn.net/a396901990),謝謝支援!

寫在最前:

本文的思路主要借鑒了2014年AnDevCon開發者大會的一個演講PPT,加上把網上搜集的各種記憶體零散知識點進行彙總、挑選、簡化後整理而成。

是以我将本文定義為一個工具類的文章,如果你在ANDROID開發中遇到關于記憶體問題,或者馬上要參加面試,或者就是單純的學習或複習一下記憶體相關知識,都歡迎閱讀。(本文最後我會盡量列出所參考的文章)。

OOM:

記憶體洩露可以引發很多的問題:

1.程式卡頓,響應速度慢(記憶體占用高時JVM虛拟機會頻繁觸發GC)

2.莫名消失(當你的程式所占記憶體越大,它在背景的時候就越可能被幹掉。反之記憶體占用越小,在背景存在的時間就越長)

3.直接崩潰(OutOfMemoryError)

ANDROID記憶體面臨的問題:

1.有限的堆記憶體,原始隻有16M

2.記憶體大小消耗等根據裝置,作業系統等級,螢幕尺寸的不同而不同

3.程式不能直接控制

4.支援背景多任務處理(multitasking)

5.運作在虛拟機之上

5R:

本文主要通過如下的5R方法來對ANDROID記憶體進行優化:

1.Reckon(計算)

首先需要知道你的app所消耗記憶體的情況,知己知彼才能百戰不殆

2.Reduce(減少)

消耗更少的資源

3.Reuse(重用)

當第一次使用完以後,盡量給其他的使用

5.Recycle(回收)

回收資源

4.Review(檢查)

回顧檢查你的程式,看看設計或代碼有什麼不合理的地方。

Reckon:

關于記憶體簡介,和Reckon(記憶體計算)的内容請看上一篇文章:ANDROID記憶體優化(大彙總——上)

Reduce :

Reduce的意思就是減少,直接減少記憶體的使用是最有效的優化方式。

下面來看看有哪些方法可以減少記憶體使用:

Bitmap : Bitmap是記憶體消耗大戶,絕大多數的OOM崩潰都是在操作Bitmap時産生的,下面來看看幾個處理圖檔的方法:

圖檔顯示:

我們需要根據需求去加載圖檔的大小。

例如在清單中僅用于預覽時加載縮略圖(thumbnails )。

隻有當使用者點選具體條目想看詳細資訊的時候,這時另啟動一個fragment/activity/對話框等等,去顯示整個圖檔

圖檔大小:

直接使用ImageView顯示bitmap會占用較多資源,特别是圖檔較大的時候,可能導緻崩潰。 

使用BitmapFactory.Options設定inSampleSize, 這樣做可以減少對系統資源的要求。 

屬性值inSampleSize表示縮略圖大小為原始圖檔大小的幾分之一,即如果這個值為2,則取出的縮略圖的寬和高都是原始圖檔的1/2,圖檔大小就為原始大小的1/4。 

[java]

view plain copy print ?

  1. BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();  
  2. bitmapFactoryOptions.inJustDecodeBounds = true;  
  3. bitmapFactoryOptions.inSampleSize = 2;  
  4. // 這裡一定要将其設定回false,因為之前我們将其設定成了true    
  5. // 設定inJustDecodeBounds為true後,decodeFile并不配置設定空間,即,BitmapFactory解碼出來的Bitmap為Null,但可計算出原始圖檔的長度和寬度    
  6. options.inJustDecodeBounds = false;  
  7. Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);  
BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
        bitmapFactoryOptions.inJustDecodeBounds = true;
        bitmapFactoryOptions.inSampleSize = 2;
        // 這裡一定要将其設定回false,因為之前我們将其設定成了true  
        // 設定inJustDecodeBounds為true後,decodeFile并不配置設定空間,即,BitmapFactory解碼出來的Bitmap為Null,但可計算出原始圖檔的長度和寬度  
        options.inJustDecodeBounds = false;
        Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);
           

圖檔像素:

Android中圖檔有四種屬性,分别是:

ALPHA_8:每個像素占用1byte記憶體 

ARGB_4444:每個像素占用2byte記憶體 

ARGB_8888:每個像素占用4byte記憶體 (預設)

RGB_565:每個像素占用2byte記憶體 

Android預設的顔色模式為ARGB_8888,這個顔色模式色彩最細膩,顯示品質最高。但同樣的,占用的記憶體也最大。 是以在對圖檔效果不是特别高的情況下使用RGB_565(565沒有透明度屬性),如下:

[java]

view plain copy print ?

  1. publicstaticBitmapreadBitMap(Contextcontext, intresId) {  
  2.     BitmapFactory.Optionsopt = newBitmapFactory.Options();  
  3.     opt.inPreferredConfig = Bitmap.Config.RGB_565;  
  4.     opt.inPurgeable = true;  
  5.     opt.inInputShareable = true;  
  6.     //擷取資源圖檔   
  7.     InputStreamis = context.getResources().openRawResource(resId);  
  8.     returnBitmapFactory.decodeStream(is, null, opt);  
  9. }  
publicstaticBitmapreadBitMap(Contextcontext, intresId) {
            BitmapFactory.Optionsopt = newBitmapFactory.Options();
            opt.inPreferredConfig = Bitmap.Config.RGB_565;
            opt.inPurgeable = true;
            opt.inInputShareable = true;
            //擷取資源圖檔 
            InputStreamis = context.getResources().openRawResource(resId);
            returnBitmapFactory.decodeStream(is, null, opt);
        }
           

圖檔回收:

使用Bitmap過後,就需要及時的調用Bitmap.recycle()方法來釋放Bitmap占用的記憶體空間,而不要等Android系統來進行釋放。

下面是釋放Bitmap的示例代碼片段。

[java]

view plain copy print ?

  1. // 先判斷是否已經回收  
  2. if(bitmap != null && !bitmap.isRecycled()){  
  3.     // 回收并且置為null  
  4.     bitmap.recycle();  
  5.     bitmap = null;  
  6. }  
  7. System.gc();  
// 先判斷是否已經回收
        if(bitmap != null && !bitmap.isRecycled()){
            // 回收并且置為null
            bitmap.recycle();
            bitmap = null;
        }
        System.gc();
           

捕獲異常:

經過上面這些優化後還會存在報OOM的風險,是以下面需要一道最後的關卡——捕獲OOM異常:

[java]

view plain copy print ?

  1. Bitmap bitmap = null;  
  2. try {  
  3.     // 執行個體化Bitmap  
  4.     bitmap = BitmapFactory.decodeFile(path);  
  5. } catch (OutOfMemoryError e) {  
  6.     // 捕獲OutOfMemoryError,避免直接崩潰  
  7. }  
  8. if (bitmap == null) {  
  9.     // 如果執行個體化失敗 傳回預設的Bitmap對象  
  10.     return defaultBitmapMap;  
  11. }  
Bitmap bitmap = null;
        try {
            // 執行個體化Bitmap
            bitmap = BitmapFactory.decodeFile(path);
        } catch (OutOfMemoryError e) {
            // 捕獲OutOfMemoryError,避免直接崩潰
        }
        if (bitmap == null) {
            // 如果執行個體化失敗 傳回預設的Bitmap對象
            return defaultBitmapMap;
        }
           

修改對象引用類型:

引用類型:

引用分為四種級别,這四種級别由高到低依次為:強引用>軟引用>弱引用>虛引用。

強引用(strong reference)

如:Object object=new Object(),object就是一個強引用了。當記憶體空間不足,Java虛拟機甯願抛出OutOfMemoryError錯誤,使程式異常終止,也不會靠随意回收具有強引用的對象來解決記憶體不足問題。

軟引用(SoftReference)

隻有記憶體不夠時才回收,常用于緩存;當記憶體達到一個閥值,GC就會去回收它;

弱引用(WeakReference)   

弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它 所管轄的記憶體區域的過程中,一旦發現了隻具有弱引用的對象,不管目前記憶體空間足夠與否,都會回收它的記憶體。 

虛引用(PhantomReference)   

“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。  

軟引用和弱引用的應用執行個體:

注意:對于SoftReference(軟引用)或者WeakReference(弱引用)的Bitmap緩存方案,現在已經不推薦使用了。自Android2.3版本(API Level 9)開始,垃圾回收器更着重于對軟/弱引用的回收,是以下面的内容可以選擇忽略。

在Android應用的開發中,為了防止記憶體溢出,在處理一些占用記憶體大而且聲明周期較長的對象時候,可以盡量應用軟引用和弱引用技術。

下面以使用軟引用為例來詳細說明(弱引用的使用方式與軟引用是類似的):

假設我們的應用會用到大量的預設圖檔,而且這些圖檔很多地方會用到。如果每次都去讀取圖檔,由于讀取檔案需要硬體操作,速度較慢,會導緻性能較低。是以我們考慮将圖檔緩存起來,需要的時候直接從記憶體中讀取。但是,由于圖檔占用記憶體空間比較大,緩存很多圖檔需要很多的記憶體,就可能比較容易發生OutOfMemory異常。這時,我們可以考慮使用軟引用技術來避免這個問題發生。

首先定義一個HashMap,儲存軟引用對象。

[java]

view plain copy print ?

  1. private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();  
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
           

再來定義一個方法,儲存Bitmap的軟引用到HashMap。

[java]

view plain copy print ?

  1. public void addBitmapToCache(String path) {  
  2.        // 強引用的Bitmap對象  
  3.        Bitmap bitmap = BitmapFactory.decodeFile(path);  
  4.        // 軟引用的Bitmap對象  
  5.        SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);  
  6.        // 添加該對象到Map中使其緩存  
  7.        imageCache.put(path, softBitmap);  
  8.    }  
public void addBitmapToCache(String path) {
        // 強引用的Bitmap對象
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        // 軟引用的Bitmap對象
        SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
        // 添加該對象到Map中使其緩存
        imageCache.put(path, softBitmap);
    }
           

擷取的時候,可以通過SoftReference的get()方法得到Bitmap對象。

[java]

view plain copy print ?

  1. public Bitmap getBitmapByPath(String path) {  
  2.         // 從緩存中取軟引用的Bitmap對象  
  3.         SoftReference<Bitmap> softBitmap = imageCache.get(path);  
  4.         // 判斷是否存在軟引用  
  5.         if (softBitmap == null) {  
  6.             return null;  
  7.         }  
  8.         // 取出Bitmap對象,如果由于記憶體不足Bitmap被回收,将取得空  
  9.         Bitmap bitmap = softBitmap.get();  
  10.         return bitmap;  
  11.     }  
public Bitmap getBitmapByPath(String path) {
        // 從緩存中取軟引用的Bitmap對象
        SoftReference<Bitmap> softBitmap = imageCache.get(path);
        // 判斷是否存在軟引用
        if (softBitmap == null) {
            return null;
        }
        // 取出Bitmap對象,如果由于記憶體不足Bitmap被回收,将取得空
        Bitmap bitmap = softBitmap.get();
        return bitmap;
    }
           

使用軟引用以後,在OutOfMemory異常發生之前,這些緩存的圖檔資源的記憶體空間可以被釋放掉的,進而避免記憶體達到上限,避免Crash發生。

需要注意的是,在垃圾回收器對這個Java對象回收前,SoftReference類所提供的get方法會傳回Java對象的強引用,一旦垃圾線程回收該Java對象之後,get方法将傳回null。是以在擷取軟引用對象的代碼中,一定要判斷是否為null,以免出現NullPointerException異常導緻應用崩潰。

到底什麼時候使用軟引用,什麼時候使用弱引用呢?

個人認為,如果隻是想避免OutOfMemory異常的發生,則可以使用軟引用。如果對于應用的性能更在意,想盡快回收一些占用記憶體比較大的對象,則可以使用弱引用。

還有就是可以根據對象是否經常使用來判斷。如果該對象可能會經常使用的,就盡量用軟引用。如果該對象不被使用的可能性更大些,就可以用弱引用。

另外,和弱引用功能類似的是WeakHashMap。WeakHashMap對于一個給定的鍵,其映射的存在并不阻止垃圾回收器對該鍵的回收,回收以後,其條目從映射中有效地移除。WeakHashMap使用ReferenceQueue實作的這種機制。

其他小tips:

對常量使用static final修飾符

讓我們來看看這兩段在類前面的聲明:

static int intVal = 42;

static String strVal = “Hello, world!”;

編譯器會生成一個叫做clinit的初始化類的方法,當類第一次被使用的時候這個方法會被執行。方法會将42賦給intVal,然後把一個指向類中常量表 的引用賦給strVal。當以後要用到這些值的時候,會在成員變量表中查找到他們。 下面我們做些改進,使用“final”關鍵字:

static final int intVal = 42;

static final String strVal = “Hello, world!”;

現在,類不再需要clinit方法,因為在成員變量初始化的時候,會将常量直接儲存到類檔案中。用到intVal的代碼被直接替換成42,而使用strVal的會指向一個字元串常量,而不是使用成員變量。

将一個方法或類聲明為final不會帶來性能的提升,但是會幫助編譯器優化代碼。舉例說,如果編譯器知道一個getter方法不會被重載,那麼編譯器會對其采用内聯調用。

你也可以将本地變量聲明為final,同樣,這也不會帶來性能的提升。使用“final”隻能使本地變量看起來更清晰些(但是也有些時候這是必須的,比如在使用匿名内部類的時候)。

靜态方法代替虛拟方法

如果不需要通路某對象的字段,将方法設定為靜态,調用會加速15%到20%。這也是一種好的做法,因為你可以從方法聲明中看出調用該方法不需要更新此對象的狀态。

減少不必要的全局變量

盡量避免static成員變量引用資源耗費過多的執行個體,比如Context

因為Context的引用超過它本身的生命周期,會導緻Context洩漏。是以盡量使用Application這種Context類型。 你可以通過調用Context.getApplicationContext()或 Activity.getApplication()輕松得到Application對象。 

避免建立不必要的對象

最常見的例子就是當你要頻繁操作一個字元串時,使用StringBuffer代替String。

對于所有所有基本類型的組合:int數組比Integer數組好,這也概括了一個基本事實,兩個平行的int數組比 (int,int)對象數組性能要好很多。

總體來說,就是避免建立短命的臨時對象。減少對象的建立就能減少垃圾收集,進而減少對使用者體驗的影響。

避免内部Getters/Setters 在Android中,虛方法調用的代價比直接字段通路高昂許多。通常根據面向對象語言的實踐,在公共接口中使用Getters和Setters是有道理的,但在一個字段經常被通路的類中宜采用直接通路。

避免使用浮點數

通常的經驗是,在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 plain copy print ?

  1. for(int i =0; i < this.mCount; i++)  {}  
for(int i =0; i < this.mCount; i++)  {}
           

永遠不要在for的第二個條件中調用任何方法,如下面一段代碼:

[java]

view plain copy print ?

  1. for(int i =0; i < this.getCount(); i++) {}  
for(int i =0; i < this.getCount(); i++) {}
           

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

[java]

view plain copy print ?

  1. int count = this.mCount; / int count = this.getCount();  
  2. for(int i =0; i < count; i++)  {}  
int count = this.mCount; / int count = this.getCount();
for(int i =0; i < count; i++)  {}
           

在java1.5中引入的for-each文法。編譯器會将對數組的引用和數組的長度儲存到本地變量中,這對通路數組元素非常好。 但是編譯器還會在每次循環中産生一個額外的對本地變量的存儲操作 (如下面例子中的變量a),這樣會比普通循環多出4個位元組,速度要稍微慢一些:

[java]

view plain copy print ?

  1. for (Foo a : mArray) {  
  2.     sum += a.mSplat;  
  3. }  
for (Foo a : mArray) {
    sum += a.mSplat;
}
           

了解并使用類庫

選擇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 plain copy print ?

  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. static class ViewHolder {  
  24.     TextView tv;  
  25.     ImageView img;  
  26. }  
@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder vHolder = null;
        //如果convertView對象為空則建立新對象,不為空則複用  
        if (convertView == null) {
            convertView = inflater.inflate(..., null);
            // 建立 ViewHodler 對象  
            vHolder = new ViewHolder();
            vHolder.img= (ImageView) convertView.findViewById(...);
            vHolder.tv= (TextView) convertView.findViewById(...);
            // 将ViewHodler儲存到Tag中(Tag可以接收Object類型對象,是以任何東西都可以儲存在其中)
            convertView.setTag(vHolder);
        } else {
            //當convertView不為空時,通過getTag()得到View  
            vHolder = (ViewHolder) convertView.getTag();
        }
        // 給對象指派,修改顯示的值  
        vHolder.img.setImageBitmap(...);
        vHolder.tv.setText(...);
        return convertView;
    }
    //将顯示的View 包裝成類  
    static class ViewHolder {
        TextView tv;
        ImageView img;
    }
           

池(PooL)

對象池:

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

線程池:

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

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

java提供了ExecutorService和Executors類,我們可以應用它去建立線程池。

通常可以建立如下4種:

[java]

view plain copy print ?

  1. ExecutorService singleTaskExecutor =  Executors.newSingleThreadExecutor();  
  2. ExecutorService limitedTaskExecutor = Executors.newFixedThreadPool(3);  
  3. ExecutorService allTaskExecutor = Executors.newCachedThreadPool();  
  4. ExecutorService scheduledTaskExecutor = Executors.newScheduledThreadPool(3);  
/** 每次隻執行一個任務的線程池 */
ExecutorService singleTaskExecutor =  Executors.newSingleThreadExecutor();

/** 每次執行限定個數個任務的線程池 */
ExecutorService limitedTaskExecutor = Executors.newFixedThreadPool(3);

/** 所有任務都一次性開始的線程池 */
ExecutorService allTaskExecutor = Executors.newCachedThreadPool();

/** 建立一個可在指定時間裡執行任務的線程池,亦可重複執行 */
ExecutorService scheduledTaskExecutor = Executors.newScheduledThreadPool(3);
           

更多關于線程池的内容我推薦這篇文章: http://www.xuanyusong.com/archives/2439

注意:

要根據情況适度使用緩存,因為記憶體有限。

能儲存路徑位址的就不要存放圖檔資料,不經常使用的盡量不要緩存,不用時就清空。

寫在最後:

我準備将文章分為上、中、下三部分。現在已經全部完成:

記憶體簡介,Recoken(計算)請看:ANDROID記憶體優化(大彙總——上)

Reduce(減少),Reuse(重用) 請看:ANDROID記憶體優化(大彙總——中)

Recycle(回收), Review(檢查) 請看:ANDROID記憶體優化(大彙總——全)

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

參考文章:

解析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)

繼續閱讀