前言:
LeakCanary一個(gè)直白的展示Android中內(nèi)存泄露的工具。它是Square公司開(kāi)源出來(lái)的內(nèi)存泄露自動(dòng)探測(cè)神器,能夠在程序發(fā)生內(nèi)存泄漏的時(shí)候在通知欄提示通知,而且學(xué)習(xí)成本巨低。通過(guò)學(xué)習(xí)本文,了解和如何使用LeakCanary工具,同時(shí)了解和解決實(shí)際開(kāi)發(fā)中出現(xiàn)的經(jīng)常遇到的內(nèi)存泄露案例。
更多詳細(xì)介紹請(qǐng)參見(jiàn)Github地址:https://github.com/square/leakcanary
好了,在學(xué)習(xí)如何使用LeakCanary之前,我們先對(duì)內(nèi)存泄露與內(nèi)存溢出做出概念性的理解。原因是大部分人對(duì)這兩個(gè)的區(qū)別總是朦朦朧朧分不清楚。
▲概念要點(diǎn)(什么是內(nèi)存泄露,內(nèi)存溢出)
- 內(nèi)存泄露(Memory Leak)指你用malloc或new申請(qǐng)了一塊內(nèi)存,但是沒(méi)有通過(guò)free或delete將內(nèi)存釋放,導(dǎo)致這塊內(nèi)存一直處于占用狀態(tài)內(nèi)存泄露和硬件沒(méi)有關(guān)系,它是由軟件設(shè)計(jì)缺陷引起的。
- 內(nèi)存溢出(Memory Overflow)指程序在申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存空間供其使用,出現(xiàn)out of memory。比如你申請(qǐng)了10個(gè)字節(jié)的空間,但是你在這個(gè)空間寫(xiě)入11或以上字節(jié)的數(shù)據(jù),就是溢出
▲內(nèi)存泄露、溢出的異同(兩者之間的區(qū)別)
相同點(diǎn):都會(huì)導(dǎo)致應(yīng)用程序運(yùn)行出現(xiàn)問(wèn)題,性能下降或掛起。
不同點(diǎn):
- 內(nèi)存泄露是導(dǎo)致內(nèi)存溢出的原因之一;內(nèi)存泄露積累起來(lái)將導(dǎo)致內(nèi)存溢出。
2)內(nèi)存泄露可以通過(guò)完善代碼來(lái)避免;內(nèi)存溢出可以通過(guò)調(diào)整配置來(lái)減少發(fā)生頻率,但無(wú)法徹底避免。
▲Android中會(huì)造成內(nèi)存泄露的情景無(wú)外乎兩種
- 全局進(jìn)程(process-global)的static變量。這個(gè)無(wú)視應(yīng)用的狀態(tài),持有Activity的強(qiáng)引用的怪物。
- 活在Activity生命周期之外的線程。沒(méi)有清空對(duì)Activity的強(qiáng)引用。
了解了內(nèi)存溢出與內(nèi)存泄露之后,我們接下來(lái)看看如何使用LeakCanary工具減少我們項(xiàng)目中的內(nèi)存泄露的問(wèn)題。
▲Android Studio集成LeakCanary
在app的build文件加上:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}
新建MyApplication 中初始化,同時(shí)別忘了在AndroidManifest中配置Application標(biāo)簽中的name
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
enabledStrictMode();
LeakCanary.install(this);
}
private void enabledStrictMode() {
if (SDK_INT >= GINGERBREAD) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() //
.detectAll() //
.penaltyLog() //
.penaltyDeath() //
.build());
}
}
}
LeakCanary集成完成。接下來(lái)通過(guò)介紹三個(gè)經(jīng)常會(huì)在項(xiàng)目中寫(xiě)錯(cuò)的內(nèi)存泄露實(shí)例很大眾的實(shí)例!!!,介紹和使用如何LeakCanary。同時(shí)給出**解決方法!!! **沒(méi)錯(cuò),不是五個(gè),不是六個(gè),只有三個(gè)。三個(gè)不多,但相比百度上搜索常見(jiàn)的內(nèi)存泄露,寫(xiě)出了5個(gè)同時(shí)給出文!字!描!述!的解決方法,卻不給demo,那才是在耍流氓。但在給出三種解決方法之前,常見(jiàn)的內(nèi)存泄露情況,我們還是有必要過(guò)目一下。
▲常見(jiàn)的內(nèi)存泄露
- 持有Context引用造成的泄漏
- 線程之間通過(guò)Handler通信引起的內(nèi)存泄漏
- 將變量的作用域設(shè)置為最小
- 構(gòu)造Adapter時(shí),沒(méi)有使用緩存的convertView
- 資源對(duì)象沒(méi)關(guān)閉造成的內(nèi)存泄露(Cursor、IO 流)
- 各種注冊(cè)沒(méi)取消
- 集合容器對(duì)象沒(méi)清理造成的內(nèi)存泄露
- static關(guān)鍵字的濫用
- WebView對(duì)象沒(méi)有銷(xiāo)毀
- GridView的濫用
- Handler的使用
- 線程的使用
- Bitmap的回收和置空(對(duì)象內(nèi)存過(guò)大)
(如有紕漏,還望指正)
▲接下來(lái)是很大眾的內(nèi)存泄露實(shí)例與解決方法
1 . 單例模式造成的內(nèi)存泄露
//X錯(cuò)誤的示范
public class InsUtil {
private static InsUtil instance;
private Context mContext;
private InsUtil(Context context) {
this.mContext = context;
}
}
相信很多人看到上述單例的代碼,都會(huì)感到內(nèi)心有一股泥石流,是的,沒(méi)錯(cuò),因?yàn)樽约阂彩沁@么寫(xiě)的。而上述造成內(nèi)存泄露的原因是傳入Activity的Context,當(dāng)前Activity退出時(shí)它的內(nèi)存并不會(huì)被回收,因?yàn)閱卫龑?duì)象持有該Activity的引用。Context的引用超過(guò)了本身的生命周期,所以不會(huì)被回收。正確的寫(xiě)法是使用Application的Context,使得這個(gè)Context的生命周期跟Application一樣長(zhǎng)
//√正確的示范
public class InsUtil {
private static InsUtil instance;
private Context mContext;
private InsUtil(Context context) {
this.mContext = context.getApplicationContext();
}
public static InsUtil getInsUtil(Context context) {
if (instance == null) {
instance = new InsUtil(context);
}
return instance;
}
}
2 . handler造成的內(nèi)存泄露
//X錯(cuò)誤的示范
public class HandlerActivity extends Activity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// do something you want
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 10000);
//just finish this activity
finish();
}
}
從上述的HandlerActivity 可以看出,在finish()的時(shí)候,該Message還沒(méi)有被處理,Message持有Handler, Handler持有Activity,這樣阻止了GC對(duì)Acivity的回收,就發(fā)生了內(nèi)存泄露。正確的寫(xiě)法應(yīng)該是使用顯形的引用,靜態(tài)內(nèi)部類(lèi)與 外部類(lèi)。使用弱引用WeakReference。 最后在Activity調(diào)用onDestroy()的時(shí)候要取消掉該Handler對(duì)象的Message和Runnable
//√正確的示范
public class HandlerActivity extends Activity {
private static class MyHandler extends Handler {
private final WeakReference<HandlerActivity> mActivityReference;
public MyHandler(HandlerActivity activity) {
mActivityReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
HandlerActivity handlerAct = mActivityReference.get();
if (handlerAct == null) {
return;
}
// Do something you want
}
}
private MyHandler mHandler ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler=new MyHandler(HandlerActivity.this);
//just finish this activity
finish();
}
@Override
protected void onDestroy() {
// Remove all Runnable and Message.
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
}
3 . Thread造成的內(nèi)存泄露
//X錯(cuò)誤的示范
public class ThreadActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
//兩種常見(jiàn)線程寫(xiě)法造成的內(nèi)存泄露
new MyAsyncTask().execute();
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
}
private class MyAsyncTask extends AsyncTask<Void,Void,Void>{
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}
}
從上述ThreadActivity可以看出 以上的異步任務(wù)和Runnable都是一個(gè)匿名內(nèi)部類(lèi),因此它們對(duì)當(dāng)前Activity都有一個(gè)隱式引用。 如果Activity在銷(xiāo)毀之前,任務(wù)還未完成, 那么將導(dǎo)致Activity的內(nèi)存資源無(wú)法回收,造成內(nèi)存泄漏。 正確的做法還是使用靜態(tài)內(nèi)部類(lèi)的方式 最后在Activity銷(xiāo)毀的時(shí)候,相對(duì)應(yīng)的取消異步任務(wù)
//√正確的示范
public class ThreadActivity extends Activity {
private MyAsyncTask myAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
myAsyncTask = new MyAsyncTask(ThreadActivity.this);
myAsyncTask.execute();
new Thread(new MyRunnable()).start();
}
private static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
//doInBackground方法內(nèi)部執(zhí)行后臺(tái)任務(wù),不可在此方法內(nèi)修改UI
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
//onPostExecute方法用于在執(zhí)行完后臺(tái)任務(wù)后更新UI,顯示結(jié)果
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
//onCancelled方法用于在取消執(zhí)行中的任務(wù)時(shí)更改UI
@Override
protected void onCancelled() {
super.onCancelled();
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
SystemClock.sleep(10000);
}
}
@Override
protected void onDestroy() {
//判斷異步任務(wù)是否存在
if (myAsyncTask != null && myAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
myAsyncTask.cancel(true);
}
super.onDestroy();
}
}
最后下面祭出本案例使用LeakCanary會(huì)出現(xiàn)的效果圖。
Github下載地址:https://github.com/ChenYXin/LeakCanary_Demo
關(guān)于我:
- Android開(kāi)發(fā)交流QQ群:537891203
- 郵箱:donkor@yeah.net