平時(shí)我們?cè)趯?xiě)Android代碼的時(shí)候會(huì)經(jīng)常遇到非常多的
Out Of Memory
異常,可以通過(guò)leakcanary
這個(gè)第三方庫(kù)幫助我們定位出現(xiàn)問(wèn)題的地方
- GitHub地址 : leakcanary
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>
好了,到此為止你的
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)容如下
- 問(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ò)了
如果你有看過(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)容
如何處理這個(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上了,便于大家查看