平時我們在寫Android代碼的時候會經常遇到非常多的
Out Of Memory
異常,可以通過leakcanary
這個第三方庫幫助我們定位出現問題的地方
- GitHub地址 : leakcanary
LeakCanary是什么
- LeakCanary是一個能夠幫助Android和Java開發者檢查內存泄露的第三方庫
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'
- 新建一個
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
中配置剛才寫好的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
已經添加上了LeakCanary
下面寫一個例子用來展示一下LeeakCanary
的實力
使用例子
我們在使用多線程Handler + Thread
進行交互的的時候經常會出現Out of Memory
的情況,下面通過一個例子來進行分析,以及修正這個例子
- 首先確認布局
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>
這是一個很簡單的布局,在一個相對布局中包含一個ImageView
.
- 編寫
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", "運行-->" + i + "次");
SystemClock.sleep(2000);
Message message = mHandler.obtainMessage();
message.what = 0;
mHandler.sendMessageDelayed(message, 5000);
}
}
}).start();
}
在onCreate
方法中,定義了一個Thread
,這個子線程要運行20次,每運行一次都要發送一個消息給Handler
去處理,這里使用了SystemClock.sleep(time)
方法,是為了模擬網絡請求的等待
- 編寫成員變量
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
設置一個圖片
- 運行這個app
在app被成功安裝到手機上之后,我們看到這里除了我們自己的app還多了一個應用,另外一個應用就是LeakCanary
在運行的時候我們不要等待兩秒鐘的時間,一進入到程序之后就點擊返回鍵,退出程序,發現有LeakCanary
的消息推送,點開以后內容如下
- 問題分析
可以很清楚的看到,這里的問題是這個Thread導致的,那么為什么會出現這個問題呢,原因就是我們在Thread
中sleep了兩秒,然后在還沒有到兩秒的時間中,我們點擊了返回鍵,此時這個MainActivity就已經被銷毀了,但是在MainActivity中,這個Thread還是存在的,所以導致了最終的內存溢出問題
- 改造
Thread
由于產生問題的原因在于,MainActivity已經銷毀了,但是我們的Thread還沒有銷毀,那么我們可以自定義一個靜態的MyThread類去繼承Thread類,讓MyThread與MainActivity的關系脫離,除此以外我們在MyThread中使用MainActivity的弱引用,這樣當GC掃描到我們的Thread類的時候,發現有弱應用就會自動回收了,這樣進一步提高內存的利用率.
- 定義一個
MyThread
繼承Thread
并且聲明MainActivity
的弱引用
重寫后的MyThread,因為將MyThread聲明成一個靜態的類,所以在里面使用mHandler的時候需要繞一下彎子,通過弱引用weakReference獲取到MainActivity里面的mHandler對象
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", "運行-->" + 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);
}
}
}
}
- 再次運行
還是跟剛才一樣,進入程序之后,不到兩秒的時間點擊返回鍵,過一會以后,LeakCanary
又報錯了
如果你有看過Handler和Message的源碼,Message.target實際上指的就是Handler.這里的Handler報錯的原因跟剛才Thread報錯的原因是一樣的,這里就不再重復說明了,接下來我們就要對Handler進行改造.
- 對
Handler
進行改造
改造后的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;
}
}
}
這個時候再去運行app已經不提示錯誤了,但是此時我們又發現了一個新的問題,當我們退出app的時候,我們MyThread卻還在一直運行,后臺的日志里也在不斷的輸出內容
如何處理這個問題呢?
- 后臺一直在輸出Log日志的原因分析
我們在退出app之后并沒有將MyThread的停止,所以導致了后臺一直在輸出打印語句,那么我們該如何解決這個問題呢?
- 停止
MyThread
線程
首先我們需要明確,停止掉一個線程不能使用Thread.stop()方法,查看了源碼發現已經是過時了的方法,并且在注釋里面也明確說明,調用這個方法是不安全的,如果調用了這個方法會導致一個不可預知的狀態.
其實,我們可以使用一個標識位來判斷一下是否要繼續執行MyThread里面的循環方法,如果標識位的判斷結果為false我們直接跳出循環就可以了.在MainActivity銷毀的時候將這個標識位設置成false即可.
新增標識位
/**
* 定義的標識位,判斷MyThread中的循環是否執行
**/
private static boolean mIsRunning = false;
/**
* 將標識位設置成false跳出循環
**/
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", "運行-->" + 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();
// 將標識位設置成false
setmIsRunning();
// 清空message中的消息
myHandler.removeCallbacksAndMessages(null);
}
到這里問題就已經解決啦~
最后本次Demo的源碼已經上傳到github上了,便于大家查看