Android 性能優(yōu)化之內(nèi)存泄露

什么是內(nèi)存泄露

內(nèi)存泄露:程序在向系統(tǒng)申請(qǐng)分配內(nèi)存空間后(new)鸵贬,在使用完畢后未釋放。通俗講脐区,沒有用的對(duì)象一直無法回收的現(xiàn)象就是內(nèi)存泄露绳姨,值得注意的是登澜,我們App隨著內(nèi)存泄露的不斷積累,最終會(huì)導(dǎo)致內(nèi)存溢出OOM(Out Of Memory)飘庄,更嚴(yán)重的導(dǎo)致程序崩潰
內(nèi)存溢出:程序向系統(tǒng)申請(qǐng)的內(nèi)存空間超出了系統(tǒng)能給的脑蠕,比如內(nèi)存只能分配一個(gè)int類型,我卻要塞給他一個(gè)long類型跪削,系統(tǒng)就出現(xiàn)oom谴仙。

在Android中造成內(nèi)存泄露的原因

  • 1 單例
public class TestManager
{
    private  static TestManager mTestManager;
    private static Context mContext;
    private TestManager(Context context){
        this.mContext=context;
    }

    public static  TestManager getmTestManager(Context context){
        if(mTestManager !=null){
            synchronized (TestManager.class){
                if(mTestManager !=null){
                    mTestManager=new TestManager(context);
                }
            }
        }
        return  mTestManager;
    }
}

然后在MainActivity中使用

  private void initData()
    {
         TestManager testManager = TestManager.getmTestManager(this);
    }

需要外部傳入一個(gè) Context 來獲取該類的實(shí)例,如果此時(shí)傳入的 Context 是 Activity 的話切揭,此時(shí)單例就有持有該 Activity 的強(qiáng)引用(直到整個(gè)應(yīng)用生命周期結(jié)束)狞甚。這樣的話,即使該 Activity 退出廓旬,該 Activity 的內(nèi)存也不會(huì)被回收哼审,這樣就造了內(nèi)存泄露,

解決辦法——單例模式引用的對(duì)象的生命周期 = 應(yīng)用生命周期孕豹。將 context.getApplicationContext() 賦值給 mContext涩盾,此時(shí)單例引用的對(duì)象是 Application,而 Application 的生命周期本來就跟應(yīng)用程序是一樣的励背,也就不存在內(nèi)存泄露春霍。

public class TestManager
{

    private  static TestManager mTestManager;
    private static Context mContext;

    private TestManager(Context context){

        this.mContext=context.getApplicationContext();

    }


    public static  TestManager getmTestManager(Context context){
        if(mTestManager !=null){
            synchronized (TestManager.class){
                if(mTestManager !=null){
                    mTestManager=new TestManager(context);
                }
            }
        }
        return  mTestManager;
    }
}
  • 2.匿名內(nèi)部類/非靜態(tài)內(nèi)部類(匿名類和非靜態(tài)內(nèi)部類最大的共同點(diǎn)就是 都持有外部類的引用)

匿名內(nèi)部類叶眉,在Java當(dāng)中址儒,非靜態(tài)內(nèi)部類默認(rèn)將會(huì)有持有外部類的引用,當(dāng)在內(nèi)部類實(shí)例化一個(gè)靜態(tài)的對(duì)象衅疙,那么莲趣,這個(gè)對(duì)象將會(huì)與App的生命周期一樣長(zhǎng),又因?yàn)榉庆o態(tài)內(nèi)部類一直持有外部的MainActivity的引用饱溢,導(dǎo)致MainActivity無法被回收喧伞,內(nèi)存泄露的代碼如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyAscnyTask().execute();
    }

    class MyAscnyTask extends AsyncTask<Void, Integer, String>{
        @Override
        protected String doInBackground(Void... params) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "";
        }
    }
}

可以看到我們?cè)?Activity 中繼承 AsyncTask 自定義了一個(gè)非靜態(tài)內(nèi)部類,在 doInbackground() 方法中做了耗時(shí)的操作,然后在 onCreate() 中啟動(dòng) MyAsyncTask潘鲫。如果在耗時(shí)操作結(jié)束之前翁逞,Activity 被銷毀了,這時(shí)候因?yàn)?MyAsyncTask 持有 Activity 的強(qiáng)引用溉仑,便會(huì)導(dǎo)致 Activity 的內(nèi)存無法被回收挖函,這時(shí)候便會(huì)產(chǎn)生內(nèi)存泄露。

  • 解決辦法 :將 MyAsyncTask 變成靜態(tài)內(nèi)部類
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyAscnyTask().execute();
    }

    static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
        @Override
        protected String doInBackground(Void... params) {
            try {
                Thread.sleep(50000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "";
        }
    }
}

