Android內(nèi)存泄漏例子分析训枢,如何避免泄漏

什么是內(nèi)存泄漏踩寇?

Java內(nèi)存泄漏指的是進(jìn)程中某些對(duì)象(垃圾對(duì)象)已經(jīng)沒有使用價(jià)值了(可以說生命周期結(jié)束了),但是它們卻可以直接或間接地引用到gc root導(dǎo)致無法被GC回收藏古。嚴(yán)重時(shí)會(huì)造成內(nèi)存溢出OOM。

什么是內(nèi)存溢出忍燥?

Android系統(tǒng)為每一個(gè)應(yīng)用程序都設(shè)置了一個(gè)硬性的條件:DalvikHeapSize最大閥值64M/48M/24M.如果你的應(yīng)用程序內(nèi)存占用接近這個(gè)閥值校翔,此時(shí)如果再嘗試內(nèi)存分配的時(shí)候就會(huì)造成OOM。

內(nèi)存泄露的例子分析

一灾前、 靜態(tài)變量引起的內(nèi)存泄漏

在java中靜態(tài)變量的生命周期是在類加載時(shí)開始防症,類卸載時(shí)結(jié)束。換句話說哎甲,在android中其生命周期是在進(jìn)程啟動(dòng)時(shí)開始蔫敲,進(jìn)程死亡時(shí)結(jié)束。所以在程序的運(yùn)行期間炭玫,如果進(jìn)程沒有被殺死奈嘿,靜態(tài)變量就會(huì)一直存在,不會(huì)被回收掉吞加。如果靜態(tài)變量強(qiáng)引用了某個(gè)Activity中變量裙犹,那么這個(gè)Activity就同樣也不會(huì)被釋放,即便是該Activity執(zhí)行了onDestroy(不要將執(zhí)行onDestroy和被回收劃等號(hào))尽狠。這類問題的解決方案為:1.尋找與該靜態(tài)變量生命周期差不多的替代對(duì)象。2.若找不到叶圃,將強(qiáng)引用方式改成弱引用袄膏。比較典型的例子如下:

1. 單例引起的Context內(nèi)存泄漏
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;
    }
}

當(dāng)調(diào)用getInstance時(shí),如果傳入的context是Activity的context掺冠。只要這個(gè)單例沒有被釋放沉馆,這個(gè)Activity也不會(huì)被釋放。

解決方案

傳入Application的context,因?yàn)锳pplication的context的生命周期比Activity長(zhǎng)德崭,可以理解為Application的context與單例的生命周期一樣長(zhǎng)斥黑,傳入它是最合適的。(有一種說法:內(nèi)存泄漏就是生命周期不一致造成的)

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)
                    //將傳入的context轉(zhuǎn)換成Application的context           
                    mInstance = new IMManager(context.getApplicationContext());
            }
        }
        return mInstance;
    }

    private IMManager(Context context) {
        this.context = context;
    }
}

二眉厨、 非靜態(tài)內(nèi)部類引起的內(nèi)存泄漏

內(nèi)部類的優(yōu)勢(shì)之一就是可以訪問外部類锌奴,不幸的是,導(dǎo)致內(nèi)存泄漏的原因憾股,就是內(nèi)部類持有外部類實(shí)例的強(qiáng)引用缨叫。
在java里,非靜態(tài)內(nèi)部類 和 匿名類 都會(huì)潛在的引用它們所屬的外部類荔燎。但是耻姥,靜態(tài)內(nèi)部類卻不會(huì)。如果這個(gè)非靜態(tài)內(nèi)部類實(shí)例做了一些耗時(shí)的操作有咨,就會(huì)造成外圍對(duì)象不會(huì)被回收琐簇,從而導(dǎo)致內(nèi)存泄漏。這類問題的解決方案為:1.將內(nèi)部類變成靜態(tài)內(nèi)部類 2.如果有強(qiáng)引用Activity中的屬性座享,則將該屬性的引用方式改為弱引用婉商。3.在業(yè)務(wù)允許的情況下,當(dāng)Activity執(zhí)行onDestory時(shí)渣叛,結(jié)束這些耗時(shí)任務(wù)丈秩。

