LeakCanary-幫助你檢測(cè)Android所有的內(nèi)存泄漏

平時(shí)我們?cè)趯?xiě)Android代碼的時(shí)候會(huì)經(jīng)常遇到非常多的Out Of Memory異常,可以通過(guò)leakcanary這個(gè)第三方庫(kù)幫助我們定位出現(xiàn)問(wèn)題的地方

LeakCanary是什么

  • LeakCanary是一個(gè)能夠幫助Android和Java開(kāi)發(fā)者檢查內(nèi)存泄露的第三方庫(kù)

LeakCanary的配置

  • 在你的build.gradle文件的dependencies中添加如下代碼 :
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
添加依賴
  • 新建一個(gè)MyApplication類繼承Application初始化LeakCanary
package com.android.oz.myleakactiviy;
import android.app.Application;
import com.squareup.leakcanary.LeakCanary;
/** * @author O.z Young 
* @date 16/8/29 
*/
public class MyApplication extends Application {    
    @Override    
    public void onCreate() {        
        super.onCreate();
        // 安裝LeakCanary
        LeakCanary.install(this);
    }}
}
  • 在你的AndroidManifest.xml中配置剛才寫(xiě)好的MyApplication
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.android.oz.myleakactiviy">    
  <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
配置MyApplication

好了,到此為止你的App已經(jīng)添加上了LeakCanary下面寫(xiě)一個(gè)例子用來(lái)展示一下LeeakCanary的實(shí)力


使用例子

我們?cè)谑褂枚嗑€程Handler + Thread進(jìn)行交互的的時(shí)候經(jīng)常會(huì)出現(xiàn)Out of Memory的情況,下面通過(guò)一個(gè)例子來(lái)進(jìn)行分析,以及修正這個(gè)例子

  • 首先確認(rèn)布局activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context="com.android.oz.myleakactiviy.MainActivity">
    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

這是一個(gè)很簡(jiǎn)單的布局,在一個(gè)相對(duì)布局中包含一個(gè)ImageView.

  • 編寫(xiě)onCreate方法
@Overrideprotected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    iv_image = (ImageView) findViewById(R.id.iv_image);
    new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                Log.v("Oz", "運(yùn)行-->" + i + "次");
                SystemClock.sleep(2000);
                Message message = mHandler.obtainMessage();
                message.what = 0;
                mHandler.sendMessageDelayed(message, 5000);
            }
        }
    }).start();
}

onCreate方法中,定義了一個(gè)Thread,這個(gè)子線程要運(yùn)行20次,每運(yùn)行一次都要發(fā)送一個(gè)消息給Handler去處理,這里使用了SystemClock.sleep(time)方法,是為了模擬網(wǎng)絡(luò)請(qǐng)求的等待

  • 編寫(xiě)成員變量Handler
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                iv_image.setImageResource(R.mipmap.ic_launcher);
                break;
        }
    }
};

這里就是處理剛才接受到消息的地方啦,接收到消息之后給iv_image設(shè)置一個(gè)圖片

  • 運(yùn)行這個(gè)app

在app被成功安裝到手機(jī)上之后,我們看到這里除了我們自己的app還多了一個(gè)應(yīng)用,另外一個(gè)應(yīng)用就是LeakCanary

安裝成功

在運(yùn)行的時(shí)候我們不要等待兩秒鐘的時(shí)間,一進(jìn)入到程序之后就點(diǎn)擊返回鍵,退出程序,發(fā)現(xiàn)有LeakCanary的消息推送,點(diǎn)開(kāi)以后內(nèi)容如下

LeakCanary中的內(nèi)容
  • 問(wèn)題分析

可以很清楚的看到,這里的問(wèn)題是這個(gè)Thread導(dǎo)致的,那么為什么會(huì)出現(xiàn)這個(gè)問(wèn)題呢,原因就是我們?cè)?code>Thread中sleep了兩秒,然后在還沒(méi)有到兩秒的時(shí)間中,我們點(diǎn)擊了返回鍵,此時(shí)這個(gè)MainActivity就已經(jīng)被銷毀了,但是在MainActivity中,這個(gè)Thread還是存在的,所以導(dǎo)致了最終的內(nèi)存溢出問(wèn)題

  • 改造Thread

