Handler 存在的意义?
Handler 实现了App的内部通信,Binder实现了App间的进程通信,这是Framework的核心的技术。
Handler 是做线程间的通信的,那么线程通信一定要用Handler吗?(不一定) Handler 更重要的是实现了线程的切换,MessageQueue 消息队列解决了线程切换中的通信问题。 通过最简单的方式来一步步演进Handler的实现。
如下代码,实现线程间的通信和切换:
下面的代码实现非常简单,线程1和线程2进行通信,只需要一个全局的message变量,线程1无限循环通过state判断线程2是否发送了消息,然后执行相关的方法,这个方法是在线程1中执行的。想·
/*** 实现最原始的线程切换*/public class ThreadChange {public static void main(String[] args) {ThreadChange change = new ThreadChange();change.method();}String message;boolean state;public void method(){//线程1new Thread(new Runnable() {@Overridepublic void run() {//线程1 给线程2发送消息message = "hello";//线程2收到消息后,线程1 执行方法for (;;){//无限循环 监听消息if (state){execute();//执行}}}}).start();//线程2new Thread(new Runnable() {@Overridepublic void run() {String data = message;//收到了消息if (data.equals("hello")){state = true;}}}).start();}public void execute(){System.out.println(Thread.currentThread());}}
那么Handler是如何显示线程的切换和通信的呢?其实Handler就是对线程进行封装处理。
假设要封装一个线程的通信框架,你会如何设计?最简单就是有两个方法一个是发送消息,一个处理消息
如下示例代码:
public class THandler {public void sendMessage(Message message){handleMessage(message);}public void handleMessage(Message message){}}
然后写一个ActivityThread来模拟app的入口:从下面的代码看起来没有任何问题,子线程可以收到主线程发送过来的消息。
public class ActivityThread {public static void main(String[] args) {//假设这是在主线程ActivityThread activityThread = new ActivityThread();activityThread.method();}private THandler handler;public void method(){Thread thread = new Thread(new Runnable() {@Overridepublic void run() {handler = new THandler(){@Overridepublic void handleMessage(Message message) {super.handleMessage(message);//是在主线程处理的消息System.out.println("message = " + message.obj);}};}});thread.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//发送消息handler.sendMessage(new Message("hello"));handler.sendMessage(new Message("hello1"));handler.sendMessage(new Message("hello2"));}}
但是假设消息处理时间很长,由于sendMessage直接调用了handleMessage,如果主线程通过sendMessage发送消息,而handleMessage处理消息是在主线程中处理消息。
假设如下的示例代码:处理消息需要5000ms
handler = new THandler(){@Overridepublic void handleMessage(Message message) {super.handleMessage(message);try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("message = " + message.obj+" thread:"+Thread.currentThread().getName());}};
而实际想要的结果是,消息发送完立即去执行其他的代码,如下示例代码,实际是想要发送完消息就显示页面1、页面2、页面3,由于handleMessage处理消息时间长导致了页面无法显示。
handler.sendMessage(new Message("hello"));System.out.println("显示页面1");handler.sendMessage(new Message("hello1"));System.out.println("显示页面2");handler.sendMessage(new Message("hello2"));System.out.println("显示页面3");
那么要如何优化呢?
解决点:
- 处理大量消息问题
- 处理消息不要阻塞主线程,在当前的线程处理消息
对于大量消息的问题,可以使用消息队列的方式来解决,将发送的消息放到消息队列中,然后无限循环获取消息。
这时候框架的架构就变成了如下图所示:
如下示例代码:
/*** 使用消息队列解决大量消息的问题*/public class MessageQueue {//BlockingQueue 阻塞队列 后续了解,以及线程的了解问题private BlockingQueue<Message> queue = new ArrayBlockingQueue<>(100);/*** 将消息存入消息队列*/public void enqueueMessage(Message message){try {queue.put(message);} catch (InterruptedException e) {e.printStackTrace();}}/*** 从消息队列取消息*/public Message next(){Message message = null;try {message = queue.take();} catch (InterruptedException e) {e.printStackTrace();}return message;}}
THandler 中的代码变成了如下:
在子线程中调用loop,使handleMessage在子线程中处理消息,不会阻塞主线程。
public class THandler {private MessageQueue queue = new MessageQueue();public void loop(){for (;;){Message next = queue.next();handleMessage(next);}}public void sendMessage(Message message) {queue.enqueueMessage(message);}public void handleMessage(Message message) {}}
通过loop来循环获取消息,然后调用handleMessage处理消息
Thread thread = new Thread(new Runnable() {@Overridepublic void run() {handler = new THandler(){@Overridepublic void handleMessage(Message message) {super.handleMessage(message);try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("message = " + message.obj+" thread:"+Thread.currentThread().getName());}};handler.loop();}});thread.start();
那么结果就保证了主线程中界面的正常显示,子线程处理消息。
那么子线程向主线程发送消息呢?将代码倒过来就可以了,如下代码:
private THandler tHandler2;public void method2(){tHandler2 = new THandler(){@Overridepublic void handleMessage(Message message) {super.handleMessage(message);System.out.println("主线程处理消息 message = " + message.obj);}};Thread thread = new Thread(new Runnable() {@Overridepublic void run() {tHandler2.sendMessage(new Message("我是子线程发送的消息"));}});thread.start();tHandler2.loop();//开启消息循环机制System.out.println("loop后面的代码无法执行,因为loop是一个无限循环");}
运行结果:
:::tips
会发现一个问题,loop()方法后面的代码无法执行了,因为loop是一个死循环,这个问题后面详细讲解,loop()如何解决主线程阻塞的问题。
:::
上面的代码看似实现了线程间的通信,没什么问题,但是如果在子线程中创建多个THandler时就存在问题了。如下示例代码
其实我们已经注意到了,loop()方法是一个无限循环,loop()后面的代码都无法执行
private THandler handler;private THandler tHandler3;public void method3(){Thread thread = new Thread(new Runnable() {@Overridepublic void run() {handler = new THandler(){@Overridepublic void handleMessage(Message message) {super.handleMessage(message);try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("message = " + message.obj+" thread:"+Thread.currentThread().getName());}};tHandler3 = new THandler(){@Overridepublic void handleMessage(Message message) {super.handleMessage(message);try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("message = " + message.obj+" thread:"+Thread.currentThread().getName());}};tHandler3.loop();//loop后面的代码都无法执行handler.loop();}});thread.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//发送消息handler.sendMessage(new Message("hello"));System.out.println("显示页面1");tHandler3.sendMessage(new Message("hello1"));System.out.println("显示页面2");tHandler3.sendMessage(new Message("hello2"));System.out.println("显示页面3");}
显然只执行了tHandler3而handler没有执行handleMessage,这是因为handler.loop在tHandler3.loop后面了,在for(;;) 后面肯定是无法执行的。
难道我们只能在一个线程中定义一个THandler?,这显然是不行的,思考一下导致无限循环的是消息队列,如果想要在一个线程中创建多个THandler,那么消息队列就不能在THandler中创建,而是要和线程绑定,一个线程拥有一个消息队列。
那么问题来了,如何在一个线程中保存一个对象呢?Java正好提供了一个类ThreadLocal,ThreadLocal提供了两个方法get和set,分别是从当前线程获取某个值,以及从当前线程中存储某个值。
public void set(T value) {Thread t = Thread.currentThread();//获取当前线程ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);//存储到当前线程的map集合中 key:ThreadLocal对象 value:存储的任何值elsecreateMap(t, value);}public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);//根据ThreadLocal对象 获取存储的值。if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}
OK,找到了解决方案,下面就开始写代码了,首先要梳理一下,通过创建Looper类来管理当前线程和消息队列也就是MessageQueue,我们中需要暴露两个静态方法就可以了一个是prepare 通过ThreadLocal来向当前的线程存储当前类的对象,另一个方法就是loop开启消息队列循环机制,还可以在加一个方法myLooper就是通过ThreadLocal来返回当前的Looper对象。
Looper类的结构图如下:
代码实现如下:
/*** Looper和线程绑定一个线程只能有一个Looper,管理队列*/public class Looper {public MessageQueue messageQueue;//ThreadLocalprivate static ThreadLocal<Looper> threadLocal = new ThreadLocal<>();//保证Looper唯一public Looper() {messageQueue = new MessageQueue();}/*** Looper的生命周期是当前线程的生命周期长度* 通过ThreadLocal保证一个线程只有一个Looper,ThreadLocal,* 将Looper存储到当前线程的ThreadLocalMap,key是ThreadLocal对象*/public static void prepare(){if (threadLocal.get()!=null){throw new RuntimeException("Only one Looper my be create per thread");}//当前线程 存储LooperthreadLocal.set(new Looper());}/*** 获取当前线程的Looper* @return*/public static Looper myLooper(){return threadLocal.get();}public static void loop(){//获取当前线程的Looperfinal Looper looper = myLooper();MessageQueue messageQueue = looper.messageQueue;for (;;){Message next = messageQueue.next();// handleMessage(next); 无法在多个Handler中找到}}}
上述代码,通过Looper类来管理消息队列MessageQueue,通过prepare方法向当前线程存储Looper对象,通过myLooper方法获取当前线程存储的Looper对象,通过loop方法进行消息队列的循环获取消息。
但是:无法在Looper中调用THandler的handleMessage方法,思考一下如果在一个线程中创建了多个THandler实例,难道要想Looper传递一个THandler实例的列表吗?这样是不现实的,那么如何解决呢?在THandler中有Message消息的引用,而MessageQueue存储了Message引用,所以我们向Message添加一个THander target的引用。
public class Message {public String obj;public MHandler target;public Message() {}public Message(String obj) {this.obj = obj;}}
而在THandler中的sendMessage中通过message的实例,设置THandler的实例,然后通过Looper.myLooper()获取到当前线程的Looper实例,就可以获取到消息队列的MessageQueue的实例,调用enqueueMessage(message)方法添加到消息队列中。
Looper mLooper;public MHandler() {mLooper = Looper.myLooper();}//发送消息public void sendMessage(Message message){enqueueMessage(message);}private void enqueueMessage(Message message){message.target = this;//保存当前的Handler//将消息添加到消息队列中去mLooper.messageQueue.enqueueMessage(message);}
那么就可以在Looper中的loop方法中拿到THandler的实例对象了,直接调用handleMessage就可以了
public static void loop(){//获取当前线程的Looperfinal Looper looper = myLooper();MessageQueue messageQueue = looper.messageQueue;for (;;){Message next = messageQueue.next();// handleMessage(next); 无法在多个Handler中找到next.target.handleMessage(next);}}
通过Looper类就实现了,一个线程只有一个消息队列。
测试代码如下:
private MHandler handler;private MHandler handler2;public void method() {//主线程向子线程发送消息//子线程new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();//创建Looper保证一个线程一个消息队列//那么如何在子线程切换到主线程呢?handler = new MHandler() {@Overridepublic void handleMessage(Message message) {try {Thread.sleep(1000);//处理消息阻塞} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+" message = " + message.obj);}};//支持多Handler实例handler2 = new MHandler() {@Overridepublic void handleMessage(Message message) {try {Thread.sleep(1000);//处理消息阻塞} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+" message2 = " + message.obj);}};//looper会阻塞子线程,导致后面的代码无法运行,所以需要Looper机制,一个线程只能有一个队列Looper.loop();//无限循环,在子线程调用了handleMessage,所以handleMessage是在子线程执行的System.out.println("无法执行");}}).start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程:"+Thread.currentThread().getName());//主线程,向子线程发送消息 发送大量消息 通过阻塞队列,将消息放入队列,不会因为长时间处理消息而阻塞主线程handler.sendMessage(new Message("hello"));System.out.println("显示1");handler.sendMessage(new Message("hello"));handler2.sendMessage(new Message("hello"));handler2.sendMessage(new Message("hello"));}
结果如下:消息都得到了处理。
那么整体的线程间通信框架的封装思路,如下图所示:
上述对THandler的封装处理,其实就是Android源码中的Handler的大致思路。其实同THandler还可以看出Handler为什么会造成内存泄漏的问题:
内存泄漏的本质:长生命周期持有短生命周期对象,也就是Message持有了Handler的引用而造成的。 从THandler的持有链来看: 线程 -> Looper -> MessageQueue -> Message -> THandler -> 其他/Activity 如此长的持有链,Looper和MessageQueue的生命周期和线程的生命周期一样长,这样长的生命周期持有了THandler和其他/Acitivity的短生命周期的引用,是很有可能会造成内存泄漏的。
还可以看出:Handler存在的意义在于:主线程的通信,也是App内部的通信都必须使用Handler,一个线程只有一个Looper,一个MessageQueue(消息队列),有多个Handler的实例。Android所有的主线程通信都是在Handler上运行的,因为Looper.loop()开启了一个死循环,在loop()后面的代码都无法执行,使得主线程一直在运行,而Android中的App内部通信必须使用Handler进行通信,包括启动服务、四大组件、UI绘制和更新都是在Looper.loop()的线程上进行执行的。
Message 设计
在Handler中的Message设计非常巧妙,为什么要设计Message呢?因为在App的内部都是通过Handler进行通信的,然而除了应用层的代码还有底层的其他服务事件等等都需要通过Handler传递Message进行通信,会创建大量的Message,会造成Message不停的创建和销毁,而造成内存抖动,下面来看看如何设计Message的回收与复用机制,以及通过什么的方式创建Message。
在上述自己写了一个线程的通信框架THandler在设计,设计的Message特别简单:通过Object存储数据
public class Message {public Object obj;public MHandler target;public Message() {}public Message(Object obj) {this.obj = obj;}}
我们指定一个线程只有一个Looper和MessageQueue,当使用THandler大量的发送消息时,会频繁的创建Message对象,使Message对象频繁的创建和销毁,从而造成内存抖动。
内存抖动:短时间大量的创建和销毁对象会造成内存抖动。内存抖动解决方式就是复用。而享元设计模式就是解决主要用于减少创建对象的数量,以减少内存占用和提高性能。
享元模式的设计思路如下:
假设要大量的生成一个类的对象:
public class Circle implements Shape {private String color;private int x;private int y;public String getColor() {return color;}public void setColor(String color) {this.color = color;}public int getX() {return x;}public void setX(int x) {this.x = x;}public int getY() {return y;}public void setY(int y) {this.y = y;}@Overridepublic void draw() {System.out.println("Circle:" + color + " x:" + x + " y:" + y);}}
通过HashMap记录创建过的对象,当下次在有相同的key则直接复用对象,注意HashMap必须是static.
public class ShapeFactory {private static final HashMap<String,Shape> cirMap= new HashMap();public static Shape getCir(String color){Circle circle = (Circle) cirMap.get(color);if (circle == null){//如果为null则创建对象circle = new Circle();circle.setColor(color);cirMap.put(color,circle);}//如果不为空则复用对象return circle;}}
其实享元模式非常简单,只需要用一个在内存中一直存在的static对象,根据唯一标识码来存储到内存中即可,用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。其实上述代码还存在问题虽然减少了创建对象的数量,但是没有回收对象的机制,也会存在导致对象越来越多占用内存越来越大。
那么Handler中的Message是如何设计的呢?下面来进行源码的探索。
Message._obtain_()复用Message,Message采用单向链表实现了复用,可以检查是否有可以复用的Message,用过复用避免过多的创建、销毁Message对象达到优化内存和性能。
其实Message中的复用和回收机制是享元模式的一种实现,不一定要使用HashMap来存储对象。Message采用了更简单的单链表的形式对对象进行存储。
那么什么是单链表呢?其实就是在当前对象加上一个next变量,next指向下一个对象。如下代码:
Message具备了享元模式的特性,必须通过static来存储复用对象,以单链表的形式代替唯一标识码进行存储复用对象链表,因为Message对象没有什么特性,不需要唯一标识码。这样Message就具备了对象的复用机制。
Message next;//典型的单链表,指向下一个对象private static Message sPool;//在内存中保存复用的对象private static int sPoolSize = 0;//链表的大小private static final int MAX_POOL_SIZE = 50;//链表的最大大小,如果超过了最大的大小则通过new创建
Message实现复用的机制,通过obtain方法:
public static Message obtain() {synchronized (sPoolSync) {//if (sPool != null) {//首先判断 如果在内存中的static为空则直接new创建Message m = sPool;//m 表示是当前要复用的对象sPool = m.next;//将sPool 指到下一个复用对象,供下一个Message使用m.next = null;//一定要断开当前的链表,因为外部要使用这个m对象m.flags = 0; // clear in-use flagsPoolSize--;//链表的大小减一return m;}}return new Message();}
上述代码设计的非常巧妙,只看代码可能发现不了,直接上图:下图是当前某个时刻的链表结构
当调用obtain()方法时:将链表的头部对象返回,sPool指向下一个复用对象。
看到这里可能存在疑问?sPool没有看到初始化的地方啊?它从哪里初始化的呢?而且对象如何复用的呢?
Message对象是在使用完毕后,存放到单链表复用池的,那么什么地方使用了Message对象呢?就是handleMessage,而调用handleMessage的地方就是Looper.loop()方法
public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}.......msg.target.dispatchMessage(msg);//调用了Handler.handleMessagemsg.recycleUnchecked();//调用了销毁对象的方法}}
从上述代码可以看到使用完Message后调用了recycleUnchecked方法,其实就是回收对象,将对象复用存储到复用池中。
void recycleUnchecked() {// Mark the message as in use while it remains in the recycled object pool.// Clear out all other details.flags = FLAG_IN_USE;what = 0;arg1 = 0;arg2 = 0;obj = null;replyTo = null;sendingUid = UID_NONE;workSourceUid = UID_NONE;when = 0;target = null;callback = null;data = null;synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {next = sPool;sPool = this;sPoolSize++;}}}
嗖嘎,没错就在这里,sPool 没有初始化,是因为sPool第一次指向了回收的Message对象,复用了使用完的Message对象,同时还判断了链表复用池的大小,如果超过了MAX_POOL_SIZE,就不会往复用池中添加了,防止无限添加Message对象,导致内存占用越来越大。
sPool 其实不用初始化,如下图就是第一次产生Message对象。
当突然一起并发来了三个obtain(),这时候复用池只有一个Message对象,其中复用池的一个对象被拿去使用,剩下的两个通过new创建了两个Message对象,返回被拿去使用,这时候复用池是空的
当第一个消息处理完毕:
第二个消息处理完毕:
第三个消息处理完毕:
可以看出,形成了一个单向循环链表,最后一个被回收的消息对象最先被复用。
可以得出结论:Handler中的Message,推荐使用Message.obtain()方法复用Message对象,Message类采用享元模式,通过static来使复用池存储在内存中,使用单向循环链表作为复用池。
那我们自己写的THandler中的Message就可以优化成如下:
public class Message {public String obj;public MHandler target;Message next;private static Message sPool;private static final int MAX_POOL = 50;private static int sPoolSize = 0;public Message() {}public Message(String obj) {this.obj = obj;}public static Message obtain(){if (sPool != null){Message m = sPool;sPool = m.next;m.next = null;sPoolSize--;return m;}return new Message();}public static Message obtain(String msg){Message obtain = obtain();obtain.obj = msg;return obtain;}public void recycle(){if (sPoolSize < MAX_POOL){next = sPool;sPool = this;sPoolSize++;}}}//Looperpublic static void loop(){//获取当前线程的Looperfinal Looper looper = myLooper();MessageQueue messageQueue = looper.messageQueue;for (;;){Message next = messageQueue.next();// handleMessage(next); 无法在多个Handler中找到next.target.handleMessage(next);next.recycle();//回收Message对象}}
MessageQueue 设计
在上述自己写的THandler中的,MessageQueue消息队列,实现的非常简单,只是解决了大量消息的问题,对于按照时间发送消息和阻塞唤醒都没有实现,那么在Android 中Handler的MessageQueue是如何做到按时间排序发送消息以及阻塞和唤醒的呢?如何重新设计一下自己的MessageQueue呢?
首先,来看MessageQueue如何实现按时间排序消息队列的,在上述Message设计中,Message是一个单向循环链表,而MessageQueue正是基于单向链表进行时间排序的。MessageQueue中有一个Message mMessage的变量而这个变量就是消息队列。
在Handler中通过sendMessageDelayed,按照时间进行发送消息。在(当前时间+ delayMillis)之前的所有挂起消息之后将消息放入消息队列。
threadHandler.sendMessageDelayed(threadHandler.obtainMessage(),1000);public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}// SystemClock.uptimeMillis() + delayMillis 转换从绝对的时间return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}
将消息按时间放入消息队列正是调用了MessageQueue的enqueueMessage方法
其实实现很简单,当
when==0或者小于链表头部的when那么就会将msg插入链表头部。 如果when > 0,就会遍历链表当遍历到when < p.when,那么msg就会插入到p的前面。其实就是链表的插入操作。
boolean enqueueMessage(Message msg, long when){//msg 要放入消息队列的消息 when 什么时间执行这个 消息.......msg.markInUse();msg.when = when;Message p = mMessages;if (p == null || when == 0 || when < p.when) {msg.next = p;mMessages = msg;} else {Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}........}
如下图所示:
- 当when==0或者when<p.when msg插入链表头部

- 当when > p.when 时就会遍历链表

那么总结一下:一个线程对应着一个Looper,一个Looper对应着一个MessageQueue,而一个MessageQueue对应一个Message单向链表作为消息队列和时间排序。这样整体的设计清晰明了了。
Message 基于单链表的实现,正是方便了对时间的排序,MessageQueue消息队列对时间排序实现很简单,那么MessageQueue是如何当时间没有到如何实现消息阻塞和唤醒呢?单向链表无法实现阻塞
其实阻塞和唤醒通过底层内核层的代码epoll 机制来实现的。
消息阻塞的Java层实现,其实就是通过JNI调用底层的nativePollOnce方法使其进入阻塞状态。
//出队阻塞的逻辑Message next() {final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}//下一次进入 阻塞调用Native的方法nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {//.....if (msg != null) {if (now < msg.when) {// 阻塞的时间nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {///return msg;}} else {// No more messages. 如果没有消息-1表示一直阻塞nextPollTimeoutMillis = -1;}.....if (pendingIdleHandlerCount <= 0) {// 如果没有返回msg则说明要进入阻塞状态,将mBlocked设置为true,当有新的消息来时就会//根据mBlocked==true 唤醒mBlocked = true;continue;}}.....}}
唤醒机制:通过JNI调用nativeWake进入唤醒状态
//入队唤醒的逻辑boolean enqueueMessage(Message msg, long when) {....synchronized (this) {if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;//mBlocked 决定了是否要唤醒消息} else {needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.// 唤醒next阻塞,执行消息 唤醒if (needWake) {nativeWake(mPtr);}}return true;}
Loop 消息循环机制
在上述探索线程的通信机制中,发现在Looper类中的loop()方法是一个死循环,那么按照正常逻辑会阻塞主线程,如下代码:假设我们在主线程中开启了loop使用自己写的THandler他是不会执行loop后面的代码的,在Android中主线程也就是UI线程,为什么不会阻塞UI线程呢?
private THandler tHandler2;public void method2(){tHandler2 = new THandler(){@Overridepublic void handleMessage(Message message) {super.handleMessage(message);System.out.println("主线程处理消息 message = " + message.obj);}};Thread thread = new Thread(new Runnable() {@Overridepublic void run() {tHandler2.sendMessage(new Message("我是子线程发送的消息"));}});thread.start();tHandler2.loop();//开启消息循环机制System.out.println("loop后面的代码无法执行,因为loop是一个无限循环");}
在Android的App中通过ActivityThread中的main函数中的代码:
public static void main(String[] args) {......Looper.prepareMainLooper();......Looper.loop();//死循环 主线程一直会存在//如果继续执行抛出异常 主线程都没了 那么整个app就会杀死throw new RuntimeException("Main thread loop unexpectedly exited");}
loop必须要一直执行,保证主线程不会杀死。如果主线程没了那么App肯定也会被杀死,所以在App运行阶段必须要保证主线程一直处于运行的状态,那么Android在主线程是如何更新UI呢?
在Android中的所有的主线程操作都是通过Handler来执行这些操作的,以事件为驱动的操作系统。
Looper通过阻塞+任务执行来实现更新UI,当没有任务时进入阻塞状态,当任务来时就会添加到消息队列,loop()死循环查到有新的消息就会唤醒执行任务,阻塞可以让出CPU资源,阻塞和唤醒机制通过了MessageQueue的next方法进行了实现,不会让主线程导致卡死。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();//在ActivityThread中就是调用了这个方法public static void prepareMainLooper() {prepare(false);//消息队列不可以quitsynchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}//创建MessageQueue 获取当前的线程与线程绑定private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}public static void prepare() {//消息队列可以quitprepare(true);}private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {//每个线程只能创建一个Looperthrow new RuntimeException("Only one Looper may be created per thread");}//将Looper加入到当前的线程中sThreadLocal.set(new Looper(quitAllowed));}
在上述代码中prepare有两个重载方法,quitAllowed表示的MessageQueue的消息队列是否可以销毁。
MessageQueue的构造方法如下:mQuitAllowed决定队列是否可以销毁,主线程的队列不可以被销毁需要传入false,在MessageQueue的quit方法
MessageQueue(boolean quitAllowed) {mQuitAllowed = quitAllowed;mPtr = nativeInit();}void quit(boolean safe) {if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");}synchronized (this) {if (mQuitting) {return;}mQuitting = true;if (safe) {removeAllFutureMessagesLocked();} else {removeAllMessagesLocked();}// We can assume mPtr != 0 because mQuitting was previously false.nativeWake(mPtr);}}
ThreadLocal 干什么的?
线程上下文的存储变量,线程隔离的工具类,
public void set(T value) {Thread t = Thread.currentThread();//获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);//当前线程存储信息elsecreateMap(t, value);}
一个线程只有一个Looper,因为一个Thread线程只有一个ThreadLocalMap<this,value>, this存储的是唯一的ThreadLocal,所以value也是唯一的。
:::tips
一个线程只有一个唯一的Looper.Looper.ThreadLocal在整个APP的是唯一的,因为他是static final.
:::
:::tips 在Looper中,一个Looper只有一个MQ. :::
final MessageQueue mQueue;
那么Handler的MessageQueue来自哪里呢?如下代码来自Looper的mQueue,所以Looper的mQueue是共享在Handler的
public Handler(Callback callback, boolean async) {......mLooper = Looper.myLooper();//调用了sThreadLocal.get()获得刚才创建的Looper对象//如果Looper为空则抛出异常if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;}
- 一个线程有几个Handler? 可以有多个Handler,Looper在主线程就只有一个
- 一个线程有一个Looper,通过ThreadLocal
Handler内存泄露的原因:匿名内部类持有了外部类对象
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;//Message会持久Handler而Handler持有了Activity.if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}
如果要在子线程new Handler要做什么工作? 主线程持有Looper,而子线程没有Looper 必须调用Looper.praper()在子线程创建Looper.
//MessageQueuevoid quit(boolean safe) {.....synchronized (this) {if (mQuitting) {return;}mQuitting = true;if (safe) {removeAllFutureMessagesLocked();} else {removeAllMessagesLocked();}// We can assume mPtr != 0 because mQuitting was previously false.nativeWake(mPtr);}}//Looperpublic void quit() {mQueue.quit(false);}
消息队列无效是怎么处理呢?

MessageQueue 可以无限制的存放Message,因为系统需要调用如果入队阻塞那么系统功能就无法使用了。出队如果MessageQueue为空就会一直阻塞。线程会阻塞,CPU可以降低CPU的调用该线程,提高CPU的性能。
这也是为甚Looper死循环不会导致应用卡死,因为Looper死循环,当从消息队列中获取next没有消息的时候就会一直阻塞,当有消息入队的时候就会唤醒。
卡死是发生了 ANR,而ANR 是一定的时间内,消息没有处理完,又用Handler发送一个ANR提醒。ANR和阻塞是没有关系的。 而Looper死循环,没有消息处理时就会block(阻塞) 不过是线程没事做了。而ANR 其实也是通过Handler发送了一个消息提醒。
如下代码:
//出队阻塞的逻辑Message next() {final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}//阻塞调用Native的方法nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {//.....if (msg != null) {if (now < msg.when) {// 阻塞的时间nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {///}} else {// No more messages. 如果没有消息-1表示一直阻塞nextPollTimeoutMillis = -1;}.....}.....}}//入队唤醒的逻辑boolean enqueueMessage(Message msg, long when) {....synchronized (this) {if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.// 唤醒next阻塞,执行消息 唤醒if (needWake) {nativeWake(mPtr);}}return true;}
- 为什么子线程无消息一定要调用quit方法
因为子线程不会向主线程一直运行,当子线程销毁时,一定要将Looper的循环结束掉,下面我们看看是如何处理的:
//Looperpublic void quit() {mQueue.quit(false);}//MessageQueue 将mQuitting设置ture并且唤醒阻塞执行nextvoid quit(boolean safe) {if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");}synchronized (this) {if (mQuitting) {return;}mQuitting = true;if (safe) {removeAllFutureMessagesLocked();} else {removeAllMessagesLocked();}// We can assume mPtr != 0 because mQuitting was previously false.nativeWake(mPtr);}}Message next() {final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}//阻塞调用Native的方法nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {//.....if (msg != null) {if (now < msg.when) {// 阻塞的时间nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {///}} else {// No more messages. 如果没有消息-1表示一直阻塞nextPollTimeoutMillis = -1;}// Process the quit message now that all pending messages have been handled.//如果mQuitting=true返回null,这个null是关键if (mQuitting) {dispose();return null;}.....}.....}}
上述代码在执行Looper.quit()方法后,最终返回了null,那么在loop()方法中的处理,如下代码直接return并且结束掉了无限循环。
public static void loop() {
....
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
}
}
线程同步
Handler是用于线程间通信,整个APP都是用它来进行线程间的协调。
主要有几个关键点:消息入库(enqueueMessage)2. 消息出库(next)3. quit销毁消息队列
MQ入队和出队以及调用
quit()都会加上synchronized锁,另外主线程不能quit(),会抛出异常//MessageQueue.class boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } //上锁,如果发现有线程正在入队列,其他线程要想入队列需要等待 synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } 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. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; }synchronized锁是一个内置锁,说明对所有调用同一个MessageQueue对象的线程来说,他们都是互斥的,在Handler里一个线程对应着一个唯一的Looper对象,而Looper中有只有一个唯一的MessageQueue.所以主线程只有一个MessageQueue对象,也就是说当子线程向主线程发送消息的时候,主线程一次只会处理一个消息,并发的情况下其他线程都需要等待,这样消息队列就不会出现混乱。
再看next函数:
有个疑问,入队列的时候加锁就可以了,为什么出队列还要加锁呢?next加锁主要是可以保证next和enqueueMessage方法能够实现互斥,当一个线程调用对消息队列入队操作,那么next时候主线程中的Looper调用的,必须要等到当前的线程入队完毕,那么Looper才能做出队操作。这样才能够真正的保证多线程访问MessageQueue时有序进行的
Message next() {
//....
for (;;) {
//.... 上锁
synchronized (this) {
}
//....
}
}
消息机制之同步屏障
在上述的学习中,线程的消息都是放在同一个MessageQueue里面,取消息的时候互斥取消息,而且只能从头部取消息,而添加消息是按照消息的执行先后顺序进行的排序,那么同一个时间范围内的消息,如果它是需要立刻执行的,需要怎么办?常规方法需要等到队列轮询到自己的时候才能执行,所以需要给一个紧急需要执行的消息是一个绿色通道,这个绿色通道就是同步屏障。 例如:救护车前面后20辆车,如果一辆一辆的走,肯定不行,所以救火车要优先通过立刻执行。 msg.taget = null(标记为同步屏障)-> msg1 -> msg2 -> msg3() -> msg4
同步屏障:就是阻碍同步消息,只让异步消息通过。
开启同步屏障:
//MessageQueue.class
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
//从消息池获取Message
final Message msg = Message.obtain();
msg.markInUse();
//就是这里 初始化Message对象的时候,并没给target赋值,因此target==null
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
//如果开启同步屏障的时间T不为0,且当前的同步消息里有时间小于T,则prev也不为null
prev = p;
p = p.next;
}
}
//根据prev是不是null,将msg按照时间顺序插入到消息队列的合适位置
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
//Message.java
//如果是异步消息 需要对消息对象设置为 true
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
从上述代码,可以看出将一条Message对象并且target=null的消息插入到消息队列中了,那么异步消息如何处理呢?从分析上述的Looper的代码中可以看出调用MessageQueue.next 处理消息,我们再来看next
Message next() {
//......
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//nextPollTimeoutMillis == -1 一直阻塞
//nextPollTimeoutMillis == 0 不会阻塞立即返回
//nextPollTimeoutMillis > 0 最长阻塞nextPollTimeoutMillis毫秒
//如果期间有程序唤醒会立即执行
int nextPollTimeoutMillis = 0;
//next也是一个无线循环
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 获取系统开启到现在时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;//当前链表的头结点
//注意这是关键,如果msg!=null 并且 msg.target ==null 他就是屏障,需要循环遍历,一直往后找到第一个异步消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
//isAsynchronous() 表示是否是异步消息 如果是异步消息立即执行
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
//如果有消息需要处理,先判断时候有没有到,如果没到的话设置一下阻塞时间
//如有些场景常用的postDelay
if (now < msg.when) {
//计算出离执行时间还有多久赋值给nextPollTimeoutMillis
//表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//获取消息
mBlocked = false;
//链表操作,获取msg并且删除该节点
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
//返回拿到的消息
return msg;
}
} else {
// 没有消息nextPollTimeoutMillis = -1 阻塞等待唤醒
nextPollTimeoutMillis = -1;
}
//..........
}
}
从上面的代码可以看出,开启同步屏障的时候,消息机制在处理消息的时候,优先处理异步消息。这样同步屏障就起到了一种过滤和优先级的作用。
如上图所示,在msg_1后面有一道墙就是同步屏障(红色部分)。
我们来分析一下next循环的处理方式:
当next循环到同步屏障的时候,就会循环查找该节点后面的异步消息,如果发现异步消息:msg_2 结束循环,将msg_2处理。然后next在进行一次循环,有发现了同步屏障,然后循环查找异步消息:msg_M msg_M处理。这样就保证了先处理异步消息msg_2 和 msg_M其他的消息不会处理。那么同步消息什么时候被处理呢?首先需要移除这个同步屏障,调用removeSyncBarrier.
在日常的应用开发中,其实很少会用到同步屏障,同步屏障在系统的源码中会经常使用,例如UI更新消息即为异步消息,需要优先处理。
比如:view更新:draw requestLayout invalidata等很多地方调用
例如在ViewRootImpl.scheduleTraversals():
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//开启同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//发送异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
//...postCallback最终走到了 Choreographer.java
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
//标记为异步消息
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
上述代码开启了同步屏障,并发送异步消息,UI更新相关的消息是优先级最高的,这样系统会有限处理异步消息。
当然最后要移除同步屏障
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用
postSyncBarrier并设置消息的setAsynchronous(ture) 异步消息时,target即为null,这是开启同步屏障的关键。当在消息轮询器Looper在loop中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。
HandlerThread 是什么?
一般有个需求在线程创建一个Looper,主线程可以使用子线程的Looper那么我们要怎样写呢?
根据上述学到的知识,我们可以写出如下代码:
Thread thread = new Thread() {
public Looper looper;
@Override
public void run() {
//在子线程中使用Handler,去更新UI 必须使用Looper.getMainLooper()
//因为Handler()构造方法中的,Looper.mylooper()是从ThreadLocal中获取,而ThreadLocal
//是根据当前的线程获取Looper 在子线程中并没有looper
Looper.prepare();
looper = Looper.myLooper();
threadHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
Log.e("TAG", "handleMessage: " + Thread.currentThread());
}
};
Looper.loop();
threadHandler.sendEmptyMessage(1);
}
public Looper getLooper() {
return looper;
}
};
thread.start();
threadHandler.sendEmptyMessage(1);
但是上述代码会出现问题,因为线程是异步启动的,如果threadHandler发送消息,那么threadHandler得到的是null.
Android 提供了一个HandlerThread类,它就是帮助我们创建子线程的Looper:
核心代码如下:
@Override
public void run() {
mTid = Process.myTid();
//在子线程 创建了一个looper
Looper.prepare();
//得到锁
synchronized (this) {
//赋值
mLooper = Looper.myLooper();
//然后唤醒所有等待的锁
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
//当我们调用getLooper的时候 上锁
synchronized (this) {
//如果mLooper等于null,那么说明run方法中的mLooper并没有赋值,进入等待 释放锁
while (isAlive() && mLooper == null) {
try {
//等待 释放锁 收到通知 将mLooper返回
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
那么我们可以使用HandlerThread书写如下代码:这样我们就拿到了子线程的looper
//Thread[main,5,main]
Log.e("TAG", "onCreate: " + Thread.currentThread());
HandlerThread handlerThread = new HandlerThread("t1");
handlerThread.start();
threadHandler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
//Thread[t1,5,main]
Log.e("TAG", "handleMessage: " + Thread.currentThread());
}
};
//主线程 向子线程发送消息
threadHandler.sendEmptyMessage(1);
IntentService
IntentService : 就是应用了HandlerThread,IntentService所以是在子线程中运行的。
如下核心代码:
在onCreate方法中,创建了HandlerThread就是一个子线程的Looper,onStart()方法通过创建的handler去发送了消息,最终消息会调用到onHandleIntent,那么onHandleIntent就是在子线程中运行的。
@Override
public void onCreate() {
super.onCreate();
//创建子线程的Looper
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
//获取Looper
mServiceLooper = thread.getLooper();
//创建Handler
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
//发送消息
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//消息通知
onHandleIntent((Intent)msg.obj);
//处理完消息,service自动停止,内存释放
stopSelf(msg.arg1);
}
}
//由子类实现此方法
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
//service 停止服务
public final void stopSelf(int startId) {
if (mActivityManager == null) {
return;
}
try {
mActivityManager.stopServiceToken(
new ComponentName(this, mClassName), mToken, startId);
} catch (RemoteException ex) {
}
}
应用在:一个任务分成几个子任务,子任务按照任务顺序执行,保证同一个线程中执行任务队列,子任务全部执行完成后,这项任务才算成功。IntentServie 可以帮我们完成这个工作,而且能够很好的管理线程,保证只有一个子线程处理工作,而且是一个一个的完成任务。