1. 內(nèi)部線程造成的內(nèi)存泄漏
public class LeakAty extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aty_leak);
        test();
    }

    public void test() {
        //匿名內(nèi)部類會(huì)引用其外部實(shí)例LeakAty.this,所以會(huì)導(dǎo)致內(nèi)存泄漏,默認(rèn)持有外部Activity實(shí)例
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}
解決方案

將非靜態(tài)匿名內(nèi)部類修改為靜態(tài)匿名內(nèi)部類

public class LeakAty extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aty_leak);
        test();
    }

    //加上static淳衙,變成靜態(tài)匿名內(nèi)部類  (靜態(tài)方法不能引用非靜態(tài)方法)
    public static void test() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

2. Handler引起的內(nèi)存泄漏

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)部類實(shí)例蘑秽,會(huì)引用外圍對(duì)象LeakAty.this,如果該Handler在Activity退出時(shí)依然還有消息需要處理,那么這個(gè)Activity就不會(huì)被回收箫攀。
比如尤其是:mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

解決方案
public class LeakAty extends Activity {
    private TextView tvResult;
    private MyHandler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aty_leak);
        tvResult = (TextView) findViewById(R.id.tvResult);
        handler = new MyHandler(this);
        fetchData();
    }

    //第一步肠牲,將Handler改成靜態(tài)內(nèi)部類。   
    private static class MyHandler extends Handler {
        //第二步靴跛,將需要引用Activity的地方缀雳,改成弱引用。     
        private WeakReference<LeakAty> atyInstance;

        public MyHandler(LeakAty aty) {
            this.atyInstance = new WeakReference<LeakAty>(aty);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            LeakAty aty = atyInstance == null ? null : atyInstance.get();
            //如果Activity被釋放回收了梢睛,則不處理這些消息       
            if (aty == null || aty.isFinishing()) {
                return;
            }
            aty.tvResult.setText("fetch data success");
        }
    }

    private void fetchData() {
        // 獲取數(shù)據(jù)     
        handler.sendEmptyMessage(0);
    }

    @Override
    protected void onDestroy() {
        //第三步肥印,在Activity退出的時(shí)候移除回調(diào)     
        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
    }
}

小結(jié)
雖然靜態(tài)類與非靜態(tài)類之間的區(qū)別并不大识椰,但是對(duì)于Android開發(fā)者而言卻是必須理解的。至少我們要清楚深碱,如果一個(gè)內(nèi)部類實(shí)例的生命周期比Activity更長(zhǎng)腹鹉,那么我們千萬不要使用非靜態(tài)的內(nèi)部類。最好的做法是莹痢,使用靜態(tài)內(nèi)部類,然后在該類里使用弱引用來指向所在的Activity墓赴。

三竞膳、 資源未關(guān)閉引起的內(nèi)存泄漏

當(dāng)使用了BraodcastReceiver、Cursor诫硕、Bitmap坦辟、自定義屬性attr等資源時(shí),當(dāng)不需要使用時(shí)章办,需要及時(shí)釋放掉锉走,若沒有釋放,則會(huì)引起內(nèi)存泄漏

四藕届、 不用的監(jiān)聽未移除

調(diào)用了View.getViewTreeObserver().addOnXXXListener ,而沒有調(diào)用View.getViewTreeObserver().removeXXXListener

五挪蹭、 無限循環(huán)動(dòng)畫

在Activity中播放屬性動(dòng)畫中的一類無限循環(huán)動(dòng)畫,沒有在onDestory中停止動(dòng)畫休偶,Activity會(huì)被動(dòng)畫持有而無法釋放梁厉。

如何避免內(nèi)存泄露:

