Android內(nèi)存泄露及解決方法總結(jié)

1. 概述

Java內(nèi)存泄漏指的是進(jìn)程中某些對(duì)象(垃圾對(duì)象)已經(jīng)沒(méi)有使用價(jià)值了,但是它們卻可以直接或間接地引用到gc roots導(dǎo)致無(wú)法被GC回收。無(wú)用的對(duì)象占據(jù)著內(nèi)存空間,使得實(shí)際可使用內(nèi)存變小,形象地說(shuō)法就是內(nèi)存泄漏了。

2. 常見(jiàn)泄露類(lèi)型

2.1. 集合類(lèi)泄露

如果集合類(lèi)僅僅有添加元素,而沒(méi)有相應(yīng)的刪除機(jī)制,會(huì)導(dǎo)致內(nèi)存被占用。當(dāng)將集合中元素置空,但是集合因?yàn)槌钟袑?duì)元素的引用,導(dǎo)致內(nèi)存回收不,而發(fā)生內(nèi)存泄露。解決方法是,可先刪除元素然后置空,或者直接將集合置空。

Android 中常見(jiàn)的集合類(lèi)內(nèi)存泄露有ValueAnimator調(diào)用addUpdateListener,EditText調(diào)用addTextChangedListener()而未注銷(xiāo)監(jiān)聽(tīng)導(dǎo)致內(nèi)存泄露,他們代碼如下


     //ValueAnimator
   public void addUpdateListener(AnimatorUpdateListener listener) {
        if (mUpdateListeners == null) {
            mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
        }
        mUpdateListeners.add(listener);
    }
    
    //EditText
    public void addTextChangedListener(TextWatcher watcher) {
        if (mListeners == null) {
            mListeners = new ArrayList<TextWatcher>();
        }

        mListeners.add(watcher);
    }

2.2 靜態(tài)變量引起的內(nèi)存泄漏

在java中靜態(tài)變量的生命周期是在類(lèi)加載時(shí)開(kāi)始,類(lèi)卸載時(shí)結(jié)束。換句話(huà)說(shuō),在android中其生命周期是在進(jìn)程啟動(dòng)時(shí)開(kāi)始,進(jìn)程死亡時(shí)結(jié)束。所以在程序的運(yùn)行期間,如果進(jìn)程沒(méi)有被殺死,靜態(tài)變量就會(huì)一直存在,不會(huì)被回收掉。如果靜態(tài)變量強(qiáng)引用了某個(gè)Activity中變量,那么這個(gè)Activity就同樣也不會(huì)被釋放,即便是該Activity執(zhí)行了onDestroy(不要將執(zhí)行onDestroy和被回收劃等號(hào))。這類(lèi)問(wèn)題的解決方案為:

  1. 尋找與該靜態(tài)變量生命周期差不多的替代對(duì)象。
  2. 若找不到,將強(qiáng)引用方式改成弱引用

2.2.1 單例引起的Context內(nèi)存泄漏

由于單例的靜態(tài)特性使得其生命周期跟應(yīng)用的生命周期一樣長(zhǎng),所以如果使用不恰當(dāng)?shù)脑?huà),很容易造成內(nèi)存泄漏。如果單例中引用activity類(lèi)型Context而非ApplicationContext,會(huì)導(dǎo)致ondestroy后不能被回收

public class IMManager {
  private Context context;
  private static IMManager mInstance;
 
  public static IMManager getInstance(Context context) {
    if (mInstance == null) {
      synchronized (IMManager.class) {
        if (mInstance == null)
          mInstance = new IMManager(context);
      }
    }
    return mInstance;
  }
 
  private IMManager(Context context) {
    this.context = context;
  }
 
}

可以讓傳入的context 轉(zhuǎn)化為ApplicationContext。

2.2.2 靜態(tài)Activity和View,drawable

靜態(tài)變量Activity和View會(huì)導(dǎo)致內(nèi)存泄漏,在下面這段代碼中對(duì)Activity的Context和TextView設(shè)置為靜態(tài)對(duì)象,從而產(chǎn)生內(nèi)存泄漏。