由于產(chǎn)生問(wèn)題的原因在于,MainActivity已經(jīng)銷毀了,但是我們的Thread還沒(méi)有銷毀,那么我們可以自定義一個(gè)靜態(tài)的MyThread類去繼承Thread類,讓MyThread與MainActivity的關(guān)系脫離,除此以外我們?cè)贛yThread中使用MainActivity的弱引用,這樣當(dāng)GC掃描到我們的Thread類的時(shí)候,發(fā)現(xiàn)有弱應(yīng)用就會(huì)自動(dòng)回收了,這樣進(jìn)一步提高內(nèi)存的利用率.

  • 定義一個(gè)MyThread繼承Thread并且聲明MainActivity的弱引用

重寫(xiě)后的MyThread,因?yàn)閷yThread聲明成一個(gè)靜態(tài)的類,所以在里面使用mHandler的時(shí)候需要繞一下彎子,通過(guò)弱引用weakReference獲取到MainActivity里面的mHandler對(duì)象

private static class MyThread extends Thread {
    private WeakReference<MainActivity> weakReference;
    public MyThread(MainActivity mainActivity) {
        weakReference = new WeakReference<MainActivity>(mainActivity);
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            Log.v("Oz", "運(yùn)行-->" + i + "次");
            SystemClock.sleep(2000);
            if (weakReference != null && weakReference.get() != null) {
                //Message message = mHandler.obtainMessage();
                //message.what = 0;
                //mHandler.sendMessageDelayed(message, 5000);
                Message message =
 weakReference.get().mHandler.obtainMessage();
                message.what = 0;
                weakReference.get().mHandler.sendMessageDelayed(message, 5000);
            }
        }
    }
}
  • 再次運(yùn)行

還是跟剛才一樣,進(jìn)入程序之后,不到兩秒的時(shí)間點(diǎn)擊返回鍵,過(guò)一會(huì)以后,LeakCanary又報(bào)錯(cuò)了

第二次報(bào)錯(cuò)

如果你有看過(guò)Handler和Message的源碼,Message.target實(shí)際上指的就是Handler.這里的Handler報(bào)錯(cuò)的原因跟剛才Thread報(bào)錯(cuò)的原因是一樣的,這里就不再重復(fù)說(shuō)明了,接下來(lái)我們就要對(duì)Handler進(jìn)行改造.

  • 對(duì)Handler進(jìn)行改造

改造后的Handler

private static class MyHandler extends Handler {
    private WeakReference<MainActivity> weakReference;
    public MyHandler(MainActivity mainActivity) {
        weakReference = new WeakReference<MainActivity>(mainActivity);    }
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                if (weakReference != null && weakReference.get() != null) {
                    weakReference.get().iv_image.setImageResource(R.mipmap.ic_launcher);
                }
                break;
        }
    }
}

這個(gè)時(shí)候再去運(yùn)行app已經(jīng)不提示錯(cuò)誤了,但是此時(shí)我們又發(fā)現(xiàn)了一個(gè)新的問(wèn)題,當(dāng)我們退出app的時(shí)候,我們MyThread卻還在一直運(yùn)行,后臺(tái)的日志里也在不斷的輸出內(nèi)容

后臺(tái)打印的內(nèi)容

如何處理這個(gè)問(wèn)題呢?

  • 后臺(tái)一直在輸出Log日志的原因分析

我們?cè)谕顺鯽pp之后并沒(méi)有將MyThread的停止,所以導(dǎo)致了后臺(tái)一直在輸出打印語(yǔ)句,那么我們?cè)撊绾谓鉀Q這個(gè)問(wèn)題呢?

  • 停止MyThread線程

首先我們需要明確,停止掉一個(gè)線程不能使用Thread.stop()方法,查看了源碼發(fā)現(xiàn)已經(jīng)是過(guò)時(shí)了的方法,并且在注釋里面也明確說(shuō)明,調(diào)用這個(gè)方法是不安全的,如果調(diào)用了這個(gè)方法會(huì)導(dǎo)致一個(gè)不可預(yù)知的狀態(tài).