匿名類和非靜態(tài)內(nèi)部類最大的共同點(diǎn)就是 都持有外部類的引用彼念,因此挪圾,匿名類造成內(nèi)存泄露的原因也跟靜態(tài)內(nèi)部類基本是一樣的浅萧,下面舉個(gè)幾個(gè)比較常見的例子:

public class MainActivity extends AppCompatActivity {

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // ① 匿名線程持有 Activity 的引用逐沙,進(jìn)行耗時(shí)操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(50000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        // ② 使用匿名 Handler 發(fā)送耗時(shí)消息
        Message message = Message.obtain();
        mHandler.sendMessageDelayed(message, 60000);
    }

上面舉出了兩個(gè)比較常見的例子:

  • new 出一個(gè)匿名的 Thread,進(jìn)行耗時(shí)的操作洼畅,如果 MainActivity 被銷毀而 Thread 中的耗時(shí)操作沒有結(jié)束的話吩案,便會(huì)產(chǎn)生內(nèi)存泄露
  • new 出一個(gè)匿名的 Handler,這里我采用了 sendMessageDelayed() 方法來發(fā)送消息帝簇,這時(shí)如果 MainActivity 被銷毀徘郭,而 Handler 里面的消息還沒發(fā)送完畢的話,Activity 的內(nèi)存也不會(huì)被回收

解決辦法:

  • 繼承 Thread 實(shí)現(xiàn)靜態(tài)內(nèi)部類
  • 繼承 Handler 實(shí)現(xiàn)靜態(tài)內(nèi)部類丧肴,以及在 Activity 的 onDestroy() 方法中残揉,移除所有的消息 mHandler.removeCallbacksAndMessages(null);
private MyHandler mHandler = new MyHandler(this);

private static class MyHandler extends Handler{
    private WeakReference<Context> reference;
    public MyHandler(Context context){
        reference = new WeakReference<Context>(context);
    }

    @Override
    public void handleMessage(Message msg) {
        MainActivity mainActivity = (MainActivity) reference.get();
        if(mainActivity != null){
            //業(yè)務(wù)處理邏輯
        }
    }
}

3.其他情況

除了上述 3 種常見情況外,還有其他的一些情況

1芋浮、需要手動(dòng)關(guān)閉的對(duì)象沒有關(guān)閉

網(wǎng)絡(luò)抱环、文件等流忘記關(guān)閉
手動(dòng)注冊(cè)廣播時(shí),退出時(shí)忘記 unregisterReceiver()
Service 執(zhí)行完后忘記 stopSelf()
EventBus 等觀察者模式的框架忘記手動(dòng)解除注冊(cè)

2纸巷、static 關(guān)鍵字修飾的成員變量
3镇草、ListView 的 Item 泄露

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瘤旨,隨后出現(xiàn)的幾起案子梯啤,更是在濱河造成了極大的恐慌,老刑警劉巖存哲,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件因宇,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡祟偷,警方通過查閱死者的電腦和手機(jī)察滑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肩袍,“玉大人杭棵,你說我怎么就攤上這事。” “怎么了魂爪?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵先舷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我滓侍,道長(zhǎng)蒋川,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任撩笆,我火速辦了婚禮捺球,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘夕冲。我一直安慰自己氮兵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布歹鱼。 她就那樣靜靜地躺著泣栈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弥姻。 梳的紋絲不亂的頭發(fā)上南片,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音庭敦,去河邊找鬼疼进。 笑死,一個(gè)胖子當(dāng)著我的面吹牛秧廉,可吹牛的內(nèi)容都是我干的伞广。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼定血,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼赔癌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起澜沟,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤灾票,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后茫虽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刊苍,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年濒析,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了正什。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡号杏,死狀恐怖婴氮,靈堂內(nèi)的尸體忽然破棺而出斯棒,到底是詐尸還是另有隱情,我是刑警寧澤主经,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布荣暮,位于F島的核電站,受9級(jí)特大地震影響罩驻,放射性物質(zhì)發(fā)生泄漏穗酥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一惠遏、第九天 我趴在偏房一處隱蔽的房頂上張望砾跃。 院中可真熱鬧,春花似錦节吮、人聲如沸抽高。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)厨内。三九已至祈秕,卻和暖如春渺贤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背请毛。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工志鞍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人方仿。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓固棚,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親仙蚜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子此洲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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