自己動手擼一個Handler
一、關於Handler
Handler對於我們Android開發者來說應該是再熟悉不過了,這也是在Android中最重要的訊息機制,特別是在面試筆試時,Handler機制也是最常問到的話題。今天我們就來動手擼一個自己寫的Handler,用java層程式碼方式來實現,進一步來了解Handler線上程通訊過程中的作用。
二、問題
Handler機制也可以理解為執行緒間的訊息機制,如果我們自己來設計Handler實現執行緒間通訊,需要怎麼做呢?我們知道,在Handler機制中,最重要的幾個類:Handler
、Looper
、MessageQueue
、Message
、ThreadLocal
。那它們在具體實現中又有什麼作用呢?
三、思考
首先,從使用者角度來看,他的操作只有兩步:
- 在主執行緒建立Handler例項,並重寫
handleMessage
方法處理訊息。- 在子執行緒獲取Handler的引用呼叫
sendMessage
方法傳送訊息,在handleMessage
中即可處理該訊息。
那從設計者角度來看,我們要分清Handler
、Looper
、MessageQueue
、Message
、ThreadLocal
這幾個類都擔當了什麼職責:
- Handler 負責傳送和處理訊息
- Looper 訊息泵,也就是負責取出訊息交給
Handler
來處理。- MessageQueue 訊息佇列,負責存取訊息。
- Message 具體傳送的訊息。
- ThreadLocal 它主要用於做執行緒間的資料隔離用的,這裡它在每個執行緒中存放各自對應的
Looper
。
好了,簡單分析完各個類的作用,那我們開始挽起袖子擼程式碼吧。
四、實現
1、 Handler的實現
由於Handler
主要負責傳送和處理訊息,那我們主要實現它的sendMessage
、sendMessage
、dispatchMessage
三個方法,來處理訊息的傳送和接收:
public class Handler {
//訊息佇列
MessageQueue mQueue;
//Looper
Looper mLooper;
public Handler() {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
}
public final void sendMessage(Message msg){
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
queue.enqueueMessage(msg);
}else {
RuntimeException e = new RuntimeException(
this + " sendMessage() called with no mQueue");
throw e;
}
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
handleMessage(msg);
}
}
我們在Handler
的建構函式中獲取當前執行緒對應的looper,並取出Looper
中對應的訊息佇列儲存在成員變數中。sendMessage
方法中我們給Message
的target
變數賦值為this
,也就是表明了Message
是由當前的Handler
來負責處理的,之後呼叫enqueueMessage
方法將訊息存入訊息佇列中。而dispatchMessage
方法我們實現比較簡單,負責呼叫handleMessage
來處理訊息。
2、 Looper的實現
Looper
主要負責取出訊息交由Handler處理,我們主要來實現prepare
、loop
方法:
public class Looper {
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
MessageQueue mQueue;
private Looper() {
mQueue = new MessageQueue();
}
public static Looper myLooper() {
return sThreadLocal.get();
}
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException(
"Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
public static void loop() {
Looper me = myLooper();
if (me == null) {
throw new RuntimeException(
"No Looper; Looper.prepare() wasn't called on this thread.");
}
MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next();
if (msg == null || msg.target == null)
continue;
//轉發給handler
msg.target.dispatchMessage(msg);
}
}
}
在Looper
的建構函式中我們建立了對應的訊息佇列來存取訊息,並且在prepare
方法中存入ThreadLocal
當前執行緒的Looper
,loop
方法從當前執行緒的Looper
的訊息佇列中取出訊息,最終呼叫msg.target.dispatchMessage(msg)
交友之前傳送訊息的Handler
來處理訊息。
3、Message的實現
Message
的實現比較簡單:
public final class Message {
//處理該訊息的Handler
Handler target;
public int what;
public Object obj;
@Override
public String toString() {
return obj.toString();
}
}
4、MessageQueue訊息佇列的實現
在訊息佇列的實現中我們主要考慮幾個問題:
1. 用什麼資料結構存放訊息,存放資料大小有限制。
2. 當next()
方法取出訊息時,訊息佇列沒有訊息,該方法應阻塞。
3. 當enqueueMessage
方法存放訊息時,訊息大於存放訊息限制大小,應阻塞。
//訊息佇列
public class MessageQueue {
//互斥鎖
Lock lock;
//條件變數
Condition mEmptyQueue;
Condition mFullQueue;
//訊息
Message[] mMessages;
//裝入 和取出訊息的下標
int putIndex;
int takeIndex;
//記錄數 用於判斷是否繼續生產和消費
int count;
public MessageQueue(){
//初始化50個訊息
mMessages = new Message[50];
lock = new ReentrantLock();
//標示
mEmptyQueue = lock.newCondition();
mFullQueue = lock.newCondition();
}
//生產者 子執行緒
final void enqueueMessage(Message msg){
//新增至訊息佇列
try{
lock.lock();
while(count == mMessages.length){
try {
mFullQueue.await();
} catch (Exception e) {
e.printStackTrace();
}
}
mMessages[putIndex] = msg;
putIndex = (++putIndex == mMessages.length ? 0 : putIndex);
count++;
//通知主執行緒繼續執行
mEmptyQueue.signalAll();
}finally{
lock.unlock();
}
}
//消費者 主執行緒
final Message next(){
//取出訊息
Message message = null;
try{
lock.lock();
//取到最後一個
while (count == 0) {
try {
mEmptyQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
message = mMessages[takeIndex];
mMessages[takeIndex] = null;
takeIndex = (++takeIndex == mMessages.length ? 0 : takeIndex);
count--;
//通知子執行緒
mFullQueue.signalAll();
}finally{
lock.unlock();
}
return message;
}
}
這裡的next
和enqueueMessage
是典型的生產者、消費者的關係,為防止出現錯亂我們給兩個方法都加上Lock
鎖,當enqueueMessage
方法存放訊息時如果當前佇列訊息滿了,則呼叫mFullQueue.await();
進行等待訊息處理,當向訊息佇列中存放訊息後,也就是說訊息佇列不為空了,呼叫mEmptyQueue.signalAll();
通知next()
方法來處理訊息。
至此,我們的Handler訊息處理過程已經基本完成了,下面我們測試下看看:
5、測試
public class Test {
public static void main(String[] args) {
//初始化Looper
Looper.prepare();
final Handler hander = new Handler(){
public void handleMessage(Message msg) {
System.out.println(Thread.currentThread().getName() + "--receiver--" + msg.toString());
};
};
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
public void run() {
while (true) {
Message msg = new Message();
msg.what = 0;
synchronized (UUID.class) {
msg.obj = Thread.currentThread().getName()+"--send---"+UUID.randomUUID().toString();
}
System.out.println(msg);
hander.sendMessage(msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
//開始訊息迴圈
Looper.loop();
}
}
看下測試結果:
Thread-0--send---e4ed9a81-3477-4f5d-a663-1d30626d93b5
Thread-8--send---4c403131-ec14-406c-b57b-a4943dfa93ac
Thread-7--send---9752a85d-9517-4607-a54f-92c2342b7a28
Thread-6--send---7d4ee443-3ab5-4c4e-aac5-eeafc26b78d9
Thread-9--send---70ba7292-1ff4-404d-974e-dfedb1a3fa71
Thread-4--send---614e07e6-bc39-45be-93b0-6996de7f159e
Thread-2--send---7bfaa831-a31b-457a-82cd-145a9d98d351
Thread-5--send---8ffd7327-6ddb-4088-93e6-1304fc926814
Thread-1--send---f6d5e373-88b0-44e9-ab51-f95808acb068
main--receiver--Thread-0--send---e4ed9a81-3477-4f5d-a663-1d30626d93b5
main--receiver--Thread-8--send---4c403131-ec14-406c-b57b-a4943dfa93ac
main--receiver--Thread-7--send---9752a85d-9517-4607-a54f-92c2342b7a28
main--receiver--Thread-6--send---7d4ee443-3ab5-4c4e-aac5-eeafc26b78d9
main--receiver--Thread-9--send---70ba7292-1ff4-404d-974e-dfedb1a3fa71
main--receiver--Thread-4--send---614e07e6-bc39-45be-93b0-6996de7f159e
main--receiver--Thread-2--send---7bfaa831-a31b-457a-82cd-145a9d98d351
main--receiver--Thread-5--send---8ffd7327-6ddb-4088-93e6-1304fc926814
Thread-3--send---56f8b613-99fa-4ef2-a4b9-c762c4d0cd27
main--receiver--Thread-1--send---f6d5e373-88b0-44e9-ab51-f95808acb068
main--receiver--Thread-3--send---56f8b613-99fa-4ef2-a4b9-c762c4d0cd27
測試成功!!我們自己的Handler也可以正常處理訊息啦~
五、總結
Handler
原始碼的實現過程要比我們自己的複雜很多,特別是訊息處理的細節,呼叫了底層C++的程式碼。但實現的整體思路和我們是一樣的,通過動手實踐一次,加深對Handler
的理解,對我們認識和處理訊息機制的問題大有裨益。