public class MainActivity extends AppCompatActivity {
 
    private static Context context;
    private static TextView textView;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this;
        textView = new TextView(this);
    }
}

靜態(tài)變量drawable 也類(lèi)似,但android 4.0的Drawable.Java對(duì)setCallback的實(shí)現(xiàn)進(jìn)行了軟引用,避免了內(nèi)存泄露:

  public final void setCallback(Callback cb){

        mCallback = newWeakReference<Callback> (cb);

}

2.2.3 非靜態(tài)內(nèi)部類(lèi)創(chuàng)建靜態(tài)實(shí)例

public class MainActivity extends AppCompatActivity {
    private static TestResource mResource = null;
     @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mManager == null){
            mManager = new TestResource();
        }
            //...
    }
   
   class TestResource {
    //...
   }
}

因?yàn)榉庆o態(tài)內(nèi)部類(lèi)默認(rèn)會(huì)持有外部類(lèi)的引用,而該非靜態(tài)內(nèi)部類(lèi)TestResource又創(chuàng)建了一個(gè)靜態(tài)的實(shí)例,該實(shí)例的生命周期和應(yīng)用的一樣長(zhǎng),這就導(dǎo)致了該靜態(tài)實(shí)例一直會(huì)持有該Activity的引用,導(dǎo)致Activity的內(nèi)存資源不能正常回收。

2.3. 線(xiàn)程造成內(nèi)存泄露

android開(kāi)發(fā)經(jīng)常會(huì)繼承實(shí)現(xiàn)Activity/Fragment/View,此時(shí)如果你使用了匿名類(lèi)/非靜態(tài)內(nèi)部類(lèi),并被異步線(xiàn)程持有了,那要小心了,如果沒(méi)有任何措施這樣一定會(huì)導(dǎo)致泄露

2.3.1 多線(xiàn)程

如果多線(xiàn)程中含有外部activity的引用,當(dāng)activity銷(xiāo)毀后,而多線(xiàn)程任務(wù)還未執(zhí)行完,因?yàn)榫€(xiàn)程持有對(duì)activity的引用而導(dǎo)致activity不能被回收掉

2.3.2 內(nèi)部線(xiàn)程造成內(nèi)存泄露

public class LeakActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        leakFun();
    }
 
    private void leakFun(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

可以將leakFun()加上static 改成靜態(tài)方法,讓匿名內(nèi)部類(lèi)不會(huì)持有LeakActivity.this應(yīng)用。

2.3.1 handler

public class LeakAty extends Activity {
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.aty_leak);
    fetchData();
 
  }
 
  private Handler mHandler = new Handler() {
    public void handleMessage(android.os.Message msg) {
      switch (msg.what) {
      case 0:
        // 刷新數(shù)據(jù)
        break;
      default:
        break;
      }
 
    };
  };
 
  private void fetchData() {
    //獲取數(shù)據(jù)
    mHandler.sendEmptyMessage(0);
  }
}

mHandler 為匿名內(nèi)部類(lèi)實(shí)例,會(huì)引用外圍對(duì)象LeakAty.this,如果該Handler在Activity退出時(shí)依然還有消息需要處理,那么這個(gè)Activity就不會(huì)被回收。

如果當(dāng)Handler為非靜態(tài)內(nèi)部類(lèi)也會(huì)導(dǎo)致內(nèi)存泄露,因?yàn)榉庆o態(tài)內(nèi)部類(lèi)也會(huì)持有外部類(lèi)的引用,萬(wàn)一 Handler 發(fā)送的 Message 尚未被處理,則該 Message 及發(fā)送它的 Handler 對(duì)象將被線(xiàn)程 MessageQueue 一直持有。

2.3.2 AsyncTask

