1. 問題描述
-
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
在無設定為靜態類時,為什麼會造成記憶體洩露呢?
2. 原因講解
2.1 儲備知識
- 主執行緒的
Looper
物件的生命週期 = 該應用程式的生命週期 - 在
Java
中,非靜態內部類 & 匿名內部類都預設持有 外部類的引用
2.2 洩露原因描述
從上述示例程式碼可知:
- 上述的
Handler
例項的訊息佇列有2個分別來自執行緒1、2的訊息(分別 為延遲1s
、6s
) - 在
Handler
訊息佇列 還有未處理的訊息 / 正在處理訊息時,訊息佇列中的Message
持有Handler
例項的引用 - 由於
Handler
= 非靜態內部類 / 匿名內部類(2種使用方式),故又預設持有外部類的引用(即MainActivity
例項),引用關係如下圖
上述的引用關係會一直保持,直到
Handler
訊息佇列中的所有訊息被處理完畢
示意圖
- 在
Handler
訊息佇列 還有未處理的訊息 / 正在處理訊息時,此時若需銷燬外部類MainActivity
,但由於上述引用關係,垃圾回收器(GC)
無法回收MainActivity
,從而造成記憶體洩漏。如下圖:
示意圖
2.3 總結
- 當
Handler
訊息佇列 還有未處理的訊息 / 正在處理訊息時,存在引用關係: “未被處理 / 正處理的訊息 ->Handler
例項 -> 外部類” - 若出現
Handler
的生命週期 > 外部類的生命週期 時(即Handler
訊息佇列 還有未處理的訊息 / 正在處理訊息 而 外部類需銷燬時),將使得外部類無法被垃圾回收器(GC)
回收,從而造成 記憶體洩露
3. 解決方案
從上面可看出,造成記憶體洩露的原因有2個關鍵條件:
- 存在“未被處理 / 正處理的訊息 ->
Handler
例項 -> 外部類” 的引用關係 -
Handler
的生命週期 > 外部類的生命週期
即
Handler
訊息佇列 還有未處理的訊息 / 正在處理訊息 而 外部類需銷燬
解決方案的思路 = 使得上述任1條件不成立 即可。
解決方案1:靜態內部類+弱引用
-
原理
靜態內部類 不預設持有外部類的引用,從而使得 “未被處理 / 正處理的訊息 ->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;
}
}
}
}
複製程式碼
解決方案2:當外部類結束生命週期時,清空Handler內訊息佇列
-
原理
不僅使得 “未被處理 / 正處理的訊息 ->Handler
例項 -> 外部類” 的引用關係 不復存在,同時 使得Handler
的生命週期(即 訊息存在的時期) 與 外部類的生命週期 同步 -
具體方案
當 外部類(此處以Activity
為例) 結束生命週期時(此時系統會呼叫onDestroy()
),清除Handler
訊息佇列裡的所有訊息(呼叫removeCallbacksAndMessages(null)
) -
具體程式碼
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
// 外部類Activity生命週期結束時,同時清空訊息佇列 & 結束Handler生命週期
}
複製程式碼
使用建議
為了保證Handler
中訊息佇列中的所有訊息都能被執行,此處推薦使用解決方案1解決記憶體洩露問題,即 靜態內部類 + 弱引用的方式
4. 總結
- 本文主要講解了
Handler
造成 記憶體洩露的相關知識:原理 & 解決方案 - 實際中我們使用rxjava更方便.