Android
開發中,內存泄露 十分常見
- 內存泄露的定義:本該被回收的對象不能被回收而停留在堆內存中
- 內存泄露出現的緣由:當一個對象已經再也不被使用時,本該被回收但卻由於有另一個正在使用的對象持有它的引用從而致使它不能被回收。 這就致使了內存泄漏。
Handler
中發生的內存泄露閱讀本文前,建議先閱讀文章:Android開發:Handler異步通訊機制全面解析(包含Looper、Message Queue)bash
Handler
的通常用法 = 新建Handler
子類(內部類) 、匿名Handler
內部類/**
* 方式1:新建Handler子類(內部類)
*/
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主線程建立時便自動建立Looper & 對應的MessageQueue
// 以後執行Loop()進入消息循環
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1. 實例化自定義的Handler類對象->>分析1
//注:此處並沒有指定Looper,故自動綁定當前線程(主線程)的Looper、MessageQueue
showhandler = new FHandler();
// 2. 啓動子線程1
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要發送的消息
Message msg = Message.obtain();
msg.what = 1;// 消息標識
msg.obj = "AA";// 消息存放
// b. 傳入主線程的Handler & 向其MessageQueue發送消息
showhandler.sendMessage(msg);
}
}.start();
// 3. 啓動子線程2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要發送的消息
Message msg = Message.obtain();
msg.what = 2;// 消息標識
msg.obj = "BB";// 消息存放
// b. 傳入主線程的Handler & 向其MessageQueue發送消息
showhandler.sendMessage(msg);
}
}.start();
}
// 分析1:自定義Handler子類
class FHandler extends Handler {
// 經過複寫handlerMessage() 從而肯定更新UI的操做
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到線程1的消息");
break;
case 2:
Log.d(TAG, " 收到線程2的消息");
break;
}
}
}
}
/**
* 方式2:匿名Handler內部類
*/
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主線程建立時便自動建立Looper & 對應的MessageQueue
// 以後執行Loop()進入消息循環
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1. 經過匿名內部類實例化的Handler類對象
//注:此處並沒有指定Looper,故自動綁定當前線程(主線程)的Looper、MessageQueue
showhandler = new Handler(){
// 經過複寫handlerMessage()從而肯定更新UI的操做
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到線程1的消息");
break;
case 2:
Log.d(TAG, " 收到線程2的消息");
break;
}
}
};
// 2. 啓動子線程1
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要發送的消息
Message msg = Message.obtain();
msg.what = 1;// 消息標識
msg.obj = "AA";// 消息存放
// b. 傳入主線程的Handler & 向其MessageQueue發送消息
showhandler.sendMessage(msg);
}
}.start();
// 3. 啓動子線程2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要發送的消息
Message msg = Message.obtain();
msg.what = 2;// 消息標識
msg.obj = "BB";// 消息存放
// b. 傳入主線程的Handler & 向其MessageQueue發送消息
showhandler.sendMessage(msg);
}
}.start();
}
}
複製代碼
測試結果 微信
上述例子雖可運行成功,但代碼會出現嚴重警告:異步
- 警告的緣由 = 該
Handler
類因爲無設置爲 靜態類,從而致使了內存泄露- 最終的內存泄露發生在
Handler
類的外部類:MainActivity
類
那麼,該Handler
在無設置爲靜態類時,爲何會形成內存泄露呢?ide
Looper
對象的生命週期 = 該應用程序的生命週期Java
中,非靜態內部類 & 匿名內部類都默認持有 外部類的引用從上述示例代碼可知:oop
Handler
實例的消息隊列有2個分別來自線程一、2的消息(分別 爲延遲1s
、6s
)Handler
消息隊列 還有未處理的消息 / 正在處理消息時,消息隊列中的Message
持有Handler
實例的引用Handler
= 非靜態內部類 / 匿名內部類(2種使用方式),故又默認持有外部類的引用(即MainActivity
實例),引用關係以下圖上述的引用關係會一直保持,直到
Handler
消息隊列中的全部消息被處理完畢測試
Handler
消息隊列 還有未處理的消息 / 正在處理消息時,此時若需銷燬外部類MainActivity
,但因爲上述引用關係,垃圾回收器(GC)
沒法回收MainActivity
,從而形成內存泄漏。以下圖:Handler
消息隊列 還有未處理的消息 / 正在處理消息時,存在引用關係: 「未被處理 / 正處理的消息 -> Handler
實例 -> 外部類」Handler
的生命週期 > 外部類的生命週期 時(即 Handler
消息隊列 還有未處理的消息 / 正在處理消息 而 外部類需銷燬時),將使得外部類沒法被垃圾回收器(GC)
回收,從而形成 內存泄露從上面可看出,形成內存泄露的緣由有2個關鍵條件:ui
Handler
實例 -> 外部類」 的引用關係Handler
的生命週期 > 外部類的生命週期即
Handler
消息隊列 還有未處理的消息 / 正在處理消息 而 外部類需銷燬this
解決方案的思路 = 使得上述任1條件不成立 便可。spa
原理 靜態內部類 不默認持有外部類的引用,從而使得 「未被處理 / 正處理的消息 -> Handler
實例 -> 外部類」 的引用關係 的引用關係 不復存在。線程
具體方案 將Handler
的子類設置成 靜態內部類
- 同時,還可加上 使用WeakReference弱引用持有Activity實例
- 緣由:弱引用的對象擁有短暫的生命週期。在垃圾回收器線程掃描時,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主線程建立時便自動建立Looper & 對應的MessageQueue
// 以後執行Loop()進入消息循環
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1. 實例化自定義的Handler類對象->>分析1
//注:
// a. 此處並沒有指定Looper,故自動綁定當前線程(主線程)的Looper、MessageQueue;
// b. 定義時需傳入持有的Activity實例(弱引用)
showhandler = new FHandler(this);
// 2. 啓動子線程1
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要發送的消息
Message msg = Message.obtain();
msg.what = 1;// 消息標識
msg.obj = "AA";// 消息存放
// b. 傳入主線程的Handler & 向其MessageQueue發送消息
showhandler.sendMessage(msg);
}
}.start();
// 3. 啓動子線程2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要發送的消息
Message msg = Message.obtain();
msg.what = 2;// 消息標識
msg.obj = "BB";// 消息存放
// b. 傳入主線程的Handler & 向其MessageQueue發送消息
showhandler.sendMessage(msg);
}
}.start();
}
// 分析1:自定義Handler子類
// 設置爲:靜態內部類
private static class FHandler extends Handler{
// 定義 弱引用實例
private WeakReference<Activity> reference;
// 在構造方法中傳入需持有的Activity實例
public FHandler(Activity activity) {
// 使用WeakReference弱引用持有Activity實例
reference = new WeakReference<Activity>(activity); }
// 經過複寫handlerMessage() 從而肯定更新UI的操做
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到線程1的消息");
break;
case 2:
Log.d(TAG, " 收到線程2的消息");
break;
}
}
}
}
複製代碼
原理 不只使得 「未被處理 / 正處理的消息 -> Handler
實例 -> 外部類」 的引用關係 不復存在,同時 使得 Handler
的生命週期(即 消息存在的時期) 與 外部類的生命週期 同步
具體方案 當 外部類(此處以Activity
爲例) 結束生命週期時(此時系統會調用onDestroy()
),清除 Handler
消息隊列裏的全部消息(調用removeCallbacksAndMessages(null)
)
具體代碼
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
// 外部類Activity生命週期結束時,同時清空消息隊列 & 結束Handler生命週期
}
複製代碼
爲了保證Handler
中消息隊列中的全部消息都能被執行,此處推薦使用解決方案1解決內存泄露問題,即 靜態內部類 + 弱引用的方式
Handler
形成 內存泄露的相關知識:原理 & 解決方案Android
開發中關於內存泄露的知識,有興趣能夠繼續關注Carson_Ho的安卓開發筆記