1. 程式人生 > >GitHub相關android記憶體洩漏

GitHub相關android記憶體洩漏

Activity的洩漏是記憶體洩漏裡面最嚴重的問題,它佔用的記憶體多,影響面廣

a.內部類引用導致Activity的洩漏。(學習未注意知識點:非靜態內部類 和 匿名類 都會潛在的引用它們所屬的外部類。但是,靜態內部類卻不會。)

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

當activity結束(finish)時,裡面的延時訊息在得到處理前,會一直儲存在主執行緒的訊息佇列裡持續10分鐘。而且,由上文可知,這條訊息持有對handler的引用,而handler又持有對其外部類(在這裡,即SampleActivity)的潛在引用。這條引用關係會一直保持直到訊息得到處理,從而,這阻止了SampleActivity被垃圾回收器回收,同時造成應用程式的洩漏。

<<<<<<< HEAD注意,上面程式碼中的Runnable類--非靜態匿名類--同樣持有對其外部類的引用。從而也導致洩漏。

注意,上面程式碼中的Runnable類--非靜態匿名類--同樣持有對其外部類的引用。從而也導致洩漏。

洩漏解決方案

首先,上面已經明確了記憶體洩漏來源:

只要有未處理的訊息,那麼訊息會引用handler,非靜態的handler又會引用外部類,即Activity,導致Activity無法被回收,造成洩漏;

Runnable類屬於非靜態匿名類,同樣會引用外部類。

為了解決遇到的問題,我們要明確一點:靜態內部類不會持有對外部類的引用。所以,我們可以把handler類放在單獨的類檔案中,或者使用靜態內部類便可以避免洩漏。

另外,如果想要在handler內部去呼叫所在的外部類Activity,那麼可以在handler內部使用弱引用的方式指向所在Activity,這樣統一不會導致記憶體洩漏。

對於匿名類Runnable,同樣可以將其設定為靜態類。因為靜態的匿名類不會持有對外部類的引用。

public class SampleActivity extends Activity {

  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;

    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if (activity != null) {
        // ...
      }
    }
  }

  private final MyHandler mHandler = new MyHandler(this);

  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

小結

<<<<<<< HEAD雖然靜態類與非靜態類之間的區別並不大,但是對於Android開發者而言卻是必須理解的。至少我們要清楚,如果一個內部類例項的生命週期比Activity更長,那麼我們千萬不要使用非靜態的內部類。最好的做法是,使用靜態內部類,然後在該類裡使用弱引用來指向所在的Activity。

b.資源物件沒關閉造成的記憶體洩漏

描述:資源性物件比如(Cursor,File檔案等)往往都用了一些緩衝,我們在不使用的時候,應該及時關閉它們,以便它們的緩衝及時回收記憶體。它們的緩衝不僅存在於 java虛擬機器內,還存在於java虛擬機器外。如果我們僅僅是把它的引用設定為null,而不關閉它們,往往會造成記憶體洩漏。因為有些資源性物件,比如 SQLiteCursor(在解構函式finalize(),如果我們沒有關閉它,它自己會調close()關閉),如果我們沒有關閉它,系統在回收它時也會關閉它,但是這樣的效率太低了。因此對於資源性物件在不使用的時候,應該呼叫它的close()函式,將其關閉掉,然後才置為null.在我們的程式退出時一定要確保我們的資源性物件已經關閉。程式中經常會進行查詢資料庫的操作,但是經常會有使用完畢Cursor後沒有關閉的情況。如果我們的查詢結果集比較小,對記憶體的消耗不容易被發現,只有在常時間大量操作的情況下才會復現記憶體問題,這樣就會給以後的測試和問題排查帶來困難和風險。

c.構造Adapter時,沒有使用快取的convertView

描述:以構造ListView的BaseAdapter為例,在BaseAdapter中提供了方法:public View getView(int position, ViewconvertView, 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物件的話,即浪費資源也浪費時間,也會使得記憶體佔用越來越大。 ListView回收list item的view物件的過程可以檢視:android.widget.AbsListView.java --> voidaddScrapView(View scrap) 方法。示例程式碼:

public View getView(int position, ViewconvertView, ViewGroup parent) {
View view = new Xxx(...); 
... ... 
return view; 
} 

修正示例程式碼:

public View getView(int position, ViewconvertView, ViewGroup parent) {
View view = null; 
if (convertView != null) { 
view = convertView; 
populate(view, getItem(position)); 
... 
} else { 
view = new Xxx(...); 
... 
} 
return view; 
} 

d.Bitmap物件不在使用時呼叫recycle()釋放記憶體

描述:有時我們會手工的操作Bitmap物件,如果一個Bitmap物件比較佔記憶體,當它不在被使用的時候,可以呼叫Bitmap.recycle()方法回收此物件的畫素所佔用的記憶體,但這不是必須的,視情況而定。可以看一下程式碼中的註釋:

/** •Free up the memory associated with thisbitmap's pixels, and mark the •bitmap as "dead", meaning itwill throw an exception if getPixels() or •setPixels() is called, and will drawnothing. This operation cannot be •reversed, so it should only be called ifyou are sure there are no •further uses for the bitmap. This is anadvanced call, and normally need •not be called, since the normal GCprocess will free up this memory when •there are no more references to thisbitmap. */ 

e.試著使用關於application的context來替代和activity相關的context

這是一個很隱晦的記憶體洩漏的情況。有一種簡單的方法來避免context相關的記憶體洩漏。最顯著地一個是避免context逃出他自己的範圍之外。使用Application context。這個context的生存週期和你的應用的生存週期一樣長,而不是取決於activity的生存週期。如果你想保持一個長期生存的物件,並且這個物件需要一個context,記得使用application物件。你可以通過呼叫 Context.getApplicationContext() or Activity.getApplication()來獲得。更多的請看這篇文章如何避免Android記憶體洩漏。

f.註冊沒取消造成的記憶體洩漏

一些Android程式可能引用我們的Anroid程式的物件(比如註冊機制)。即使我們的Android程式已經結束了,但是別的引用程式仍然還有對我們的Android程式的某個物件的引用,洩漏的記憶體依然不能被垃圾回收。呼叫registerReceiver後未呼叫unregisterReceiver。比如:假設我們希望在鎖屏介面(LockScreen)中,監聽系統中的電話服務以獲取一些資訊(如訊號強度等),則可以在LockScreen中定義一個 PhoneStateListener的物件,同時將它註冊到TelephonyManager服務中。對於LockScreen物件,當需要顯示鎖屏介面的時候就會建立一個LockScreen物件,而當鎖屏介面消失的時候LockScreen物件就會被釋放掉。但是如果在釋放 LockScreen物件的時候忘記取消我們之前註冊的PhoneStateListener物件,則會導致LockScreen無法被垃圾回收。如果不斷的使鎖屏介面顯示和消失,則最終會由於大量的LockScreen物件沒有辦法被回收而引起OutOfMemory,使得system_process 程序掛掉。雖然有些系統程式,它本身好像是可以自動取消註冊的(當然不及時),但是我們還是應該在我們的程式中明確的取消註冊,程式結束時應該把所有的註冊都取消掉。

g.集合中物件沒清理造成的記憶體洩漏

我們通常把一些物件的引用加入到了集合中,當我們不需要該物件時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。