一、關(guān)于Handler
Handler對于我們Android開發(fā)者來說應(yīng)該是再熟悉不過了,這也是在Android中最重要的消息機制次兆,特別是在面試筆試時,Handler機制也是最常問到的話題锹锰。今天我們就來動手擼一個自己寫的Handler芥炭,用java層代碼方式來實現(xiàn),進一步來了解Handler在線程通信過程中的作用恃慧。
二园蝠、問題
Handler機制也可以理解為線程間的消息機制,如果我們自己來設(shè)計Handler實現(xiàn)線程間通信痢士,需要怎么做呢彪薛?我們知道,在Handler機制中怠蹂,最重要的幾個類:Handler
善延、Looper
、MessageQueue
城侧、Message
挚冤、ThreadLocal
。那它們在具體實現(xiàn)中又有什么作用呢赞庶?
三训挡、思考
首先,從使用者角度來看歧强,他的操作只有兩步:
- 在主線程創(chuàng)建Handler實例澜薄,并重寫
handleMessage
方法處理消息。
- 在子線程獲取Handler的引用調(diào)用
sendMessage
方法發(fā)送消息摊册,在handleMessage
中即可處理該消息肤京。
那從設(shè)計者角度來看,我們要分清Handler
、Looper
忘分、MessageQueue
棋枕、Message
、ThreadLocal
這幾個類都擔(dān)當了什么職責(zé):
- Handler 負責(zé)發(fā)送和處理消息
- Looper 消息泵妒峦,也就是負責(zé)取出消息交給
Handler
來處理重斑。- MessageQueue 消息隊列,負責(zé)存取消息肯骇。
- Message 具體發(fā)送的消息窥浪。
- ThreadLocal 它主要用于做線程間的數(shù)據(jù)隔離用的,這里它在每個線程中存放各自對應(yīng)的
Looper
笛丙。
好了漾脂,簡單分析完各個類的作用,那我們開始挽起袖子擼代碼吧胚鸯。
四骨稿、實現(xiàn)
1、 Handler的實現(xiàn)
由于Handler
主要負責(zé)發(fā)送和處理消息姜钳,那我們主要實現(xiàn)它的sendMessage
坦冠、sendMessage
、dispatchMessage
三個方法傲须,來處理消息的發(fā)送和接收:
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
的構(gòu)造函數(shù)中獲取當前線程對應(yīng)的looper,并取出Looper
中對應(yīng)的消息隊列保存在成員變量中趟脂。sendMessage
方法中我們給Message
的target
變量賦值為this
泰讽,也就是表明了Message
是由當前的Handler
來負責(zé)處理的,之后調(diào)用enqueueMessage
方法將消息存入消息隊列中昔期。而dispatchMessage
方法我們實現(xiàn)比較簡單已卸,負責(zé)調(diào)用handleMessage
來處理消息。
2硼一、 Looper的實現(xiàn)
Looper
主要負責(zé)取出消息交由Handler處理累澡,我們主要來實現(xiàn)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;
//轉(zhuǎn)發(fā)給handler
msg.target.dispatchMessage(msg);
}
}
}
在Looper
的構(gòu)造函數(shù)中我們創(chuàng)建了對應(yīng)的消息隊列來存取消息般贼,并且在prepare
方法中存入ThreadLocal
當前線程的Looper
愧哟,loop
方法從當前線程的Looper
的消息隊列中取出消息,最終調(diào)用msg.target.dispatchMessage(msg)
交友之前發(fā)送消息的Handler
來處理消息哼蛆。
3蕊梧、Message的實現(xiàn)
Message
的實現(xiàn)比較簡單:
public final class Message {
//處理該消息的Handler
Handler target;
public int what;
public Object obj;
@Override
public String toString() {
return obj.toString();
}
}
4、MessageQueue消息隊列的實現(xiàn)
在消息隊列的實現(xiàn)中我們主要考慮幾個問題:
- 用什么數(shù)據(jù)結(jié)構(gòu)存放消息腮介,存放數(shù)據(jù)大小有限制肥矢。
- 當
next()
方法取出消息時,消息隊列沒有消息叠洗,該方法應(yīng)阻塞甘改。 - 當
enqueueMessage
方法存放消息時旅东,消息大于存放消息限制大小,應(yīng)阻塞十艾。
//消息隊列
public class MessageQueue {
//互斥鎖
Lock lock;
//條件變量
Condition mEmptyQueue;
Condition mFullQueue;
//消息
Message[] mMessages;
//裝入 和取出消息的下標
int putIndex;
int takeIndex;
//記錄數(shù) 用于判斷是否繼續(xù)生產(chǎn)和消費
int count;
public MessageQueue(){
//初始化50個消息
mMessages = new Message[50];
lock = new ReentrantLock();
//標示
mEmptyQueue = lock.newCondition();
mFullQueue = lock.newCondition();
}
//生產(chǎn)者 子線程
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++;
//通知主線程繼續(xù)執(zhí)行
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
是典型的生產(chǎn)者抵代、消費者的關(guān)系,為防止出現(xiàn)錯亂我們給兩個方法都加上Lock
鎖疟羹,當enqueueMessage
方法存放消息時如果當前隊列消息滿了主守,則調(diào)用mFullQueue.await();
進行等待消息處理,當向消息隊列中存放消息后榄融,也就是說消息隊列不為空了参淫,調(diào)用mEmptyQueue.signalAll();
通知next()
方法來處理消息。
至此愧杯,我們的Handler消息處理過程已經(jīng)基本完成了涎才,下面我們測試下看看:
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();
}
//開始消息循環(huán)
Looper.loop();
}
}
看下測試結(jié)果:
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
測試成功A拧耍铜!我們自己的Handler也可以正常處理消息啦~
五、總結(jié)
Handler
源碼的實現(xiàn)過程要比我們自己的復(fù)雜很多跌前,特別是消息處理的細節(jié)棕兼,調(diào)用了底層C++的代碼。但實現(xiàn)的整體思路和我們是一樣的抵乓,通過動手實踐一次伴挚,加深對Handler
的理解,對我們認識和處理消息機制的問題大有裨益灾炭。