Android 優(yōu)化一: Leakcanary檢測內(nèi)存泄漏匯總

Leakcanary檢測內(nèi)存泄漏匯總
目錄介紹:
1.什么是內(nèi)存泄漏
2.內(nèi)存泄漏造成什么影響
3.內(nèi)存泄漏檢測的工具有哪些
4.關(guān)于Leakcanary使用介紹
5.Leakcanary捕捉常見的內(nèi)存泄漏及解決辦法
5.1 錯誤使用單例造成的內(nèi)存泄漏
5.2 錯誤使用靜態(tài)變量尿背,導(dǎo)致引用后無法銷毀【工具類使用不當(dāng)導(dǎo)致內(nèi)存泄漏】
5.3 Handler造成的內(nèi)存泄漏
5.4 線程造成的內(nèi)存泄漏
5.5 由WebView引起的內(nèi)存泄漏
5.6 資源未關(guān)閉造成的內(nèi)存泄漏
5.7 未注銷EventBus導(dǎo)致的內(nèi)存泄漏
5.8 靜態(tài)集合使用不當(dāng)導(dǎo)致的內(nèi)存泄漏
5.9 使用弱引用避免內(nèi)存泄漏
6.其他

好消息

  • 博客筆記大匯總【16年3月到至今】狭魂,包括Java基礎(chǔ)及深入知識點(diǎn),Android技術(shù)博客衷畦,Python學(xué)習(xí)筆記等等,還包括平時開發(fā)中遇到的bug匯總罚舱,當(dāng)然也在工作之余收集了大量的面試題会放,長期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的爽待!同時也開源了生活博客,從12年起翩腐,積累共計(jì)47篇[近20萬字]鸟款,轉(zhuǎn)載請注明出處,謝謝茂卦!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好何什,可以star一下,謝謝等龙!當(dāng)然也歡迎提出建議富俄,萬事起于忽微,量變引起質(zhì)變而咆!

1.什么是內(nèi)存泄漏?
一些對象有著有限的聲明周期幕袱,當(dāng)這些對象所要做的事情完成了暴备,我們希望它們會被垃圾回收器回收掉。但是如果有一系列對這個對象的引用存在们豌,那么在我們期待這個對象生命周期結(jié)束時被垃圾回收器回收的時候涯捻,它是不會被回收的。它還會占用內(nèi)存望迎,這就造成了內(nèi)存泄露障癌。持續(xù)累加,內(nèi)存很快被耗盡辩尊。
比如:當(dāng)Activity的onDestroy()方法被調(diào)用后涛浙,Activity以及它涉及到的View和相關(guān)的Bitmap都應(yīng)該被回收掉。但是,如果有一個后臺線程持有這個Activity的引用轿亮,那么該Activity所占用的內(nèi)存就不能被回收疮薇,這最終將會導(dǎo)致內(nèi)存耗盡引發(fā)OOM而讓應(yīng)用crash掉。

2.內(nèi)存泄漏會造成什么影響我注?
它是造成應(yīng)用程序OOM的主要原因之一按咒。由于android系統(tǒng)為每個應(yīng)用程序分配的內(nèi)存有限,當(dāng)一個應(yīng)用中產(chǎn)生的內(nèi)存泄漏比較多時但骨,就難免會導(dǎo)致應(yīng)用所需要的內(nèi)存超過這個系統(tǒng)分配的內(nèi)存限額励七,這就造成了內(nèi)存溢出而導(dǎo)致應(yīng)用Crash。

3.內(nèi)存泄漏檢測的工具有哪些
最常見的是:Leakcanary

4.關(guān)于Leakcanary使用介紹
leakCanary是Square開源框架奔缠,是一個Android和Java的內(nèi)存泄露檢測庫掠抬,如果檢測到某個 activity 有內(nèi)存泄露,LeakCanary 就是自動地顯示一個通知添坊,所以可以把它理解為傻瓜式的內(nèi)存泄露檢測工具剿另。通過它可以大幅度減少開發(fā)中遇到的oom問題,大大提高APP的質(zhì)量贬蛙。
關(guān)于如何配置雨女,這個就不說呢,網(wǎng)上有步驟

5.Leakcanary捕捉常見的內(nèi)存泄漏及解決辦法

  • 5.1 錯誤使用單例造成的內(nèi)存泄漏
    在平時開發(fā)中單例設(shè)計(jì)模式是我們經(jīng)常使用的一種設(shè)計(jì)模式阳准,而在開發(fā)中單例經(jīng)常需要持有Context對象氛堕,如果持有的Context對象生命周期與單例生命周期更短時,或?qū)е翪ontext無法被釋放回收野蝇,則有可能造成內(nèi)存泄漏讼稚,錯誤寫法如下:
  • 問題引起內(nèi)存泄漏代碼
public class LoginManager {
    private static LoginManager mInstance;
    private Context mContext;