AsyncTask 情況類(lèi)似handler,都有由于非靜態(tài)內(nèi)部類(lèi),或者匿名內(nèi)部類(lèi)持有activity的引用,當(dāng)activity退出時(shí)任務(wù)還未完成繼續(xù)持有對(duì)activity的引用,導(dǎo)致activity不能回收。

2.4. 動(dòng)畫(huà)導(dǎo)致的內(nèi)存泄露

Android在動(dòng)畫(huà)使用過(guò)程如果不注意也會(huì)經(jīng)常導(dǎo)致內(nèi)存泄露。

例如屬性動(dòng)畫(huà)onDestroy中未停止動(dòng)畫(huà),這時(shí)候Activity會(huì)被View所持有,從而導(dǎo)致Activity無(wú)法被釋放。解決方法onDestroy去去調(diào)用objectAnimator.cancel()來(lái)停止動(dòng)畫(huà)。

public class LeakActivity extends AppCompatActivity {

   private TextView textView;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_leak);
       textView = (TextView)findViewById(R.id.text_view);
       ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360);
       objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
       objectAnimator.start();
   }
}

2.5 資源未關(guān)閉或注銷(xiāo)引用內(nèi)存泄露

常見(jiàn)由于資源未關(guān)閉/注銷(xiāo)導(dǎo)致內(nèi)存泄露有

  • BraodcastReceiver
  • ContentObserver
  • File
  • 游標(biāo) Cursor
  • Stream
  • Bitmap
    ...

這些資源應(yīng)該打開(kāi),用完后馬上關(guān)閉,或者在Activity銷(xiāo)毀時(shí)及時(shí)關(guān)閉或者注銷(xiāo),否則這些資源將不會(huì)被回收,造成內(nèi)存泄漏。

2.6. 不良代碼

  • 構(gòu)造 Adapter 時(shí),沒(méi)有使用緩存的 convertView ,每次都在創(chuàng)建新的 converView
  • EventBus,RxJava等一些第三開(kāi)源框架的使用,若是在Activity銷(xiāo)毀之前沒(méi)有進(jìn)行解除訂閱將會(huì)導(dǎo)致內(nèi)存泄漏。
    ···

3. 內(nèi)存泄露解決措施

3.1. 工具方面:

  1. LeakCanary 檢測(cè) Android 的內(nèi)存泄漏
  2. Android Studio 的Monitor監(jiān)測(cè)內(nèi)存使用情況
  3. MAT分析heap的總內(nèi)存占用大小來(lái)初步判斷是否存在泄露

3.2 代碼方面

  1. 引入弱引用
  2. 使用合適的Context,盡量使用ApplicationContext
  3. 在 Activity 的 Destroy 時(shí)或者 Stop 時(shí)應(yīng)該移除消息隊(duì)列 MessageQueue 中的消息。
  4. 盡量避免使用 static成員變量
  5. 使用的資源及時(shí)關(guān)閉、注銷(xiāo)
  6. 避免非靜態(tài)內(nèi)部類(lèi),可改成靜態(tài)內(nèi)部類(lèi)
  7. 避免匿名內(nèi)部類(lèi)

4. 參考:

  1. Android 內(nèi)存泄漏總結(jié)
  2. Android內(nèi)存泄漏終極解決篇(下)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,071評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,409評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,569評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,360評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,895評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,123評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,643評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,559評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,742評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評(píng)論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,981評(píng)論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,363評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,622評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,354評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,707評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講,...
    宇宙只有巴掌大閱讀 2,379評(píng)論 0 12
  • 一、基礎(chǔ)知識(shí) 1、什么是內(nèi)存泄露 java中的內(nèi)存泄露是指一個(gè)無(wú)用對(duì)象持續(xù)占有內(nèi)存或無(wú)用對(duì)象的內(nèi)存得不到及時(shí)的釋放...
    LiveMoment閱讀 4,435評(píng)論 0 20
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏...
    _痞子閱讀 1,645評(píng)論 0 8
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏...
    apkcore閱讀 1,230評(píng)論 2 7
  • 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講,...
    DreamFish閱讀 798評(píng)論 0 5