前言
- 在Android開發中,多執行緒應用是非常頻繁的,其中
Handler
機制隨處可見. - 下面就本人對Handle的一些理解與大家一起分享,共同回顧下
Handle非同步訊息傳遞機制
。
1.Handler是什麼?
- Handler是一套在 Android開發中 進行非同步訊息傳遞的機制。
2.Handler在Android中的作用
- 在Android開發中多執行緒的應用中,將工作執行緒中需更新UI的操作資訊 傳遞到 UI主執行緒,從而實現 工作執行緒對UI的更新處理,最終實現非同步訊息的處理。
3. 我們為什麼要使用Handler去處理更新UI操作呢?
- 在多個執行緒併發更新UI的同時 保證執行緒安全。
4.Handler非同步訊息傳遞所涉及的相關概念
- MainThread (主執行緒)
UI執行緒,程式啟動時自動建立。
- 工作執行緒,
開發者自己開啟的執行緒,執行耗時操作等。
- Handler(處理者)
UI執行緒與子執行緒通訊的媒介,新增訊息到訊息佇列(Message Queue)處理迴圈器分發過來的訊息(Looper)。
- Message (訊息)
Handler接受&處理的物件,儲存需要操作的訊息。
- Message Queue(訊息佇列)
資料儲存結構,採用先進先出方式,儲存Handler發過來的訊息。
- Looper(循壞器)
訊息佇列與處理者的媒介,從訊息佇列中迴圈取出訊息併傳送給Handler處理。
5.使用方式
- Handler的使用方式 因傳送訊息到訊息佇列的方式不同而不同(兩種)
使用Handler.sendMessage()、使用Handler.post()
1.使用 Handler.sendMessage()方式
/**
* 方式1:新建Handler子類(內部類)
*/
// 步驟1:自定義Handler子類(繼承Handler類) & 複寫handleMessage()方法
class mHandler extends Handler {
// 通過複寫handlerMessage() 從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 執行UI操作
}
}
// 步驟2:在主執行緒中建立Handler例項
private Handler mhandler = new mHandler();
// 步驟3:建立所需的訊息物件
Message msg = Message.obtain(); // 例項化訊息物件
msg.what = 1; // 訊息標識
msg.obj = "AA"; // 訊息內容存放
// 步驟4:在工作執行緒中 通過Handler傳送訊息到訊息佇列中
mHandler.sendMessage(msg);
/**
* 方式2:匿名內部類
*/
// 步驟1:在主執行緒中 通過匿名內部類 建立Handler類物件
private Handler mhandler = new Handler(){
// 通過複寫handlerMessage()
@Override
public void handleMessage(Message msg) {
...// 需執行UI操作
}
};
// 步驟2:建立訊息物件
Message msg = Message.obtain(); // 例項化訊息物件
msg.what = 1; // 訊息標識
msg.obj = "AA"; // 訊息內容存放
// 步驟3:在工作執行緒中 通過Handler傳送訊息到訊息佇列中
mHandler.sendMessage(msg);
複製程式碼
2.使用Handler.post()
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 通過psot()傳送,傳入1個Runnable物件
mHandler.post(new Runnable() {
@Override
public void run() {
// 指定操作UI內容
}
});
}
}.start();
複製程式碼
6.Handler底層原理及原始碼分析
在原始碼分析前,先來了解Handler機制中的幾個核心類
- 處理器 (Handler)
- 訊息佇列 (MessageQueue)
- 迴圈器 (Looper) 關於這幾個類的具體作用前面已經介紹過了就不再過多闡述了。
下面開始原始碼分析,注意力集中了 上文中我們提到過Handler傳送訊息有兩種方式,分別是
- Handler.sendMessage()
- 使用Handler.post() 下面先從第一種開始分析:
方式1:使用 Handler.sendMessage()
//通過匿名內部類 建立Handler類物件
private Handler mhandler = new Handler(){
// 通過複寫handlerMessage()從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需執行的UI操作
}
};
---------->>開始原始碼分析
public Handler() {
this(null, false);
// ->>此處this指代的就是當前的Handler例項,呼叫有參構造
}
public Handler(Callback callback, boolean async) {
...// 無關程式碼我就不貼了
// 1. 指定Looper物件
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// Looper.myLooper()作用:獲取當前執行緒的Looper物件;若執行緒無Looper物件則丟擲異常
// 可通過執行Loop.getMainLooper()方法獲得主執行緒的Looper物件
// 2. 繫結訊息佇列物件(MessageQueue)
mQueue = mLooper.mQueue;
// 獲取該Looper物件中儲存的訊息佇列物件(MessageQueue)
// 至此,完成了handler 與 Looper物件中MessageQueue的關聯
}
複製程式碼
- 從上面的原始碼來看,當我們建立Handler物件後,通過Handler的構造方法系統就已經幫我們自動繫結了looper和對應的MessageQueue訊息佇列。我們只需拿著這個Handler物件執行我們所需的操作就可以了
- 但是,你肯定有疑問了,當前執行緒的Looper物件 & 對應的訊息佇列物件(MessageQueue) 是哪來的呢?我既沒有獲取也沒有建立啊?
public static void main(String[] args) {
... // 無關的程式碼
Looper.prepareMainLooper();
// 1. 為主執行緒建立1個Looper物件,同時生成1個訊息佇列物件(MessageQueue)
ActivityThread thread = new ActivityThread();
// 2. 建立主執行緒
Looper.loop();
// 3. 自動開啟 訊息迴圈
}
複製程式碼
-
我們可以看到,其實在Android應用程式啟動時,會預設建立1個主執行緒(ActivityThread,也叫UI執行緒) ,建立ActivityThread的時候,會自動呼叫ActivityThread的1個靜態的main()方法 = 應用程式的入口,而main()方法內則會自動呼叫Looper.prepareMainLooper()為主執行緒生成1個Looper物件和MessageQueue佇列。
-
而Handler物件建立時若不指定looper則預設繫結主執行緒的looper,從而可以執行主執行緒的UI更新操作。
-
若是在子執行緒中建立Handler例項,則需要指定looper了,所以就用上了Loop.getMainLooper()方法來獲得主執行緒的Looper物件。
方式1: 使用Handler.post()
public void dispatchMessage(Message msg) {
// 1. 若msg.callback屬性不為空,則代表使用了post(Runnable r)傳送訊息
// 則執行handleCallback(msg),即回撥Runnable物件裡複寫的run()
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 2. 若msg.callback屬性為空,則代表使用了sendMessage(Message msg)傳送訊息,即回撥複寫的handleMessage(msg)
handleMessage(msg);
}
}
public void handleMessage(Message msg) {
... // 建立Handler例項時複寫
}
複製程式碼
從以上原始碼來看,使用Handler.post()時,系統會自動回撥Runnable物件裡複寫的run()方法,將其打包成msg物件, 實際上和sendMessage(Message msg)傳送方式相同。
至此,關於Handler的非同步訊息傳遞機制的解析就完成了。
7.關於Handler 記憶體洩露的原因
-
在Android開發中,記憶體洩露是 十分常見的
-
其中一種情況就是在Handler中發生的記憶體洩露
-
為什麼會發生記憶體洩漏? 1.Handler的一般用法 : 新建Handler子類(內部類) 、匿名Handler內部類,而在我們編寫程式碼的時候,其實編譯器就會提示我們這種操作可能會發生記憶體洩漏,在android studio中就是這塊程式碼會變黃。 2.提示的原因是
-
該Handler類由於未設定為 靜態類,從而導致了記憶體洩露
-
最終的記憶體洩露發生在Handler類的外部類:XXXActivity類中
3.記憶體洩漏的原因 首先我們先要了解一些其他的知識點。
- 主執行緒的Looper物件的生命週期 = 應該應用程式的生命週期
- 在Java中,非靜態內部類 & 匿名內部類都預設持有 外部類的引用,
而在Handler處理訊息的時候,Handler必須處理完所有訊息才會與外部類解除引用關係,如果此時外部Activity需要提前被銷燬了,而Handler因還未完成訊息處理而繼續持有外部Activity的引用。由於上述引用關係,垃圾回收器(GC)便無法回收MainActivity,從而造成記憶體洩漏。
8.如何解決Handler記憶體洩漏
1.靜態內部類+弱引用
將Handler的子類設定成 靜態內部類,同時,還可加上 使用WeakReference弱引用持有Activity例項。 原因:弱引用的物件擁有短暫的生命週期。在垃圾回收器執行緒掃描時,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。
// 設定為:靜態內部類
private static class FHandler extends Handler{
// 定義 弱引用例項
private WeakReference<Activity> reference;
// 在構造方法中傳入需持有的Activity例項
public FHandler(Activity activity) {
// 使用WeakReference弱引用持有Activity例項
reference = new WeakReference<Activity>(activity); }
// 複寫handlerMessage()
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
//更新UI
break;
case 2:
//更新UI
break;
}
複製程式碼
2.當外部l類結束生命週期時,清空Handler內訊息佇列
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
// 外部類Activity生命週期結束時,同時清空訊息佇列 & 結束Handler生命週期
}
複製程式碼
推薦使用上述解決方法一,以保證保證Handler中訊息佇列中的所有訊息都能被執行
總結
本文主要講述了Handler的基本原理和使用方法,以及造成記憶體洩漏的原因和解決方案。
歡迎關注作者darryrzhong,更多幹貨等你來拿喲.
請賞個小紅心!因為你的鼓勵是我寫作的最大動力!
更多精彩文章請關注