handler消息机制是安卓各个进程的核心,是程序可以持续运行而不退出的原因所在,但是原理却很简单,我们完全可以自己手写一个。下面我们的思路是,分析子线程中handler用法,分析源码,找出handler源码里的关键类,最后然后手写一个handler。
第一部分:使用handler
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
//准备looper
Looper.prepare();
//创建hanlder
mHanler=new Handler(Looper.myLooper());
//开启循环
Looper.loop();
}
});
thread.start();
分四步,第一步创建looper,第二步创建handler,第三步开启循环,第四步就是发送消息了。
第二部分:源码分析
1.Looper.prepare();干了什么?
private static void prepare(boolean quitAllowed) {
//判断当前线程是否已经有looper了,设置过了就不能再设置了
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//新建一个looper,并且绑定到当前线程
sThreadLocal.set(new Looper(quitAllowed));
}
干了两件事情:
第一:判断当前线程是否已经有looper了,设置过了就不能再设置了
第二:新建一个looper,并且绑定到当前线程
这里有一个关键而且最核心的类之一 ThreadLocal ,先说一下这个类作用,保存线程私有数据,就是说 ThreadLocal.set()的数据是线程隔离的,这也就是为什么不论我们在任何线程用如下方法去创建handler发送消息,最后都是在主线程处理消息的原因,因为下面方法中所用的looper是在主线程中创建的,绑定在了主线程:
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
new Handler(Looper.getMainLooper()).sendMessage(...);
}
});
thread.start();
下面我们再分析new Looper();干了什么事呢?
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
}
很简单,创建了消息队列,没了。但是结合上面分析,如果looper是在线程A中,那么这个消息队列也是在线程A中。
那么这个准备方法就全部分析完了,总结一下:创建了一个和当前线程绑定到一起的一个Looper和消息队列。
2.先来分析第三步 Looper.loop()方法。
public static void loop() {
//获取绑定在当前线程的looper
final Looper me = myLooper();
//当前线程没有绑定looper不行
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//取出looper中的消息队列
final MessageQueue queue = me.mQueue;
//死循环,除非程序崩溃或者looper退出
for (;;) {
//阻塞操作,取出消息
Message msg = queue.next(); // might block
//消息为空就退出死循环,也就是looper清空消息退出
if (msg == null) {
return;
}
try {
//调用msg.target处理消息
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
}
//消息回收复用
msg.recycleUnchecked();
}
}
很简单几部操作:
第一:拿到当前线程上一步绑定的looper,没拿到就报错了
第二:拿到looper中的消息队列
第三:死循环,不停的从消息队列中取消息,如果消息队列消息为空,则退出死循环,looper退出,否则取出消息后调用msg.target 处理。
第四:处理完的消息回收存放,已备复用。
这里重点说一下第三步的取消息,这个是一个阻塞操作,我们进去看看:
Message next() {
//当这个消息队列被垃圾回收器回收了之后就退出
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//表示下一个需要处理消息的间隔时间
int nextPollTimeoutMillis = 0;
for (;;) {
//核心代码,这也就是阻塞为什么不会退出的关键代码
if (nextPollTimeoutMillis != 0) {
//这个方法什么意思呢?
/**
* Flush any Binder commands pending in the current thread to the kernel
* driver. This can be
* useful to call before performing an operation that may block for a long
* time, to ensure that any pending object references have been released
* in order to prevent the process from holding on to objects longer than
* it needs to.
*/
//翻译过来大概意思就是说,会通知虚拟内核,将当前线程暂时挂起,直到再次被唤醒。
//那挂起多长时间呢?答案是,你不叫醒他就会一直挂起
Binder.flushPendingCommands();
}
//相当于调用了Thread.sleep(),但是有两点区别:
//1.nextPollTimeoutMillis时间到了会自动唤醒Binder,自动唤醒机制
//2.不会阻塞当前线程
//3.nextPollTimeoutMillis 有三个值,-1表示一直阻塞知道被主动唤醒,0表示跳过,大于0表示具体的阻塞时间自动唤醒
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 找到下一条需要处理的消息
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null) {
if (now < msg.when) {
//计算需要阻塞的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获取消息,并返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 表示没有消息了
nextPollTimeoutMillis = -1;
}
// 退出消息队列
if (mQuitting) {
dispose();
return null;
}
}
nextPollTimeoutMillis = 0;
}
}
这里有两个关键的点:
第一:Binder.flushPendingCommands();表示通知系统内核我要做阻塞操作了,你可以将当前线程挂起了,知道我喊你行了你再醒过来。
第二:nativePollOnce(ptr, nextPollTimeoutMillis); 阻塞操作,nextPollTimeoutMillis 有三个值,-1表示一直阻塞直到被主动唤醒,0表示跳过,大于0表示具体的阻塞时间自动唤醒
那么什么时候会出现nextPollTimeoutMillis为-1呢?当然是消息队列为空的时候,没有任何消息需要执行了,那为-1的时候什么时候又被唤醒的呢?当然是我们又重新发送消息的时候,向消息队列插入消息时把他给唤醒的,看具体代码:
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
//上面英文说的很明确了,新的头节点(也就是说以前是空队列),叫醒沉睡的内核 needWake=true
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//将消息插入队列中
}
// We can assume mPtr != 0 because mQuitting is false.
//叫醒沉睡的内核
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
好了,回顾一下这个loop过程,不停的去消息队列中取消息,然后调用自己的targe属性去处理,而targe属性是谁呢?就是发送的handler,所以就又交给handler处理了,这就是谁发送谁处理。这里我们把准备和如何取消息,交给谁处理分析完了,那就差发送消息了。
3.handler.sendMessage();
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
还是干了两件事:
第一:给msg的target属性赋值,就是自己
第二:调用messagequeue的插入队列方法,上面分析过了,就是按照时间插入链表相应位置
好了,至此handler分析完成,总结一下,就是handler发消息,并且把自己绑定给消息身上,发送的消息放到消息队列中,Looper在不停的从消息队列中取消息,交给hander自己处理。
第三部分:自己手写handler
分析:几个关键类
Handler:发送消息、处理消息
Message:消息
MessageQueue:消息队列,用于操作消息队列中的消息
Looper:不停的从消息队列中取出消息,没有消息则阻塞当前线程,使程序不退出
ThreadLocal:线程隔离作用,将looper和当前线程绑定,从而使所有用当前线程looper创建的handler发送的消息都在当前线程处理,达到线程通信目的。
那来吧,上代码:
消息队列:
public interface IMessageQueue {
void pushMessage(Message message);
Message next();
}
public class BlockMessageQueue implements IMessageQueue {
private BlockingQueue<Message> messageBlockingQueue;
public BlockMessageQueue() {
this.messageBlockingQueue = new LinkedBlockingQueue<>(12);
}
@Override
public void pushMessage(Message message) {
try {
messageBlockingQueue.put(message);
System.out.println(messageBlockingQueue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public Message next() {
try {
return messageBlockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
自定义Message吧:
public class Message {
private String msg;
private MyHandler targe;
public Message(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void setTarge(MyHandler targe) {
this.targe = targe;
}
public MyHandler getTarge() {
return targe;
}
}
核心looper来了:
public class Looper {
private static Looper sMainLooper;
private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sLooper;
private IMessageQueue sMessageQueue;
public static Looper getMainLooper() {
return sMainLooper;
}
public static Looper getsLooper() {
return myLooper();
}
public Looper(IMessageQueue messageQueue) {
sMessageQueue = messageQueue;
}
public static void prepare() {
if (null != sThreadLocal.get()) {
throw new RuntimeException("一个线程只能有一个looper");
}
if (null == sMainLooper) {
sMainLooper = new Looper(new BlockMessageQueue());
sThreadLocal.set(sMainLooper);
} else {
sLooper = new Looper(new BlockMessageQueue());
sThreadLocal.set(sLooper);
}
}
public static Looper myLooper() {
return sThreadLocal.get();
}
public static void push(Looper looper, Message message) {
looper.sMessageQueue.pushMessage(message);
}
public static void loop() {
while (true) {
Message next = myLooper().sMessageQueue.next();
if (null == next) {
break;
}
next.getTarge().handleMessage(next);
}
}
}
负责发送消息的handler来了:
public class MyHandler {
private Looper mLooper;
public MyHandler() {
mLooper = Looper.getsLooper();
}
public MyHandler(Looper looper) {
this.mLooper = looper;
}
public void sendMessage(Message message) {
message.setTarge(this);
mLooper.push(mLooper,message);
}
public void handleMessage(Message msg) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我处理问题了 " + Thread.currentThread().getName()+" "+msg.getMsg());
}
}
代码都很简单,都是仿照系统源码写的方法,只不过改用java基础类实现的,自己写这个也想说明一下,系统的东西其实没那么复杂,只不过就是系统需要额外处理的情况比较多才显得复杂。
测试代码如下:
public static void main(String[] args) {
//主线程
Looper.prepare();
MyHandler handler = new MyHandler();
handler.sendMessage(new Message("111111111111111111"));
handler.sendMessage(new Message("22222222222222222"));
handler.sendMessage(new Message("333333333333333333"));
handler.sendMessage(new Message("4444444444444444444"));
handler.sendMessage(new Message("44445555555555555555"));
new Thread(new Runnable() {
@Override
public void run() {
//子线程中
Looper.prepare();
MyHandler handler = new MyHandler();
handler.sendMessage(new Message("66666666666666666"));
new MyHandler().sendMessage(new Message("777777777777777"));
//子线程中向主线程发送消息,实现线程通信
new MyHandler(Looper.getMainLooper()).sendMessage(new Message("888888888888888888"));
new MyHandler(Looper.getMainLooper()).sendMessage(new Message("999999999999999999999"));
new MyHandler(Looper.getMainLooper()).sendMessage(new Message("00000000000000000000000"));
Looper.loop();
}
}).start();
Looper.loop();
}
好了,自己写的handler消息机制就完了。其实handler机制应该从生产者消费者模式上理解可能更简单一些,handler是生产者,looper是消费者(hanlder 是真正的处理者),消息队列是缓冲池。
番外篇:
第一。分析handler源码的时候,发现了一个另外一种消息,叫做闲时消息,也就是说有一种消息只有在当前线程空闲的时候才会去处理的消息,那这种消息有什么好处呢?答案是可以合理利用cpu,把一些事情可以和重要的事情分开,不去抢占资源,比如说我们有统计性的工作要干,那完全可以让线程空闲下来的时候去做。还有一个用处,就是有写时候我们会发现有些代码需要延迟几秒再处理才可以,要不就容易崩溃啊或者是拿不到结果,那么问题来了,延迟几秒合适呢?不同的手机性能不一样,当然延迟的时间也不同了,所以这里写多少都不合适,写多了性能好的手机浪费写少了性能差的手机有问题,这个时候闲时消息就派上用场了,当当前线程空闲的时候,那肯定是前面的事情都处理完了,我们再去拿结果肯定没问题了。好了,我们看一眼怎么用闲时消息吧:
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
//处理事情
return false;//false表示执行一次,true表示空闲了就执行
}
});
好简单啊,就这么两行代码。那什么时候处理空闲消息呢?我们再去消息队列的next方法中看看:
Message next() {
for (;;) {
synchronized (this) {
//....省略
if (msg != null) {
if (now < msg.when) {
//要处理的消息还有一段时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//...有要处理的消息,则返回
return msg;
}
} else {
//没有要处理的消息了
nextPollTimeoutMillis = -1;
}
//....
//代码执行到这里就说明没有要处理的消息或者要处理的消息离现在还有段时间,现在空闲了
//....
//空闲消息数量为0则继续循环
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
}
//空闲消息不为0,则循环执行空闲消息
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
//queueIdle 方法被执行
keep = idler.queueIdle();
} catch (Throwable t) {
}
if (!keep) {
synchronized (this) {
//queueIdle 返回false则被移除掉,只执行一次
mIdleHandlers.remove(idler);
}
}
}
}
}
上面代码也说明了,闲时消息也是执行在当前线程的,如果是主线程也不可以做耗时操作。
第二。大家有没有发现一个问题,就是looper 的looper方法是一个死循环,我们说他之所以是死循环才让我们app不退出,那么也就是说反过来也成立,那就是我们app之所以会 退出,就是因为这个死循环退出来了。没问题吧?我们来验证一下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String test=null;
Log.e("TAG", "onCreate: "+test.toString());
}
很简单,就制造了一个空指针崩溃,看崩溃日志:
熟悉吗?是不是looper的loop方法报错了,然后退出死循环了,程序崩溃了。那这有什么意义呢?当然有了,假如我们的looper的loop代码变成下面这段代码:
public static void loop() {
for (; ; ) {
try {
for (; ; ) {
//处理消息
}
} catch (Exception e) {
//崩溃了,被捕获了
}
}
}
里面那成for循环负责取消息,分发消息,一旦崩溃了,内循环退出,那么就被我们给捕获了,处理了,外循环起作用,又进入内循环....什么结果?是不是程序永远不会崩溃了?只有进程被杀死一种方法了。那需要怎么做才能实现这样呢?也很简单,只要我们发送一个死循环的消息就可以了,代码如下:
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
while (true) {
try {
//死循环,而且不影响looper取消息
Looper.loop();
} catch (Throwable e) {
//捕获异常
}
}
}
});
好了,至此所有都讲解完了。