    private LoginManager(Context context) {
        this.mContext = context;          //修改代碼:**this.mContext = context.getApplicationContext();**
    }

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

    public void dealData() {}
}
  • 在一個Activity中調(diào)用的,然后關(guān)閉該Activity則會出現(xiàn)內(nèi)存泄漏绕沈。
LoginManager.getInstance(this).dealData();
  • 看看報錯
Image.png
  • 解決辦法:
要保證Context和AppLication的生命周期一樣锐想,修改后代碼如下:
this.mContext = context.getApplicationContext();
  • 原因分析
    創(chuàng)建單例對象,并且在創(chuàng)建的時候需要傳入一個Context對象乍狐,而這時候如果我們使用Activity赠摇、Service等Context對象,由于單例對象的生命周期與進(jìn)程的生命周期相同浅蚪,會造成我們傳入的Activity藕帜、Service對象無法被回收,這時候就需要我們傳入Application對象惜傲,或者在方法中使用Application對象

  • 5.2 錯誤使用靜態(tài)變量洽故,導(dǎo)致引用后無法銷毀
    在平時開發(fā)中,有時候我們創(chuàng)建了一個工具類盗誊。比如分享工具類时甚,十分方便多處調(diào)用隘弊,因此使用靜態(tài)方法是十分方便的。但是創(chuàng)建的對象撞秋,建議不要全局化长捧,全局化的變量必須加上static。這樣會引起內(nèi)存泄漏吻贿!

  • 問題代碼

  • Image.png
  • 在Activity中引用后串结,關(guān)閉該Activity會導(dǎo)致內(nèi)存泄漏

DoShareUtil.showFullScreenShareView(PNewsContentActivity.this, title, title, shareurl, logo);
  • 查看報錯

  • Image.png
  • 解決辦法
    靜態(tài)方法中,創(chuàng)建對象或變量舅列,不要全局化肌割,全局化后的變量或者對象會導(dǎo)致內(nèi)存泄漏;popMenuView和popMenu都不要全局化

  • 知識延伸

**非靜態(tài)內(nèi)部類帐要,靜態(tài)實(shí)例化**
public class MyActivity extends AppCompatActivity {
    //靜態(tài)成員變量
    public static InnerClass innerClass = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        innerClass = new InnerClass();
    }

    class InnerClass {
        public void doSomeThing() {}
    }
}

這里內(nèi)部類InnerClass隱式的持有外部類MyActivity的引用把敞,而在MyActivity的onCreate方法中調(diào)用了。
這樣innerClass就會在MyActivity創(chuàng)建的時候是有了他的引用榨惠,而innerClass是靜態(tài)類型的不會被垃圾回收奋早,
MyActivity在執(zhí)行onDestory方法的時候由于被innerClass持有了引用而無法被回收,所以這樣MyActivity就總是被innerClass持有而無法回收造成內(nèi)存泄露赠橙。
  • 5.3 Handler造成的內(nèi)存泄漏【輪播圖無限循環(huán)輪播耽装,一定要關(guān)閉,否則內(nèi)存泄漏】
    handler是工作線程與UI線程之間通訊的橋梁期揪,只是現(xiàn)在大量開源框架對其進(jìn)行了封裝掉奄,我們這里模擬一種常見使用方式來模擬內(nèi)存泄漏情形。
  • 問題代碼
public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler();
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.text);        //模擬內(nèi)存泄露
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("yangchong");
            }
        }, 2000);
    }
}
  • 造成內(nèi)存泄漏原因分析
    上述代碼通過內(nèi)部類的方式創(chuàng)建mHandler對象,此時mHandler會隱式地持有一個外部類對象引用這里就是MainActivity凤薛,當(dāng)執(zhí)行postDelayed方法時姓建,該方法會將你的Handler裝入一個Message,并把這條Message推到MessageQueue中缤苫,MessageQueue是在一個Looper線程中不斷輪詢處理消息速兔,那么當(dāng)這個Activity退出時消息隊(duì)列中還有未處理的消息或者正在處理消息,而消息隊(duì)列中的Message持有mHandler實(shí)例的引用活玲,mHandler又持有Activity的引用憨栽,所以導(dǎo)致該Activity的內(nèi)存資源無法及時回收,引發(fā)內(nèi)存泄漏翼虫。

  • 查看報錯結(jié)果如下:


    Image.png
  • 解決辦法
    要想避免Handler引起內(nèi)存泄漏問題,需要我們在Activity關(guān)閉退出的時候的移除消息隊(duì)列中所有消息和所有的Runnable屡萤。
    上述代碼只需在onDestroy()函數(shù)中調(diào)用mHandler.removeCallbacksAndMessages(null);就行了珍剑。

