自己动手撸一个Handler
Posted zhangke3016
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自己动手撸一个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
的理解,对我们认识和处理消息机制的问题大有裨益。
以上是关于自己动手撸一个Handler的主要内容,如果未能解决你的问题,请参考以下文章