listView優化以及內存泄露問題

最經開發app使出現了由於ListView產生的內存泄露問題。咱們知道內存泄露時很是很差的。java

意味着。代碼寫的有點失敗。需要作些優化修改。android

通過此次的教訓,以及在網上找了些資料。總結了一下。關於ListView的優化:面試

listview優化問題:
首先。listview必須嚴格依照convertView及viewHolder格式書寫,這樣可以基本保證數據最優。
數據庫

其次,假設本身定義Item中有涉及到圖片等等的,必定要作圖片優化。bitmap釋放可以不作。緩存

第三。儘可能避免在BaseAdapter中使用static 來定義全局靜態變量,這個影響很是大。static是Java中的一個keyword。app

當用它來修飾成員變量時,那麼該變量就屬於該類,而不是該類的實例。因此用static修飾的變量,它的生命週期是
很是長的。假設用它來引用一些資源耗費過多的實例(比方Context的狀況最多),這時就要儘可能避免使用了..
異步

第四,儘可能避免在ListView適配器中使用線程,因爲線程產生內存泄露的主要緣由在於線程生命週期的不可控制。jvm

最後,假設上述你都作到的話。你的listview已經優化的很是好了。針對你的問題,你的listview控件高度是否
設置爲fill_parent。因爲wrap會致使listview滑動中無限計算自身高度。你的文本載入是否作過線程以及多
次反覆載入的問題處理。你的item中變量是否屢次無限生成新的內存對象等等。
函數



咱們常常在作項目過程當中遇到內存溢出的問題,同一時候面試中關於OOM的問題也常常出現。
這裏。我將前輩們解決Andorid內存溢出的方法又一次整理一番,方便本身之後使用。

1、Android的內存機制

android應用層是由java開發的,android的davlik虛擬機與jvm也相似。僅僅只是它是基於寄存器的。在java中,經過new爲對象分配內存,所有對象在java堆內分配空間。而內存的釋放是由垃圾收集器(GC)來回收的。 Java採用了有向圖的原理。工具

Java將引用關係考慮爲圖的有向邊,有向邊從引用者指向引用對象。線程對象可以做爲有向圖的起始頂點。該圖就是從起始頂點(GC roots)開始的一棵樹,根頂點可以到達的對象都是有效對象。GC不會回收這些對象。假設某個對象 (連通子圖)與這個根頂點不可達(注意,該圖爲有向圖),那麼咱們以爲這個(這些)對象再也不被引用。可以被GC回收。


2、Android的內存溢出緣由

一、內存泄露致使

由於咱們程序的失誤。長期保持某些資源(如Context)的引用,垃圾回收器就沒法回收它,固然該對象佔用的內存就沒法被使用,這就形成內存泄露。



Android 中常見就是Activity被引用在調用finish以後卻沒有釋放,第二次打開activity又又一次建立,這種內存泄露不斷的發生,則會致使內存的溢出。

Android的每個應用程序都會使用一個專有的Dalvik虛擬機實例來執行。它是由Zygote服務進程孵化出來的,也就是說每個應用程序都是在屬於本身的進程中執行的。Android爲不一樣類型的進程分配了不一樣的內存使用上限。假設程序在執行過程當中出現了內存泄漏的而形成應用進程使用的內存超過了這個上限。則會被系統視爲內存泄漏,從而被kill掉。這使得只本身的進程被kill掉,而不會影響其它進程.

二、佔用內存較多的對象

保存了多個耗用內存過大的對象(如Bitmap)或載入單個超大的圖片。形成內存超出限制。

 

3、常見的內存泄漏問題及其解決方式


一、引用沒釋放形成的內存泄露

1.1註冊沒取消形成的內存泄露

 這樣的Android的內存泄露比純Java的內存泄漏還要嚴重,因爲其它一些Android程序可能引用系統的Android程序的對象(比方註冊機制)。即便Android程序已經結束了。但是別的應用程序仍然還有對Android程序的某個對象的引用。泄漏的內存依舊不能被垃圾回收。

1.2集合中對象沒清理形成的內存泄露

咱們一般把一些對象的引用增長到了集合中,當咱們不需要該對象時,並無把它的引用從集合中清理掉,這樣這個集合就會愈來愈大。假設這個集合是static的話,那狀況就更嚴重了。

1.3 static

 static是Java中的一個keyword。當用它來修飾成員變量時,那麼該變量就屬於該類,而不是該類的實例。

    private static ActivitymContext;       //省略

怎樣才幹有效的避免這樣的引用的發生呢?

    第一,應該儘可能避免static成員變量引用資源耗費過多的實例。比方Context。



    第2、Context儘可能使用ApplicationContext,因爲Application的Context的生命週期比較長,引用它不會出現內存泄露的問題。

    看使用的週期是否在activity週期內,假設超出,必須用application;常見的情景包含:AsyncTask。Thread。第三方庫初始化等等。
    還有些情景,僅僅能用activity:比方。對話框,各類View,需要startActivity的等。
    總之,儘量使用Application。



    第3、使用WeakReference取代強引用。