@Override
protected void onDestroy() {
    super.onDestroy();
    if(handler!=null){
        handler.removeCallbacksAndMessages(null);
        handler = null;
    }
}
  • 5.4 線程造成的內(nèi)存泄漏
    早時期的時候處理耗時操作多數(shù)都是采用Thread+Handler的方式,后來逐步被AsyncTask取代死陆,直到現(xiàn)在采用RxJava的方式來處理異步招拙。這里以AsyncTask為例唧瘾,可能大部分人都會這樣處理一個耗時操作然后通知UI更新結(jié)果:
  • 問題代碼
public class MainActivity extends AppCompatActivity {

    private AsyncTask<Void, Void, Integer> asyncTask;
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.text);
        testAsyncTask();
        finish();
    }

    private void testAsyncTask() {
        asyncTask = new AsyncTask<Void, Void, Integer>() {
            @Override
            protected Integer doInBackground(Void... params) {
                int i = 0;
                //模擬耗時操作
                while (!isCancelled()) {
                    i++;
                    if (i > 1000000000) {
                        break;
                    }
                    Log.e("LeakCanary", "asyncTask---->" + i);
                }
                return i;
            }

            @Override
            protected void onPostExecute(Integer integer) {
                super.onPostExecute(integer);
                mTextView.setText(String.valueOf(integer));
            }
        };
        asyncTask.execute();
    }
}
  • 造成內(nèi)存泄漏原因分析
    在處理一個比較耗時的操作時,可能還沒處理結(jié)束MainActivity就執(zhí)行了退出操作别凤,但是此時AsyncTask依然持有對MainActivity的引用就會導(dǎo)致MainActivity無法釋放回收引發(fā)內(nèi)存泄漏

  • 查看報錯結(jié)果如下:


    Image.png
  • 解決辦法
    在使用AsyncTask時饰序,在Activity銷毀時候也應(yīng)該取消相應(yīng)的任務(wù)AsyncTask.cancel()方法,避免任務(wù)在后臺執(zhí)行浪費(fèi)資源规哪,進(jìn)而避免內(nèi)存泄漏的發(fā)生

private void destroyAsyncTask() {
    if (asyncTask != null && !asyncTask.isCancelled()) {
        asyncTask.cancel(true);
    }
    asyncTask = null;
}

@Override
protected void onDestroy() {
    super.onDestroy();
    destroyAsyncTask();
}
  • 5.5 由WebView引起的內(nèi)存泄漏
    WebView解析網(wǎng)頁時會申請Native堆內(nèi)存用于保存頁面元素求豫,當(dāng)頁面較復(fù)雜時會有很大的內(nèi)存占用。如果頁面包含圖片诉稍,內(nèi)存占用會更嚴(yán)重蝠嘉。并且打開新頁面時,為了能快速回退杯巨,之前頁面占用的內(nèi)存也不會釋放蚤告。有時瀏覽十幾個網(wǎng)頁,都會占用幾百兆的內(nèi)存服爷。這樣加載網(wǎng)頁較多時杜恰,會導(dǎo)致系統(tǒng)不堪重負(fù),最終強(qiáng)制關(guān)閉應(yīng)用仍源,也就是出現(xiàn)應(yīng)用閃退或重啟
  • 問題代碼
public class MainActivity5 extends AppCompatActivity {
    private WebView mWebView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web);
        mWebView = (WebView) findViewById(R.id.web);
        mWebView.loadUrl("http://www.cnblogs.com/whoislcj/p/5720202.html");
    }
}
  • 造成內(nèi)存泄漏原因分析
    加載網(wǎng)頁有緩存心褐,當(dāng)加載了許多網(wǎng)頁,并且手機(jī)配置比較低時镜会,造成的內(nèi)存泄漏就對手機(jī)影響很大

  • 查看報錯結(jié)果如下:

  • 解決辦法

@Override
protected void onDestroy() {
    if (mWebView != null) {
        mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
        mWebView.clearHistory();
        ViewGroup parent = (ViewGroup) mWebView.getParent();
        if(parent!=null){
            parent.removeView(mWebView);
        }
        mWebView.destroy();
        mWebView = null;
    }
    super.onDestroy();
}
  • 5.6 資源未關(guān)閉造成的內(nèi)存泄漏
    對于使用了BraodcastReceiver檬寂,ContentObserver,F(xiàn)ile戳表,Cursor桶至,Stream,Bitmap等資源的使用匾旭,應(yīng)該在Activity銷毀時及時關(guān)閉或者注銷镣屹,否則這些資源將不會被回收,造成內(nèi)存泄漏价涝。

  • 5.7 未注銷EventBus導(dǎo)致的內(nèi)存泄漏
    直接展示代碼

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_common);
    EventBus.getDefault().register(this);
}

@Subscribe
public void onEvent(MessageEvent msg) {

}

