相同點:都會致使應用運行出現問題、性能降低或崩潰。 不一樣點:java
內存泄漏就是指new出來的Object(強引用)沒法被GC回收android
非靜態內部類和匿名類會隱式地持有一個外部類的引用git
外部類無論有多少個實例,都是共享同一個靜態內部類,所以靜態內部類不會持有外部類的引用github
在使用Cursor,InputStream/OutputStream,File的過程當中每每都用到了緩衝,所以在不須要使用的時候就要及時關閉它們,以便及時回收內存。它們的緩衝不只存在於 java虛擬機內,也存在於java虛擬機外,若是隻是把引用設置爲null而不關閉它們,每每會形成內存泄漏。 此外,對於須要註冊的資源也要記得解除註冊,例如:BroadcastReceiver。動畫也要在界面再也不對用戶可見時中止。bash
在以下代碼中app
public class HandlerActivity extends AppCompatActivity {
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
}
}
複製代碼
在聲明Handler對象後,IDE會給開發者一個提示:ide
This Handler class should be static or leaks might occur.
複製代碼
意思是:Handler須要聲明爲static類型的,不然可能產生內存泄漏工具
這裏來進行具體緣由分析: 應用在第一次啓動時, 系統會在主線程建立Looper對象,Looper實現了一個簡單的消息隊列,用來循環處理Message。全部主要的應用層事件(例如Activity的生命週期方法回調、Button點擊事件等)都會包含在Message裏,系統會把Message添加到Looper中,而後Looper進行消息循環。主線程的Looper存在於整個應用的生命週期期間。 當主線程建立Handler對象時,會與Looepr對象綁定,被分發到消息隊列的Message會持有對Handler的引用,以便系統在Looper處理到該Message時能調用Handle的handlerMessage(Message)方法。 在上述代碼中,Handler不是靜態內部類,因此會持有外部類(HandlerActivity)的一個引用。當Handler中有延遲的的任務或者等待執行的任務隊列過長時,因爲消息持有對Handler的引用,而Handler又持有對其外部類的潛在引用,這條引用關係會一直保持到消息獲得處理爲止,致使了HandlerActivity沒法被垃圾回收器回收,從而致使了內存泄露。oop
好比,在以下代碼中,在onCreate()方法中令handler每隔一秒就輸出Log日記post
public class HandlerActivity extends AppCompatActivity {
private final String TAG = "MainActivity";
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.e(TAG, "Hi");
handler.postDelayed(this, 1000);
}
}, 6000);
}
}
複製代碼
查看Handler的源碼能夠看到,postDelayed方法其實就是在發送一條延時的Message
public final boolean postDelayed(Runnable r, long delayMillis){
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
複製代碼
首先要意識到,非靜態類和匿名內部類都會持有外部類的隱式引用。當HandlerActivity生命週期結束後,延時發送的Message持有Handler的引用,而Handler持有外部類(HandlerActivity)的隱式引用。該引用會繼續存在直到Message被處理完成,而此處並無能夠令Handler終止的條件語句,因此阻止了HandlerActivity的回收,最終致使內存泄漏。
此處使用 LeakCanary 來檢測內存泄露狀況(該工具下邊會有介紹) 先啓動HandlerActivity後退出,等個三四秒後,能夠看到LeakCanary提示咱們應用內存泄漏了
經過文字提示能夠看到問題就出在Handler身上
解決辦法就是在HandlerActivity退出後,移除Handler的全部回調和消息
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
複製代碼
當在開啓一個子線程用於執行一個耗時操做後,此時若是改變配置(例如橫豎屏切換)致使了Activity從新建立,通常來講舊Activity就將交給GC進行回收。但若是建立的線程被聲明爲非靜態內部類或者匿名類,那麼線程會保持有舊Activity的隱式引用。當線程的run()方法尚未執行結束時,線程是不會被銷燬的,所以致使所引用的舊的Activity也不會被銷燬,而且與該Activity相關的全部資源文件也不會被回收,所以形成嚴重的內存泄露。
所以總結來看, 線程產生內存泄露的主要緣由有兩點:
例如以下代碼,在onCreate()方法中啓動一個線程,並用一個靜態變量threadIndex標記當前建立的是第幾個線程
public class ThreadActivity extends AppCompatActivity {
private final String TAG = "ThreadActivity";
private static int threadIndex;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
threadIndex++;
new Thread(new Runnable() {
@Override
public void run() {
int j = threadIndex;
while (true) {
Log.e(TAG, "Hi--" + j);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
複製代碼
旋轉幾回屏幕,能夠看到輸出結果爲:
04-04 08:15:16.373 23731-23911/com.czy.leakdemo E/ThreadActivity: Hi--2
04-04 08:15:16.374 23731-26132/com.czy.leakdemo E/ThreadActivity: Hi--4
04-04 08:15:16.374 23731-23970/com.czy.leakdemo E/ThreadActivity: Hi--3
04-04 08:15:16.374 23731-23820/com.czy.leakdemo E/ThreadActivity: Hi--1
04-04 08:15:16.852 23731-26202/com.czy.leakdemo E/ThreadActivity: Hi--5
04-04 08:15:18.374 23731-23911/com.czy.leakdemo E/ThreadActivity: Hi--2
04-04 08:15:18.374 23731-26132/com.czy.leakdemo E/ThreadActivity: Hi--4
04-04 08:15:18.376 23731-23970/com.czy.leakdemo E/ThreadActivity: Hi--3
04-04 08:15:18.376 23731-23820/com.czy.leakdemo E/ThreadActivity: Hi--1
04-04 08:15:18.852 23731-26202/com.czy.leakdemo E/ThreadActivity: Hi--5
...
複製代碼
即便建立了新的Activity,舊的Activity中創建的線程依然還在執行,從而致使沒法釋放Activity佔用的內存,從而形成嚴重的內存泄漏
LeakCanary的檢測結果:
想要避免由於Thread形成內存泄漏,能夠在Activity退出後主動中止Thread 例如,能夠爲Thread設置一個布爾變量threadSwitch來控制線程的啓動與中止
public class ThreadActivity extends AppCompatActivity {
private final String TAG = "ThreadActivity";
private int threadIndex;
private boolean threadSwitch = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
threadIndex++;
new Thread(new Runnable() {
@Override
public void run() {
int j = threadIndex;
while (threadSwitch) {
Log.e(TAG, "Hi--" + j);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
threadSwitch = false;
}
}
複製代碼
若是想保持Thread繼續運行,能夠按如下步驟來:
public class ThreadActivity extends AppCompatActivity {
private static final String TAG = "ThreadActivity";
private static int threadIndex;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
threadIndex++;
new MyThread(this).start();
}
private static class MyThread extends Thread {
private WeakReference<ThreadActivity> activityWeakReference;
MyThread(ThreadActivity threadActivity) {
activityWeakReference = new WeakReference<>(threadActivity);
}
@Override
public void run() {
if (activityWeakReference == null) {
return;
}
if (activityWeakReference.get() != null) {
int i = threadIndex;
while (true) {
Log.e(TAG, "Hi--" + i);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
複製代碼
在使用Toast的過程當中,若是應用連續彈出多個Toast,那麼就會形成Toast重疊顯示的狀況 所以,可使用以下方法來保證當前應用任什麼時候候只會顯示一個Toast,且Toast的文本信息可以獲得當即更新
/**
* 做者: 葉應是葉
* 時間: 2017/4/4 14:05
* 描述:
*/
public class ToastUtils {
private static Toast toast;
public static void showToast(Context context, String info) {
if (toast == null) {
toast = Toast.makeText(context, info, Toast.LENGTH_SHORT);
}
toast.setText(info);
toast.show();
}
}
複製代碼
而後,在Activity中使用
public class ToastActivity extends AppCompatActivity {
private static int i = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toast);
}
public void showToast(View view) {
ToastUtils.showToast(this, "顯示Toast:" + (i++));
}
}
複製代碼
先點擊一次Button使Toast彈出後,退出ToastActivity,此時LeakCanary又會提示說形成內存泄漏了
當中說起了 Toast.mContext,經過查看Toast類的源碼能夠看到,Toast類內部的mContext指向傳入的Context。而ToastUtils中的toast變量是靜態類型的,其生命週期是與整個應用同樣長的,從而致使 ToastActivity 得不到釋放。所以,對Context的引用不能超過它自己的生命週期。
public Toast(Context context) {
mContext = context;
mTN = new TN();
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
複製代碼
解決辦法是改成使用 ApplicationContext 便可,由於ApplicationContext會隨着應用的存在而存在,而不依賴於Activity的生命週期
/**
* 做者: 葉應是葉
* 時間: 2017/4/4 14:05
* 描述:
*/
public class ToastUtils {
private static Toast toast;
public static void showToast(Context context, String info) {
if (toast == null) {
toast = Toast.makeText(context.getApplicationContext(), info, Toast.LENGTH_SHORT);
}
toast.setText(info);
toast.show();
}
}
複製代碼
有時候咱們須要把一些對象加入到集合容器(例如ArrayList)中,當再也不須要當中某些對象時,若是不把該對象的引用從集合中清理掉,也會使得GC沒法回收該對象。若是集合是static類型的話,那內存泄漏狀況就會更爲嚴重。 所以,當再也不須要某對象時,須要主動將之從集合中移除
LeakCanary是Square公司開發的一個用於檢測內存溢出問題的開源庫,能夠在 debug 包中輕鬆檢測內存泄露 GitHub地址:LeakCanary
要引入LeakCanary庫,只須要在項目的build.gradle文件添加
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
複製代碼
Gradle強大的可配置性,能夠確保只在編譯 debug 版本時纔會檢查內存泄露,而編譯 release 等版本的時候則會自動跳過檢查,避免影響性能
若是隻是想監測Activity的內存泄漏,在自定義的Application中進行以下初始化便可
/**
* 做者: 葉應是葉
* 時間: 2017/4/4 12:41
* 描述:
*/
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
複製代碼
若是還想監測Fragmnet的內存泄漏狀況,則在自定義的Application中進行以下初始化
/**
* 做者: 葉應是葉
* 時間: 2017/4/4 12:41
* 描述:
*/
public class MyApplication extends Application {
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
MyApplication application = (MyApplication) context.getApplicationContext();
return application.refWatcher;
}
}
複製代碼
而後在要監測的Fragment中的onDestroy()創建監聽
public class BaseFragment extends Fragment {
@Override
public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = MyApplication.getRefWatcher();
refWatcher.watch(this);
}
}
複製代碼
當在測試debug版本的過程當中出現內存泄露時,LeakCanary將會自動展現一個通知欄顯示檢測結果