比方可以使用WeakReference<Context>mContextRef;

1.四、線程(內部類的使用)

線程產生內存泄露的主要緣由在於線程生命週期的不可控。假設咱們的線程是Activity的內部類,因此MyThread中保存了Activity的一個引用。當MyThread的run函數沒有結束時。MyThread是不會被銷燬的,所以它所引用的老的Activity也不會被銷燬,所以就出現了內存泄露的問題。



假設非靜態內部類的方法中,有生命週期大於其所在類的。那就有問題了。比方:AsyncTask、Handler。這兩個類都是方便開發人員運行異步任務的,但是,這兩個都跳出了Activity/Fragment的生命週期。


一、
解決方式

    第1、將線程的內部類,改成靜態內部類。

       緣由:

因爲非靜態內部類會本身主動持有一個所屬類的實例,假設所屬類的實例已經結束生命週期。但內部類的方法仍在運行,就會hold其主體(引用)。也就使主體不能被釋放,亦即內存泄露。靜態類編譯後和非內部類是同樣的,有本身獨立的類名。不會悄悄引用所屬類的實例,因此就不easy泄露。

    第2、假設需要引用Acitivity。使用弱引用。

 

二、資源對象沒關閉形成的內存泄露

資源性對象比方(Cursor,File文件等)每每都用了一些緩衝。咱們在不使用的時候,應該及時關閉它們,以便它們的緩衝及時回收內存。而不是等待GC來處理。它們的緩衝不只存在於java虛擬機內,還存在於java虛擬機外。假設咱們不過把它的引用設置爲null,而不關閉它們。每每會形成內存泄露。

因爲有些資源性對象,比方SQLiteCursor(在析構函數finalize(),假設咱們沒有關閉它,它本身會調close()關閉),假設咱們沒有關閉它。系統在回收它時也會關閉它,但是這種效率過低了。

而且android數據庫中對Cursor資源的是又限制個數的,假設不及時close掉,會致使別的地方沒法得到。

三、一些不良代碼成內存壓力

有些代碼並不形成內存泄露。但是它們,或是對沒使用的內存沒進行有效及時的釋放,或是沒有有效的利用已有的對象而是頻繁的申請新內存,對內存的回收和分配形成很是大影響的,easy迫使虛擬機不得不給該應用進程分配不少其它的內存,形成沒必要要的內存開支。

3.1 Bitmap沒調用recycle()

Bitmap對象在不使用時,咱們應該先調用recycle()釋放內存,而後才設置爲null.

儘管recycle()從源代碼上看,調用它應該能立刻釋放Bitmap的主要內存。但是測試結果顯示它並沒能立刻釋放內存。但是我猜應該仍是能大大的加速Bitmap的主要內存的釋放。

3.2 構造Adapter時。沒有使用緩存的 convertView

 以構造ListView的BaseAdapter爲例,在BaseAdapter中提共了方法:

public View getView(int position, View convertView, ViewGroup parent)

來向ListView提供每一個item所需要的view對象。

初始時ListView會從BaseAdapter中依據當前的屏幕布局實例化必定數量的view對象,同一時候ListView會將這些view對象緩存起來。

當向上滾動ListView時,原先位於最上面的list item的view對象會被回收,而後被用來構造新出現的最如下的list item。這個構造過程就是由getView()方法完畢的,getView()的第二個形參 View convertView就是被緩存起來的list item的view對象(初始化時緩存中沒有view對象則convertView是null)。



    由此可以看出。假設咱們不去使用convertView,而是每次都在getView()中又一次實例化一個View對象的話,即浪費時間,也形成內存垃圾,給垃圾回收添加壓力。假設垃圾回收來不及的話。虛擬機將不得不給該應用進程分配不少其它的內存。形成沒必要要的內存開支。

 

4、佔用內存較多的對象(圖片過大)形成內存溢出及其解決方式

因爲Bitmap佔用的內存實在是太多了。特別是分辨率大的圖片。假設要顯示多張那問題就更顯著了。

Android分配給Bitmap的大小僅僅有8M。

方法1:等比例縮小圖片

有時候。咱們要顯示的區域很是小,沒有必要將整個圖片都載入出來,而僅僅需要記載一個縮小過的圖片,這時候可以設置必定的採樣率。那麼就可以大大減少佔用的內存。

1
    BitmapFactory.Options options = new BitmapFactory.Options();
2
    options.inSampleSize = 2;//圖片寬高都爲原來的二分之中的一個,即圖片爲原來的四分之中的一個
儘可能不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource來設置一張大圖,
因爲這些函數在完畢decode後,終於都是經過java層的createBitmap來完畢的,需要消耗不少其它內存。