@Override
protected void onDestroy() {
    super.onDestroy();
    //未移除注冊的EventBus
    //EventBus.getDefault().unregister(this);
}
  • 5.8 靜態(tài)集合使用不當(dāng)導(dǎo)致的內(nèi)存泄漏
    添加Activity到棧女蜈,或者移除Activity出棧。導(dǎo)致內(nèi)存泄漏
public class ActivityCollector {
    public static List<Activity> activities = new ArrayList<Activity>();
    public static void addActivity(Activity activity) {
        activities.add(activity);
    }
    public static void removeActivity(Activity activity) {
        activities.remove(activity);
    }
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_common);
    ActivityCollector.addActivity(this);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    //靜態(tài)集合沒有移除元素
    //ActivityCollector.removeActivity(this);
}
  • 5.9 使用弱引用避免內(nèi)存泄漏
    在 Activity 中避免使用非靜態(tài)內(nèi)部類色瘩,比如上面我們將 Handler 聲明為靜態(tài)的伪窖,則其存活期跟 Activity 的生命周期就無關(guān)了。同時通過弱引用的方式引入 Activity居兆,避免直接將 Activity 作為 context 傳進(jìn)去
public class WeakReferenceActivity extends AppCompatActivity {

    //將Handler聲明為靜態(tài) 沒有了Activity的引用, 無法直接引用其變量或方法,
    //使用弱引用WeakReference來解決這個問題
    private static class DBHandler extends Handler {
        //弱引用, 而不是使用外部類this或者傳進(jìn)來
        private final WeakReference<WeakReferenceActivity> mActivity;
        public DBHandler(WeakReferenceActivity activity) {
            mActivity = new WeakReference<>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            WeakReferenceActivity activity = mActivity.get();
            if (activity != null) {
                Toast.makeText(activity, "what: " + msg.what, Toast.LENGTH_SHORT).show();
            }
        }
    }

    private final DBHandler mHandler = new DBHandler(this);

    private static final Runnable sRunnable = new Runnable() {
        @Override
        public void run() {
            Toast.makeText(App.getInstance(), "sRunnable run()", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_weak_reference);
        mHandler.postDelayed(sRunnable, 1000 * 20);
    }

    public void onClick(View view) {
        mHandler.sendEmptyMessage(new Random().nextInt(10));
    }
}

后續(xù):
平時喜歡寫寫文章覆山,筆記。別人建議我把筆記泥栖,以前寫的東西整理簇宽,然后寫成博客勋篓,所以我會陸續(xù)整理文章,只發(fā)自己寫的東西魏割,敬請期待譬嚣。如果有什么問題或者需要筆記,可以直接聯(lián)系我钞它,可以發(fā)送印象筆記文檔給你拜银!
知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
領(lǐng)英:https://www.linkedin.com/in/chong-yang-049216146/
簡書:http://www.reibang.com/u/b7b2c6ed9284
csdn:http://my.csdn.net/m0_37700275
網(wǎng)易博客:http://yangchong211.blog.163.com/
新浪博客:http://blog.sina.com.cn/786041010yc
github:https://github.com/yangchong211
喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
脈脈:yc930211
開源中國:https://my.oschina.net/zbj1618/blog
郵箱:yangchong211@163.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市须揣,隨后出現(xiàn)的幾起案子盐股,更是在濱河造成了極大的恐慌,老刑警劉巖耻卡,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疯汁,死亡現(xiàn)場離奇詭異,居然都是意外死亡卵酪,警方通過查閱死者的電腦和手機(jī)幌蚊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溃卡,“玉大人溢豆,你說我怎么就攤上這事∪诚郏” “怎么了漩仙?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長犹赖。 經(jīng)常有香客問我队他,道長,這世上最難降的妖魔是什么峻村? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任麸折,我火速辦了婚禮,結(jié)果婚禮上粘昨,老公的妹妹穿的比我還像新娘垢啼。我一直安慰自己,他們只是感情好张肾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布芭析。 她就那樣靜靜地躺著,像睡著了一般吞瞪。 火紅的嫁衣襯著肌膚如雪放刨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天尸饺,我揣著相機(jī)與錄音进统,去河邊找鬼。 笑死浪听,一個胖子當(dāng)著我的面吹牛螟碎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迹栓,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼掉分,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了克伊?” 一聲冷哼從身側(cè)響起酥郭,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎愿吹,沒想到半個月后不从,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡犁跪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年椿息,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坷衍。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡寝优,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出枫耳,到底是詐尸還是另有隱情乏矾,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布迁杨,位于F島的核電站钻心,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏仑最。R本人自食惡果不足惜扔役,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望警医。 院中可真熱鬧亿胸,春花似錦、人聲如沸预皇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吟温。三九已至序仙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鲁豪,已是汗流浹背潘悼。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工律秃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人治唤。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓棒动,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宾添。 傳聞我的和親對象是個殘疾皇子船惨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

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