使用过安卓的人都能体会到安卓的消息机制-handler机制使用起来非常的方便,特别是在处理线程间通信时更能体会到这点,那么如果我们离开了安卓的环境,比如使用纯java时能否使用这种机制呢?
答案当然是肯定的了,安卓的消息机制是使用基于生产者消费者模式实现的消息队列来实现的,我们只需要使用java按照这种思路来实现就行了。
话不多说,直接上代码。
首先是Handler:
package com.example.handler;
public class Handler {
Looper mLooper;
MessageQueue queue;
private Callback mCallback;
public Handler() {
this(null);
}
public Handler(Callback callback) {
mCallback = callback;
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new IllegalStateException("cannot create handler with null looper");
}
queue = mLooper.queue;
}
public interface Callback {
public boolean handleMessage(Message msg);
}
public void handleMessage(Message msg) {
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
public void sendMessage(Message msg) {
sendMessageDelayed(msg, 0);
}
public void sendMessageDelayed(Message msg, long delayedTime) {
long time = System.currentTimeMillis() + delayedTime;
sendMessageAtTime(msg, time);
}
public void sendMessageAtTime(Message msg, long time) {
msg.when = time;
queue.enqueue(msg);
//System.out.println("sendMessageAtTime, long = " + time);
}
public void post(Runnable r) {
Message msg = getPostMessage(r);
sendMessage(msg);
}
public void postDelayed(Runnable r, long delayedTime) {
Message msg = getPostMessage(r);
sendMessageDelayed(msg, delayedTime);
}
private Message getPostMessage(Runnable r) {
Message msg = Message.obtain(this);
msg.callback = r;
return msg;
}
private void handleCallback(Message msg) {
msg.callback.run();
}
}
handler的代码非常简单,与安卓的handler逻辑基本一样,所以不多解释了,下面看下Looper和MessageQueue的实现。
先是Looper的实现,Looper的代码如下:
package com.example.handler;
public class Looper {
private static ThreadLocal sThreadLocal = new ThreadLocal();
MessageQueue queue;
private Looper() {
queue = new MessageQueue();
}
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new IllegalStateException("each thread should not has more than one looper");
}
sThreadLocal.set(new Looper());
}
public static Looper myLooper() {
return sThreadLocal.get();
}
public static void loop() {
Looper looper = myLooper();
MessageQueue mMessageQueue = looper.queue;
for (; ;) {
Message msg = mMessageQueue.next();
if (msg == null) {
return;
}
msg.target.dispatchMessage(msg);
msg.recyle();
}
}
}
Looper中用到了ThreadLocal,ThreadLocal的原理是保存每个线程的某个变量,各个线程之间互不干扰,简单点说就是每个线程调用ThreadLocal.set(Object obj)和ThreadLocal.get得到的是该线程的成员obj。想更详细的了解ThreadLocal的实现原理的可以看下这篇文章。
然后是MessageQueue,其实现如下:
package com.example.handler;
import java.util.concurrent.DelayQueue;
public class MessageQueue {
Message mMessages;
DelayQueue queue = new DelayQueue<>();
public void enqueue(Message msg) {
queue.offer(msg);
System.out.println("enqueue msg: what = " + msg.what);
}
public Message next() {
Message msg = null;
try {
msg = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println("next msg: what = " + msg.what);
return msg;
}
}
这个类可以说是消息机制实现的最关键的类,MessageQueue代码看起来非常少,这是因为绝大部分工作都交给了DelayQueue,DelayQueue是一个支持延时获取元素的无界阻塞队列,队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
我们先来看一下Delayed接口,Delayed接口的定义很简单:
public interface Delayed extends Comparable {
long getDelay(TimeUnit unit);
}
从Delayed定义我们知道,队列中的消息实现Delayed接口的同时也需要实现Comparable接口,这样做的好处是:
1.实现Comparable接口可以根据消息的定时发送时间进行排序;
2.实现Delayed接口可以让当前队列头部的消息阻塞直到定时时间到期。
下面是Message的实现了,让Message实现Delayed接口即可放入DelayQueue中:
package com.example.handler;
import java.util.List;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class Message implements Delayed {
public int what;
public int arg1;
public int arg2;
public Object obj;
//微秒
long when;
List data;
Handler target;
Runnable callback;
Message next;
//使用消息池,实现反复利用
private static Object POOL_LOCK = new Object();
private static final int MAX_POOL_SIZE = 50;
private static int poolSize = 0;
private static Message sPool;
public Message() {
}
public static Message obtain(Handler target) {
Message msg = obtain();
msg.target = target;
return msg;
}
public static Message obtain() {
if (sPool != null) {
synchronized (POOL_LOCK) {
Message m = sPool;
sPool = sPool.next;
m.next = null;
poolSize--;
return m;
}
}
return new Message();
}
public void recyle() {
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
data = null;
target = null;
callback = null;
next = null;
synchronized(POOL_LOCK) {
if (poolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
poolSize++;
}
}
}
@Override
public long getDelay(TimeUnit timeUnit) {//实现Delayed接口
long result = timeUnit.convert((when - System.currentTimeMillis()) * 1000, TimeUnit.NANOSECONDS);
//System.out.println("getDelay: result=" + result);
return result;
}
@Override
public int compareTo(Delayed delayed) {//实现Comparable接口
Message msg = (Message) delayed;
if (this.when > msg.when) {
return 1;
} else if (this.when < msg.when){
return -1;
}
return 0;
}
}
代码很简单,其中也实现了消息池,就是一开始创建消息时进行调用构造函数,消息处理完后进行回收并放入池子里,以后再创建消息时优先去池子里取,这样避免了对象的反复创建和销毁,从而提高了效率。其中comparaTo接口的实现表明是根据Message的when属性来进行排序的,getDelay接口是通过比较when与当前时间,若返回0则表明该消息是时候出列了。
最后我们写个测试case来验证下:
package com.example.handler;
public class Main {
public static void main(String[] args) {
new Main().start();
}
private void start() {
Looper.prepare();
prepareTestThread();
Looper.loop();
throw new RuntimeException("exit unexpected!");
}
private Handler handler;
private void prepareTestThread() {
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
System.out.println("time:" + System.currentTimeMillis() + " Thread " + Thread.currentThread().getName()
+ " receive msg:{" + msg.obj + " what=" + msg.what + " when=" + msg.when + "}");
}
};
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain(handler);
msg.what = 1;
msg.obj = "msg from " + Thread.currentThread().getName();
//handler.sendMessage(msg);
handler.sendMessageDelayed(msg, 100);
}
});
thread1.setName("thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain(handler);
msg.what = 2;
msg.obj = "msg from " + Thread.currentThread().getName();
//handler.sendMessage(msg);
handler.sendMessageDelayed(msg, 200);
}
});
thread2.setName("thread2");
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain(handler);
msg.what = 3;
msg.obj = "msg from " + Thread.currentThread().getName();
//handler.sendMessage(msg);
handler.sendMessageDelayed(msg, 300);
}
});
thread3.setName("thread3");
thread1.start();
thread2.start();
thread3.start();
}
}
输出结果如下:
enqueue msg: what = 1
enqueue msg: what = 2
enqueue msg: what = 3
time:1544769011480 Thread main receive msg:{msg from thread1 what=1 when=1544769011480}
time:1544769011580 Thread main receive msg:{msg from thread2 what=2 when=1544769011580}
time:1544769011680 Thread main receive msg:{msg from thread3 what=3 when=1544769011680}
从结果可以看出,我们三个子线程分别往主线程发送的消息都准确的收到了且消息的排序和送达的时间点都符合我们的预期。
至此,我们使用java实现了安卓的handler消息机制。
点击这里下载代码。