LeakCanary Android中內存泄露
前言:
LeakCanary一個直白的展示Android中內存泄露的工具。它是Square公司開源出來的內存泄露自動探測神器,能夠在程序發生內存泄漏的時候在通知欄提示通知,而且學習成本巨低。通過學習本文,了解和如何使用LeakCanary工具,同時了解和解決實際開發中出現的經常遇到的內存泄露案例。
更多詳細介紹請參見Github地址:https://github.com/square/leakcanary
好了,在學習如何使用LeakCanary之前,我們先對內存泄露與內存溢出做出概念性的理解。原因是大部分人對這兩個的區別總是朦朦朧朧分不清楚。
▲概念要點(什麼是內存泄露,內存溢出)
內存泄露(Memory Leak)指你用malloc或new申請了一塊內存,但是沒有通過free或delete將內存釋放,導致這塊內存一直處於佔用狀態內存泄露和硬體沒有關係,它是由軟體設計缺陷引起的。
內存溢出(Memory Overflow)指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory。比如你申請了10個位元組的空間,但是你在這個空間寫入11或以上位元組的數據,就是溢出
▲內存泄露、溢出的異同(兩者之間的區別)
相同點:都會導致應用程序運行出現問題,性能下降或掛起。
不同點:
1) 內存泄露是導致內存溢出的原因之一;內存泄露積累起來將導致內存溢出。
2)內存泄露可以通過完善代碼來避免;內存溢出可以通過調整配置來減少發生頻率,但無法徹底避免。
▲Android中會造成內存泄露的情景無外乎兩種
全局進程(process-global)的static變數。這個無視應用的狀態,持有Activity的強引用的怪物。
活在Activity生命周期之外的線程。沒有清空對Activity的強引用。
了解了內存溢出與內存泄露之後,我們接下來看看如何使用LeakCanary工具減少我們項目中的內存泄露的問題。
▲Android Studio集成LeakCanary
在app的build文件加上:
dependencies {
}
新建MyApplication 中初始化,同時別忘了在AndroidManifest中配置Application標籤中的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集成完成。接下來通過介紹三個經常會在項目中寫錯的內存泄露實例很大眾的實例!!!,介紹和使用如何LeakCanary。同時給出解決方法!!!沒錯,不是五個,不是六個,只有三個。三個不多,但相比百度上搜索常見的內存泄露,寫出了5個同時給出文!字!描!述!的解決方法,卻不給demo,那才是在耍流氓。但在給出三種解決方法之前,常見的內存泄露情況,我們還是有必要過目一下。
▲常見的內存泄露
持有Context引用造成的泄漏
線程之間通過Handler通信引起的內存泄漏
將變數的作用域設置為最小
構造Adapter時,沒有使用緩存的convertView
資源對象沒關閉造成的內存泄露(Cursor、IO 流)
各種註冊沒取消
集合容器對象沒清理造成的內存泄露
static關鍵字的濫用
WebView對象沒有銷毀
GridView的濫用
Handler的使用
線程的使用
Bitmap的回收和置空(對象內存過大)
(如有紕漏,還望指正)
▲接下來是很大眾的內存泄露實例與解決方法
1 . 單例模式造成的內存泄露
//X錯誤的示範
public class InsUtil {
private static InsUtil instance;
private Context mContext;
private InsUtil(Context context) {
this.mContext = context;
}
}
相信很多人看到上述單例的代碼,都會感到內心有一股泥石流,是的,沒錯,因為自己也是這麼寫的。而上述造成內存泄露的原因是傳入Activity的Context,當前Activity退出時它的內存並不會被回收,因為單例對象持有該Activity的引用。Context的引用超過了本身的生命周期,所以不會被回收。正確的寫法是使用Application的Context,使得這個Context的生命周期跟Application一樣長
//√正確的示範
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造成的內存泄露
//X錯誤的示範
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()的時候,該Message還沒有被處理,Message持有Handler, Handler持有Activity,這樣阻止了GC對Acivity的回收,就發生了內存泄露。正確的寫法應該是使用顯形的引用,靜態內部類與 外部類。使用弱引用WeakReference。 最後在Activity調用onDestroy()的時候要取消掉該Handler對象的Message和Runnable
//√正確的示範
public class HandlerActivity extends Activity {
private static class MyHandler extends Handler {
private final WeakReference 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造成的內存泄露
//X錯誤的示範
public class ThreadActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
//兩種常見線程寫法造成的內存泄露
new MyAsyncTask().execute();
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
}
private class MyAsyncTask extends AsyncTask{
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}
}
從上述ThreadActivity可以看出 以上的非同步任務和Runnable都是一個匿名內部類,因此它們對當前Activity都有一個隱式引用。 如果Activity在銷毀之前,任務還未完成, 那麼將導致Activity的內存資源無法回收,造成內存泄漏。 正確的做法還是使用靜態內部類的方式 最後在Activity銷毀的時候,相對應的取消非同步任務
//√正確的示範
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 {
private WeakReference weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference(context);
}
//doInBackground方法內部執行後台任務,不可在此方法內修改UI
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
//onPostExecute方法用於在執行完後台任務後更新UI,顯示結果
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
//onCancelled方法用於在取消執行中的任務時更改UI
@Override
protected void onCancelled() {
super.onCancelled();
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
SystemClock.sleep(10000);
}
}
@Override
protected void onDestroy() {
//判斷非同步任務是否存在
if (myAsyncTask != null && myAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
myAsyncTask.cancel(true);
}
super.onDestroy();
}
}
最後下面祭出本案例使用LeakCanary會出現的效果圖。
TAG:Tester測試圈 |