原因分析

其實(shí),我們可以使用一個(gè)標(biāo)識(shí)位來(lái)判斷一下是否要繼續(xù)執(zhí)行MyThread里面的循環(huán)方法,如果標(biāo)識(shí)位的判斷結(jié)果為false我們直接跳出循環(huán)就可以了.在MainActivity銷毀的時(shí)候?qū)⑦@個(gè)標(biāo)識(shí)位設(shè)置成false即可.

新增標(biāo)識(shí)位

/**
 * 定義的標(biāo)識(shí)位,判斷MyThread中的循環(huán)是否執(zhí)行
 **/
private static boolean mIsRunning = false;
/**
 * 將標(biāo)識(shí)位設(shè)置成false跳出循環(huán)
 **/
public void setmIsRunning() {
    mIsRunning = false;
}

修改MyThread

private static class MyThread extends Thread {
    private WeakReference<MainActivity> weakReference;
    public MyThread(MainActivity mainActivity) {
        weakReference = new WeakReference<MainActivity>(mainActivity);
    }
    @Override
    public void run() {
        mIsRunning = true;
        for (int i = 0; i < 20; i++) {
            if (!mIsRunning) {
                break;
            }
            Log.v("Oz", "運(yùn)行-->" + i + "次");
            SystemClock.sleep(2000);
            if (weakReference != null && weakReference.get() != null) {
                //Message message = mHandler.obtainMessage();
                //message.what = 0;
                //mHandler.sendMessageDelayed(message, 5000);
                Message message = weakReference.get().myHandler.obtainMessage();
                message.what = 0; 
               weakReference.get().myHandler.sendMessageDelayed(message, 5000);
            }
        }
    }
}

新增onDestory方法

@Override
protected void onDestroy() {
    super.onDestroy();
    // 將標(biāo)識(shí)位設(shè)置成false
    setmIsRunning();
    // 清空message中的消息
    myHandler.removeCallbacksAndMessages(null);
}

到這里問(wèn)題就已經(jīng)解決啦~

最后本次Demo的源碼已經(jīng)上傳到github上了,便于大家查看

MyLeakActivity

轉(zhuǎn)載請(qǐng)注明出處


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市休蟹,隨后出現(xiàn)的幾起案子嘴办,更是在濱河造成了極大的恐慌圣勒,老刑警劉巖表牢,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件养渴,死亡現(xiàn)場(chǎng)離奇詭異介劫,居然都是意外死亡包归,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)赘理,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)宦言,“玉大人,你說(shuō)我怎么就攤上這事商模〉焱” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵阻桅,是天一觀的道長(zhǎng)凉倚。 經(jīng)常有香客問(wèn)我兼都,道長(zhǎng)嫂沉,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任扮碧,我火速辦了婚禮趟章,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘慎王。我一直安慰自己蚓土,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布赖淤。 她就那樣靜靜地躺著蜀漆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪咱旱。 梳的紋絲不亂的頭發(fā)上确丢,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天绷耍,我揣著相機(jī)與錄音,去河邊找鬼鲜侥。 笑死褂始,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的描函。 我是一名探鬼主播崎苗,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼舀寓!你這毒婦竟也來(lái)了胆数?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤互墓,失蹤者是張志新(化名)和其女友劉穎幅慌,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體轰豆,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胰伍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酸休。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骂租。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖斑司,靈堂內(nèi)的尸體忽然破棺而出渗饮,到底是詐尸還是另有隱情,我是刑警寧澤宿刮,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布互站,位于F島的核電站,受9級(jí)特大地震影響僵缺,放射性物質(zhì)發(fā)生泄漏胡桃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一磕潮、第九天 我趴在偏房一處隱蔽的房頂上張望翠胰。 院中可真熱鬧,春花似錦自脯、人聲如沸之景。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锻狗。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間轻纪,已是汗流浹背脚囊。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留桐磁,地道東北人悔耘。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像我擂,于是被迫代替她去往敵國(guó)和親衬以。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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