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在A(yíng)ctivity退出時(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)閉,或者在A(yíng)ctivity銷(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)源框架的使用,若是在A(yíng)ctivity銷(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閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逊脯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡竣贪,警方通過(guò)查閱死者的電腦和手機(jī)军洼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)演怎,“玉大人匕争,你說(shuō)我怎么就攤上這事∫” “怎么了甘桑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)歹叮。 經(jīng)常有香客問(wèn)我跑杭,道長(zhǎng),這世上最難降的妖魔是什么咆耿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任德谅,我火速辦了婚禮,結(jié)果婚禮上萨螺,老公的妹妹穿的比我還像新娘窄做。我一直安慰自己,他們只是感情好慰技,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布椭盏。 她就那樣靜靜地躺著,像睡著了一般惹盼。 火紅的嫁衣襯著肌膚如雪庸汗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天手报,我揣著相機(jī)與錄音蚯舱,去河邊找鬼。 笑死掩蛤,一個(gè)胖子當(dāng)著我的面吹牛枉昏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揍鸟,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼兄裂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼句旱!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起晰奖,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤谈撒,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后匾南,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體啃匿,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年蛆楞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了溯乒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡豹爹,死狀恐怖裆悄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情臂聋,我是刑警寧澤光稼,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站逻住,受9級(jí)特大地震影響钟哥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瞎访,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一腻贰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扒秸,春花似錦播演、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至拾徙,卻和暖如春洲炊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尼啡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工暂衡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人崖瞭。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓狂巢,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親书聚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子唧领,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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

  • 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題藻雌。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講斩个,...
    宇宙只有巴掌大閱讀 2,362評(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,428評(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,634評(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,221評(píng)論 2 7
  • 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏大家都不陌生了腔呜,簡(jiǎn)單粗俗的講,...
    DreamFish閱讀 791評(píng)論 0 5