事情的起因还是因为一次面试经历。
面试官:“说一下android的handler机制。”
经过几次面试之后发现几乎每个面试官都会问到这个问题,真的都快被问烦了好吧,于是用飞快的速度把之前都快说烂了答案的又说了一遍,好不容易都说完了,这时候面试官的操作来了,直接拿过来几张白纸。
“恩,说的不错,来,那你试试能不能尝试用伪代码实现一下。”
还有这种操作,当时我就蒙了,心想,要不就试试吧,反正都用了不知道多少次了。
一开始写想的思路就是启动一个线程开启一个死循环仿照looper来一直轮训消息队列,然后没有的话就阻塞线程,等消息来了再唤醒继续取出消息处理,但到了postdelay()这个方法时,延时处理就不知道怎么搞了,这把我难住了,因为需要根据时间线来取消息,比如先插入个等待2秒的后插入一个等待1秒的,那么就要时间少的最先运行,想了一会,一时没想出好的方法,然后就下一题了。。。。
回来就想,用的熟练跟自己能实现出来还是有差别呀,之前咋就没想过如果自己实现的话会是什么样子呢。
回来之后好好翻了翻android的源码,痛定思痛。花了半天,终于按照android的handler机制的思路自己实现了出来。
下面就是我的实现过程,比起android官方的肯定要简单点,但主要的线程通讯和延迟处理都实现出来了。
handler message 机制的由来
android 的 UI 操作并不是线程安全的,我们知道,在android中子线程可以有好多个,但是如果每个线程都可以对ui进行访问,我们的界面可能就会变得混乱不堪,这样多个线程操作同一资源就会造成线程安全问题,当然,需要解决线程安全问题的时候,我们第一想到的可能就是加锁,但是加锁会降低运行效率,所以android出于性能的考虑,并没有使用加锁来进行ui操作的控制。
但问题又回来了,既然想要性能,有想要解决子线程更新ui的需求,那么应该怎么办呢?
因此android规定,ui线程只能由MainThread进行访问,其他菜鸡子线程想更新ui只能先写报告(handler.sendmessage())交给MainThread代为处理。
而这个写报告并由MainThread代为更新UI的过程,就是使用handler message机制来实现的了。
因此handler message 机制也是对于UI刷新线程安全问题的一个高性能的解决方案,并且这套机制不仅仅适用于主线程和子线程通讯,它适用于任何一对多线程间的通讯。
主要构成成员
- Handler:消息处理,消息循环从消息队列中取出消息后要对消息进行处理。
- Message:消息的实体
- MessageQueue:消息队列
- Looper:消息循环
实现代码
Looper.java
package com.miqt.test;
import com.miqt.test.MessageQueue.DelayRunnable;
public class Looper {
MessageQueue queue;
public Looper() {
queue = new MessageQueue();
}
public static void loop() {
Looper me = myLooper();
while (true) {
me.queue.next().run();
}
}
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
// 检查线程中是否已经有一个Looper循环了
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
public static Looper myLooper() {
return sThreadLocal.get();
}
}
Looper实现了核心的prepare方法和loop方法,loop开启轮训之后则是开启死循环一直取消息来处理,这里的queue.next(),实际上类似于一个阻塞方法,android源码的实现方式我看是放在了native层中,而java原生不是,java原生的代码它实际上是首先会调用DelayRunnable.getDelay()取得一个纳秒值,然后一直减去系统的时间,直到减到小于0返回DelayRunnable对象,看一眼这块的实现源码:
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
final long deadline = System.nanoTime() + nanosTimeout;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return deadline - System.nanoTime();
}
Message.java
package com.miqt.test;
public class Message {
public int what;
public Object obj1;
public Object obj2;
public Handler target;
public Runnable callback;
public Message(int what, Object obj1, Object obj2) {
super();
this.what = what;
this.obj1 = obj1;
this.obj2 = obj2;
}
public Message(int what) {
super();
this.what = what;
}
public Message(Runnable run) {
super();
this.callback = run;
}
}
‘信封’实体message,我这里只实现了message传消息常用的变量,对于android源码中的消息池我没有实现。
MessageQueue.java
package com.miqt.test;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class MessageQueue {
DelayQueue<DelayRunnable> queue = new DelayQueue<DelayRunnable>();
// 入队
public void enqueue(final Message msg, long delayInMilliseconds) {
if (msg.target == null) {
throw new IllegalStateException("handler is null");
}
DelayRunnable runn = new DelayRunnable() {
@Override
public void run() {
msg.target.dispatchMessage(msg);
}
};
runn.setDelay(delayInMilliseconds);
queue.add(runn);
}
// 出队
public DelayRunnable next() {
try {
return queue.take();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
public abstract class DelayRunnable implements Delayed, Runnable {
private long delay;
public void setDelay(long delayInMilliseconds) {
this.delay = System.nanoTime() + TimeUnit.NANOSECONDS.convert(delayInMilliseconds, TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
DelayRunnable that = (DelayRunnable) o;
if (this.delay > that.delay) {
return 1;
} else if (this.delay < that.delay) {
return -1;
}
return 0;
}
@Override
public long getDelay(TimeUnit unit) {
long result = unit.convert(delay - System.nanoTime(), TimeUnit.NANOSECONDS);
return result;
}
}
}
消息队列只实现了队列的入队和出队,遵循消息先进先出并且需要根据时间优先出队的原则,使用了DelayQueue来储存消息,另外创建DelayRunnable实现了Delayed接口的compareTo方法和getDelay方法,compareTo方法用来比较消息在队列中的顺序,getDelay方法是用来决定是否出队处理,当返回值<=0时,则会出队处理。
Handler.java
package com.miqt.test;
public class Handler {
Looper mLooper;
MessageQueue mQueue;
private CallBack mCallback;
public Handler(Looper mLooper, CallBack callBack) {
super();
this.mLooper = mLooper;
this.mCallback = callBack;
mQueue = mLooper.queue;
}
public void post(Runnable r) {
Message msg = new Message(r);
if (msg.target == null) {
msg.target = this;
}
mQueue.enqueue(msg, 0);
}
public void postDelay(Runnable r, long delayMillis) {
Message msg = new Message(r);
if (msg.target == null) {
msg.target = this;
}
mQueue.enqueue(msg, delayMillis);
}
public void sendMessage(Message msg) {
if (msg.target == null) {
msg.target = this;
}
mQueue.enqueue(msg, 0);
}
public void handleMessage(Message message) {
}
private static void handleCallback(Message message) {
message.callback.run();
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
interface CallBack {
boolean handleMessage(Message msg);
}
}
最后就是handler了,它其实是最简单的,只负责调用之前封装好的就行了,我在这里按照android分发message的逻辑实现了dispatchMessage,这样的话调用起来几乎跟在android上一模一样,优先级也一样,先出发message里面的runnable运行,然后触发CallBack对象的handleMessage,如果返回false的话那么还会触发自己的方法public void handleMessage(Message message)。
到了这里就实现完成了,接下来就是测试一下是否可用。
这是我的测试代码。
package com.miqt.test;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;
public class Acty {
protected static final int HELLO = 0;
protected static Handler handler;
public static void main(String[] args) {
new Thread() {
public void run() {
Looper.prepare();
handler = new Handler(Looper.myLooper(), new MyCallBack());
Looper.loop();
};
}.start();
// 创建线程不定时发送message
test();
}
private static void test() {
new Thread() {
public void run() {
for (;;) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int type = (int) (Math.random() * 3);
switch (type) {
case 0:
System.out.println("[发送方]:sendMessage");
Message message = new Message(HELLO);
message.obj1 = "001" + Math.random();
handler.sendMessage(message);
break;
case 1:
System.out.println("[发送方]:post");
handler.post(new Runnable() {
@Override
public void run() {
System.out.println("[接受方]post");
}
});
break;
case 2:
long delaytime = (long) (Math.random() * 5000);
System.out.println("[发送方]:postDelay:" + String.valueOf(delaytime));
handler.postDelay(new Runnable() {
@Override
public void run() {
System.out.println("[接受方]postDelay" + delaytime);
}
}, delaytime);
break;
default:
break;
}
}
};
}.start();
}
public static class MyCallBack implements Handler.CallBack {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case HELLO:
String str = (String) msg.obj1;
System.out.println("[接受方]" + str + "开始处理");
System.out.println("[接受方]" + "hello,我来自消息队列");
System.out.println("[接受方]" + str + "处理完成,出队");
break;
default:
break;
}
return true;
}
}
}
开启了两个线程,一个线程作为接收处理消息的线程,一个线程负责随机一个时间并且用不同的方式来发送消息。
测试结果:
[发送方]:sendMessage
[接受方]0010.5516496029786089开始处理
[接受方]hello,我来自消息队列
[接受方]0010.5516496029786089处理完成,出队
[发送方]:post
[接受方]post
[发送方]:post
[接受方]post
[发送方]:sendMessage
[接受方]0010.45668723454926696开始处理
[接受方]hello,我来自消息队列
[接受方]0010.45668723454926696处理完成,出队
[发送方]:post
[接受方]post
[发送方]:sendMessage
[接受方]0010.43970565205942336开始处理
[接受方]hello,我来自消息队列
[接受方]0010.43970565205942336处理完成,出队
[发送方]:postDelay:3647
[发送方]:postDelay:4144
[发送方]:postDelay:497
[接受方]postDelay497
[发送方]:sendMessage
[接受方]0010.4489768565868292开始处理
[接受方]hello,我来自消息队列
[接受方]0010.4489768565868292处理完成,出队
[接受方]postDelay3647
[发送方]:post
[接受方]post
[发送方]:sendMessage
[接受方]0010.5013814373539061开始处理
[接受方]hello,我来自消息队列
[接受方]0010.5013814373539061处理完成,出队
[接受方]postDelay4144
[发送方]:postDelay:4358
可以看到不管是sendMessage、post还是postDelay,Handler都有条不紊的按照顺序进行处理,并且postDelay发出的消息会按照正确的顺序和时间点来运行。
通过这次的实现,感觉对android消息机制有了更深的了解,以后还是要多思考多学习呀。