所以,改用先經過BitmapFactory.decodeStream方法,建立出一個bitmap,再將其設爲ImageView的 source,
decodeStream最大的祕密在於其直接調用JNI>>nativeDecodeAsset()來完畢decode,
無需再使用java層的createBitmap,從而節省了java層的空間。



方法2:對圖片採用軟引用。及時地進行recycle()操做

儘管,系統能夠確認Bitmap分配的內存終於會被銷燬,但是由於它佔用的內存過多,因此很是可能會超過java堆的限制。所以。在用完Bitmap時,要及時的recycle掉。recycle並不能肯定立刻就會將Bitmap釋放掉,但是會給虛擬機一個暗示:「該圖片能夠釋放了」。

    SoftReference<Bitmap> bitmap;
                   bitmap = new SoftReference<Bitmap>(pBitmap);
      if(bitmap != null){
              if(bitmap.get() != null && !bitmap.get().isRecycled()){
                  bitmap.get().recycle();
                  bitmap = null;
              }
          }


方法3: 單個頁面。橫豎屏切換N次後 OOM

1. 看看頁面佈局其中有沒有大的圖片。比方背景圖之類的。

去除xml中相關設置,改在程序中設置背景圖(放在onCreate()方法中):

1
    Drawable bg = getResources().getDrawable(R.drawable.bg);
2
     XXX.setBackgroundDrawable(rlAdDetailone_bg);


在Activity destory時注意,bg.setCallback(null); 防止Activity得不到及時的釋放。



2. 跟上面方法類似。直接把xml配置文件載入成view 再放到一個容器裏。而後直接調用 this.setContentView(View view);避免xml的反覆載入。

 

方法4:在頁面切換時儘量少地反覆使用一些代碼。比方:反覆調用數據庫,反覆使用某些對象等等.....

 

方法5:Android堆內存也可以自定義大小和優化Dalvik虛擬機的內存

--參考資料:http://blog.csdn.net/wenhaiyan/article/details/5519567

    注意:若使用這樣的方法:project build target 僅僅能選擇 <= 2.2 版本號。不然編譯將通只是。

因此不建議用這樣的方式。

5、Android中內存泄露監測

內存監測工具 DDMS --> Heap


用法比較簡單:

·        選擇DDMS視圖,並打開Devices視圖和Heap視圖

·        點擊選擇要監控的進程,比方:上圖中我選擇的是system_process

·        選中Devices視圖界面上的"update heap" 圖標

·        點擊Heap視圖中的"Cause GC" button(至關於向虛擬機發送了一次GC請求的操做)

在Heap視圖中選擇想要監控的Type。通常咱們會觀察dataobject的 total size的變化。正常狀況下total size的值會穩定在一個有限的範圍內。也就說程序中的代碼良好,沒有形成程序中的對象不被回收的狀況。

假設代碼中存在沒有釋放對象引用的狀況。那麼data object的total size在每次GC以後都不會有明顯的回落。隨着操做次數的添加而total size也在不斷的添加。(說明:選擇好data object後。不斷的操做應用,這樣才幹夠看出total size的變化)。假設totalsize確實是在不斷添加而沒有回落。說明程序中有沒有被釋放的資源引用。那麼咱們應該怎麼來定位呢? Android中內存泄露定位 經過DDMS工具可以推斷應用程序中是否存在內存泄漏的問題。那又怎樣定位到詳細出現故障的代碼片斷,終於找到問題所在呢?內存分析工具MAT Memory Analyzer Tool攻克了這一難題。MAT工具是一個Eclipse 插件,同一時候也有單獨的RCP client,MAT工具的解析文件是.hprof,這個文件存放了某進程的內存快照。MAT工具定位內存泄漏詳細位置的方法例如如下:    ① 生成.hprof文件。Eclipse中生成.hprof文件的方法有很是多。不一樣Android版本號中生成.hprof的方式也稍有區別,但它們整體思路是同樣的。咱們在DDMS界面選中想要分析的應用進程,在Devices視圖界面上方的一行圖標button中,同一時候選中「Update Heap」和「Dump HPROF file」兩個button,這時DDMS將會本身主動生成當前選中進程的.hprof文件。    ② 將.hprof 文件導入到MAT工具中,MAT工具會本身主動解析並生成報告,點擊「Dominator Tree」button。並按包分組,選擇已定義的包類點右鍵。在彈出的菜單中選擇List objects﹥With incoming references,這時會列出所有可疑的類。右鍵點擊某一項,並選擇Path to GC Roots﹥excludeweak/soft references。MAT工具會進一步篩選出跟程序相關的所有內存泄漏的類。這樣就可以追蹤到某一個產生內存泄漏的類的詳細代碼中。    使用MAT內存分析工具查找內存泄漏的根本思路是找到哪一個類的對象的引用沒有被釋放,而後分析沒有被釋放的緣由,終於定位到代碼中哪些片斷存在着內存泄漏。

相關文章
相關標籤/搜索