1) 減小對(duì)象的內(nèi)存占用:
a) 使用更加輕量級(jí)的數(shù)據(jù)結(jié)構(gòu):
考慮適當(dāng)?shù)那闆r下替代HashMap等傳統(tǒng)數(shù)據(jù)結(jié)構(gòu)而使用安卓專門為手機(jī)研發(fā)的數(shù)據(jù)結(jié)構(gòu)類ArrayMap/SparseArray。SparseLongMap/SparseIntMap/SparseBoolMap更加高效踏兜。
HashMap.put(string,Object);Object o = map.get(string);會(huì)導(dǎo)致一些沒必要的自動(dòng)裝箱和拆箱词顾。
b) 適當(dāng)?shù)谋苊庠赼ndroid中使用Enum枚舉,替代使用普通的static常量碱妆。(一般還是提倡多用枚舉---軟件的架構(gòu)設(shè)計(jì)方面肉盹;如果碰到這個(gè)枚舉需要大量使用的時(shí)候就應(yīng)該更加傾向于解決性能問題。)疹尾。
c) 較少Bitmap對(duì)象的內(nèi)存占用上忍。
使用inSampleSize:計(jì)算圖片壓縮比例進(jìn)行圖片壓縮,可以避免大圖加載造成OOM; decodeformat:圖片的解碼格式選擇纳本,ARGB_8888/RGB_565/ARGB_4444/ALPHA_8,還可以使用WebP睡雇。
d) 使用更小的圖片
資源圖片里面,是否存在還可以繼續(xù)壓縮的空間饮醇。
2) 內(nèi)存對(duì)象的重復(fù)利用:
使用對(duì)象池技術(shù)它抱,兩種:1.自己寫;2.利用系統(tǒng)既有的對(duì)象池機(jī)制朴艰。比如LRU(Last Recently Use)算法观蓄。
a) ListView/GridView源碼可以看到重用的情況ConvertView的復(fù)用混移。RecyclerView中Recycler源碼。
b) Bitmap的復(fù)用
Listview等要顯示大量圖片侮穿。需要使用LRU緩存機(jī)制來復(fù)用圖片歌径。
C) 避免在onDraw方法里面執(zhí)行對(duì)象的創(chuàng)建,要復(fù)用亲茅。避免內(nèi)存抖動(dòng)回铛。
D) 常見的java基礎(chǔ)問題---StringBuilder等

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市克锣,隨后出現(xiàn)的幾起案子茵肃,更是在濱河造成了極大的恐慌,老刑警劉巖袭祟,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件验残,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡巾乳,警方通過查閱死者的電腦和手機(jī)您没,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胆绊,“玉大人氨鹏,你說我怎么就攤上這事⊙棺矗” “怎么了喻犁?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)何缓。 經(jīng)常有香客問我肢础,道長(zhǎng),這世上最難降的妖魔是什么碌廓? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任传轰,我火速辦了婚禮,結(jié)果婚禮上谷婆,老公的妹妹穿的比我還像新娘慨蛙。我一直安慰自己,他們只是感情好纪挎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布期贫。 她就那樣靜靜地躺著,像睡著了一般异袄。 火紅的嫁衣襯著肌膚如雪通砍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音封孙,去河邊找鬼迹冤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛虎忌,可吹牛的內(nèi)容都是我干的泡徙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼膜蠢,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼堪藐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起挑围,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤礁竞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后贪惹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苏章,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寂嘉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年奏瞬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泉孩。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡硼端,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出寓搬,到底是詐尸還是另有隱情珍昨,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布句喷,位于F島的核電站镣典,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏唾琼。R本人自食惡果不足惜兄春,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锡溯。 院中可真熱鬧赶舆,春花似錦、人聲如沸祭饭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽倡蝙。三九已至九串,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寺鸥,已是汗流浹背蒸辆。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工征炼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人躬贡。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓谆奥,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拂玻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酸些,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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

  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏...
    _痞子閱讀 1,637評(píng)論 0 8
  • 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題檐蚜。內(nèi)存泄漏大家都不陌生了魄懂,簡(jiǎn)單粗俗的講,...
    宇宙只有巴掌大閱讀 2,363評(píng)論 0 12
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題闯第。內(nèi)存泄漏...
    apkcore閱讀 1,221評(píng)論 2 7
  • 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題市栗。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講咳短,...
    DreamFish閱讀 791評(píng)論 0 5
  • 如今走在街上咙好,總有一些女生擺著架子在街邊行走篡腌!看見身旁有穿著樸素的人走過就用各種語言攻擊!我要告訴這些女生勾效,別以為...
    hcyyy閱讀 235評(píng)論 0 0