Android异步消息处理机制(源码分析+面试题)
参考文献:
- Android异步消息处理机制源码剖析
- Handler全家桶之 —— Handler 源码解析
- 你真的懂Handler吗?Handler问答
- android的消息处理机制(图+源码分析)——Looper,Handler,Message
1 概述
主线程不能执行耗时操作,因为会阻塞,在子线程里进行耗时操作;子线程不能更新UI,用handler发送一个更新UI的消息,handler分发消息,处理消息。
子线程为何不能访问UI?
- 源码角度:当访问UI时,ViewRootImpl会调用checkThread()方法检查当前线程是哪个线程,如果不是UI线程会抛出异常;
- 线程安全角度:访问UI不是线程安全的;
- 访问UI为什么不加锁:逻辑复杂、效率低;
Handler作用:
- 线程间通信(例如,子线程通知主线程更新UI);
- 执行计划任务;
下面源码分析基于Android 8.0;
2 Message 消息
Message消息,是多线程间通信的实体,是Handler发送和处理的对象。Message对象实现了Parcelable接口,说明Message对象支持序列化/反序列化操作。
2.1 属性
//msg ID public int what; //存储int类型的数据域 public int arg1; //存储int类型的数据域 public int arg2; //存储Object类型数据域 public Object obj; //存储Bundle类型数据域 /*package*/ Bundle data; /*package*/ static final int FLAG_IN_USE = 1 << 0; //消息标识,当消息对象进入消息队列或回收时设置为FLAG_IN_USE,msg.obtain时设置为0 /*package*/ int flags; //处理消息的时间 /*package*/ long when; //发送和处理消息的Handler /*package*/ Handler target; //post的Runnable /*package*/ Runnable callback; // 链式结构,指向下一个Message对象,用于维护链表结构的消息池(消息队列) /*package*/ Message next; //信号量,消息池的加锁对象 private static final Object sPoolSync = new Object(); //消息池的表头,由它维护了一个链式消息池,当消息被回收的时候,会加入到这个消息池中 private static Message sPool; //消息池大小 private static int sPoolSize = 0; //消息池最大容量50,消息队列的最大容量是50 private static final int MAX_POOL_SIZE = 50;
Message
可传输int , Object ,Bundle类型的数据;- 如果你的message只需要携带简单的int,请优先使用
Message.arg1
和Message.arg2
来传递信息,这比用Bundle
更省内存; - 擅用
message.what
来标识信息,以便用不同方式处理message; Message
维护了一个全局的消息池(消息队列),消息队列最大容量是50;消息被回收后,会放入到消息池中,并将flag
字段设置为FLAG_IN_USE
;
2.2 静态obtain()
方法
public static Message obtain() { synchronized (sPoolSync) {//对消息池加锁 if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // flags设为0 sPoolSize--;//从链表删除 return m; } } return new Message();//若消息池为空,直接new }
obtain
方法用于获取一个消息对象,如果当前消息池为空,直接new,否则从消息池头部取一个消息对象进行复用;
obtain
方法还有好几个重载方法,但最终都会调用该该无参方法。
2.3 recycle()
方法
//可手动回收消息public void recycle() { if (isInUse()) { if (gCheckRecycle) { throw new IllegalStateException("This message cannot be recycled because it " + "is still in use."); } return; } recycleUnchecked(); }//真正回收Message的方法,looper.loop()在从消息队列取出并处理消息后调用这个方法 void recycleUnchecked() { flags = FLAG_IN_USE; //修改标记?? //为了无差别(handler发送的所有消息)复用消息对象,清空所有域 what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1;when = 0;target = null;callback = null;data = null; synchronized (sPoolSync) {//将使用完的消息回收后放入消息池,头插法 if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }
在Message对象被处理或从消息队列移除后,可以手动调用recycle()
方法回收消息对象;当然recycleUnchecked()
方法才是真正回收消息的方法,looper.loop()
在从消息队列取出并处理消息后调用这个方法进行消息回收;这个方法首先会将flag标记为FLAG_IN_USE,并把清空所有属性;并在消息池没有达到最大限定值的情况下,把这个对象插入消息池的表头。同样,在操作消息池的时候需要先对sPoolSync信号量加锁。
3 MessageQueue消息队列
MessageQueue
是一个常量类,不允许被继承;
消息队列用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。
3.1 消息出队next()
Message next() {//... int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; //死循环从队列取Message,直到返回一个Message,或者MessageQueue退出 for (;;) { //... synchronized (this) {//消息队列加锁 // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //取队头消息,若该消息不为空且者是屏障消息(target为空),则继续遍历,直到取到一个异步消息为止 //屏障消息:target为空时是屏障消息;用于区分同步消息和异步消息;如果设置了屏障消息,只执行异步消息,不执行同步消息,直到移除了屏障;如果没设置屏障消息,同步消息和异步消息都执行 if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) {//如果消息执行时间未到,继续循环,等待时间到 // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { //preMsg不空,说明此时队列头结点是一个target为空的屏障消息,同时msg此时是异步消息。 prevMsg.next = msg.next;//直接从链表取下该消息 } else {//此时msg是队列头结点,直接删除队头即可 mMessages = msg.next; } msg.next = null;//断开next链接 if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse();//修改标记 return msg;//取道非空消息退出 } } else {//队列为空,next方法阻塞,继续循环,等待新消息到来 // No more messages. nextPollTimeoutMillis = -1; } //若消息队列已退出,返回true退出死循环 if (mQuitting) { dispose(); return null; //返回null后Looper.loop()方法也会结束循环 }//... if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true;//当队列为空时,next()方法会阻塞,继续循环,直到有新消息到达队列 continue; }//... } }
next()
方法用于将队列头部消息出队并返回;- 该方法内部有个死循环,如果消息队列中没消息,next方法会阻塞,继续循环,直到取道新消息;如果消息队列中有消息,先判断执行时间是否到了,如果时间没到则等待,继续循环;如果时间到了就将消息出队返回;
- 在循环过程中会对消息队列加锁,所以该方法是线程安全的;
3.2 消息入队enqueueMessage()
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) {//入队的消息的target,必须不为空,否则会抛异常 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) {//如果消息队列在退出状态 ,则直接回收消息,返回false 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; } //把消息标记为在使用状态,设置when msg.markInUse(); msg.when = when; Message p = mMessages;//此时p是链表头部 boolean needWake; if (p == null || when == 0 || when < p.when) { //如果队列为空或者when等于0,或者when小于队头Message的when,则直接把消息插入队头 msg.next = p; mMessages = msg; needWake = mBlocked; } else { needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p;//prev是p的前驱节点,依次遍历 p = p.next; if (p == null || when < p.when) { break;//当p已经到队尾或者找到一个节点msg.when < p.when时退出循环 } if (needWake && p.isAsynchronous()) { needWake = false; } } //链表插入操作,把msg插入到p节点前边,并把p的前驱节点的next改为msg msg.next = p; // invariant: p == prev.next prev.next = msg; } //... } return true;//插入成功,返回true }
- 该方法用于将消息入队,其实就是单链表的插入操作;
- 该方法对链表队列操作时,依然是进行了加锁同步,所以是线程安全的;
- 队列是一个(消息执行时间)when升序链表,所以插入也必须找到合适的节点进行插入;如果待插入Message不设置when或when=0,直接插入队列头部;否则遍历队列结点,直到找到第一个大于when的结点,插入到该结点的前面;
4 Looper消息泵
通过Looper.loop()
不断地从MessageQueue中抽取Message,将消息分发给目标处理者(Handler);
4.1 主要属性和构造器
//线程本地变量,每个线程有一个独立的Looper对象,不存在线程安全问题 //如果不调用prepare()方法,sThreadLocal.get()返回null static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; // 主线程的Looper,由Looper.class维护 final MessageQueue mQueue;//looper的MessageQueue final Thread mThread;//(创建)Looper线程//私有构造器,不允许外部调用private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread();}
- Looper的构造器是私有的,不能在Looper类外部new Looper,所以在Looper类外部必须调用prepare()方法来创建一个Looper对象;
- Looper内部有一个MessageQueue属性;
4.2 创建Looper
用Looper.prepare()
创建Looper,而不是new;
//公有方法,开发者只能调用这个方法为当前线程创建Looper,允许退出public static void prepare() { prepare(true);}//私有方法,开发者无法调用,同一个线程只允许调用一次prepare(),否则会抛出异常private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed));//将Looper对象设置为线程本地变量}//主线程的Looper初始化,虽然是公有方法,我们无法调用,//因为系统启动的时候已经调用过了,如果再次调用,会抛异常public static void prepareMainLooper() { prepare(false); synchronized(Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } //把得到的主线程Looper赋值给sMainLooper sMainLooper = myLooper(); }}//获取当前线程的Looper对象public static@Nullable Looper myLooper() { return sThreadLocal.get();//获取线程本地变量}
- 调用**
Looper.prepare()
为当前线程创建Looper对象**,并将Looper对象设置为线程本地变量; - 调用
Looper.myLooper()
方法来获取当前线程的Looper对象; - 主线程的Looper允许MessageQueue退出,而其他线程不允许;
- 一个线程只能调用一次
Looper.prepare()
方法,否则会抛出异常,所以在prepare()
创建Looper对象之前,应该先调用Looper.myLooper()
方法判断是否为空;同时也说明了一个线程只有一个Looper对象; - 主线程的Looper在ActivityThread中的main()方法中创建的,所以主线程不需要手动创建Looper;
//主线程中不需要自己创建Looperpublic static void main(String[] args) { //... Looper.prepareMainLooper();//为主线程创建Looper,该方法内部又调用 Looper.prepare() //... Looper.loop();//开启消息轮询 //... }
- 另外可以在任何地方调用Looper.getMainLooper();获取主线程的Looper;
但是子线程就不一样了,子线程在创建Handler对象前必须手动调用Looper.prepare()
方法创建Looper对象;
//子线程中创建Looper的标准写法new Thread(new Runnable() { @Override public void run() { if(Looper.myLooper()==null){//保证一个线程只有一个Looper Looper.prepare();//创建Looper } Handler handler=new Handler(); Looper.loop();//开启消息轮询 }}).start();
4.3 开启消息轮询loop()
//代码省去打印等其他无关逻辑public static void loop() { //获取当前线程的sThreadLocal变量,即Looper对象 final Looper me = myLooper(); //如果当前线程没有调用过prepare()方法,则me为null,抛出异常 if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } //从me里获得本线程的MessageQueue对象 final MessageQueue queue = me.mQueue; Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); //开启消息轮询 for (;;) { //从消息队列取消息 Message msg = queue.next(); // 当消息队列为空且未退出时,next方法会阻塞 if (msg == null) { //next返回null表明消息队列已退出 return;//结束轮询,loop方法唯一出口 } try { //target是Message的Handler,调用它的dispatchMessage()方法来分发消息 msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } final long newIdent = Binder.clearCallingIdentity(); msg.recycleUnchecked();//当消息被处理完后,回收当前消息 }}
- 通过调用
Looper.loop()
方法开启消息轮询;该方法会调用queue.next()
方法从队列中取头部消息; - 该方法会循环调用
msg.target.dispatchMessage(msg);
来进行消息分发,分发给消息的target,也就是消息对应的的Handler对象; - 当消息被处理完后,会调用
msg.recycleUnchecked();
回收消息,当前消息放入消息池,以便以后复用; - handler是在它关联的looper线程(创建Looper对象的线程)中处理消息的;
5 Handler 消息处理器
5.1 主要属性和构造器
5.1.1 主要属性
//是否发现潜在的内存泄漏,默认为falseprivate static final boolean FIND_POTENTIAL_LEAKS = false;private static final String TAG = "Handler";//静态全局变量,主线程的Handlerprivate static Handler MAIN_THREAD_HANDLER = null;//绑定的Looper对象final Looper mLooper;//绑定的MessageQueue消息队列,通过looper获取final MessageQueue mQueue;//回调接口final Callback mCallback;//是否是异步的,如果是异步的,在发送消息的时候,//会调用Message.setAsynchronous(true)把消息设为异步消息final boolean mAsynchronous;
- 由此可见一个Handler持有一个Looper类型的属性;即一个Handler对应一个唯一的Looper;而对应的MessageQueue消息队列,通过Looper属性获取;
5.1.2 构造器
public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { //如果为true,如果Handler实现类是匿名类或内部类或非static类,会给出警告,告知开发者存在内存泄漏的风险 final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } //获取当前线程的线程本地变量,即Looper对象; mLooper = Looper.myLooper(); if (mLooper == null) {//创建Handler对象前要调用Looper.prepare throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //mQueue直接拿Looper里的MessageQueue类型的引用对象 mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } //该构造器可为当前Handler指定Looper对象,所以Handler对象和Looper对象不一定是在同一线程创建的 public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; } public Handler() {this(null, false);} public Handler(Callback callback) {this(callback, false);} public Handler(Looper looper) {this(looper, null, false);}
-
Handler对应的MessageQueue对象来自其关联的Looper对象;
-
其他构造方法都会调用前面俩构造器中一个;早常用的构造器是
Handler()
和Handler(Callback)
; -
Handler(Looper looper)
用于为当前Handler指定关联的Looper对象; -
Handler关联的Looper对象既可以来自当前线程(创建Handler实例的线程)的本地变量,也可以在构造器里指定;前面一种情况,Handler和Looper是在同一线程里实例化的,后面一种情况不一定;
5.1.3obtainMessage
public final Message obtainMessage() { return Message.obtain(this);}
使用该方法获取handler当前处理的Message;Handler中有一系列obtanMessage()重载方法,最后调用的还是该方法;需要注意的是,在Message中,obtain()方法是静态方法,在Handler中,是非静态的,需要通过具体的Handler实例对象来获得,但是禁止子类进行覆写;
5.2 发送消息
在Handler中,可以发送一个Runnable对象,也可以发送一个Message对象;通过sendMessage(Message)
方式发送一个Message对象;通过post(Runnable)
方式发送一个Runnable对象,这个Runnable对象最终也会被包装成一个Message对象发送;
5.2.1 post
方式
//立即post一个Runnable对象到MessageQueue中,此时Runnable对象被包装成Message后入队(when == 当前系统时间,可能是队头,也可能不是队头,队列中已经有when值小于当前时间的Message)public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0);}//Runnable包装成Message后加入到MessageQueue中,但此时//Message.when=uptimeMillis,uptimeMills是消息的执行时间,public final boolean postAtTime(Runnable r, long uptimeMillis) { return sendMessageAtTime(getPostMessage(r), uptimeMillis);}//同上,只是又传入了token对象,存储在Message.obj中public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) { return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);}//Runnable包装成Message后加入到MessageQueue中,//但是Message.when = now + delayMillis,//表示延迟delayMills后执行public final boolean postDelayed(Runnable r, long delayMillis) { return sendMessageDelayed(getPostMessage(r), delayMillis);}//Runnable包装成Message后加入到MessageQueue中,此时when=0,所以一定是在MessageQueue的队头public final boolean postAtFrontOfQueue(Runnable r) { return sendMessageAtFrontOfQueue(getPostMessage(r));}//把Runnable对象包装成Message对象,可见只是把Runnable对象赋值给了Message的callback域private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m;}//上述方法的重载方法,把token赋值给了Message的obj域,可以用这个方法进行传Object数据private static Message getPostMessage(Runnable r, Object token) { Message m = Message.obtain(); m.obj = token; m.callback = r; return m;}
5.2.2 sendMessage
方式
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0);}public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0);}public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis);}//延迟发送,把当前时间加上延迟时间后调用了sendMessageAtTime()方法public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}//发送消息的方法,对queue判空后,调用enqueueMessage进行实际入队public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis);}//实际对消息入队的方法,在该方法中,会把Message的target域进行赋值,//如果mAsynchronous是true,则会调用setter方法把消息设置为异步消息,//调用的入队方法其实是调用的MessageQueue的enqueueMessage方法private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis);}//...//省略其他方法,基本上跟post系列方法是一一对应的
- 几个时间关系:
when = now + delay
,when 表示分发消息(dispatchMessage
)的时间,now表示当前(相对系统启动)时间SystemClock.uptimeMillis()
,delay表示延迟时间delayMillis
;
post(runnable)
方式和sendMessage(msg)
方式发送消息的联系和区别:
- 联系:调用链都是
handler.sendMessageAtTime()
->messageQueue.enqueueMessage()
将Message发送到消息队列; - 区别:
- 消息内容不同:
sendMessage
发送的消息侧重于传数据,而handleCallback
侧重于传任务(Runnable); - 处理消息的方式不同:一般情况,
sendMessage
发送的消息最终会调用handler或callback的handleMessage
方法来处理;而post(runnable)
发送的消息最终会调用handler.handleCallback
方法来处理;
- 消息内容不同:
5.3 处理消息
//消息分发 public void dispatchMessage(Message msg) { if (msg.callback != null) {//post的Runnable参数 handleCallback(msg); } else { if (mCallback != null) {//Handler(Callback)构造器,Handler无需派生子类 if (mCallback.handleMessage(msg)) {//一般为true,若为false,还要执行handleMessage return; } } handleMessage(msg);//优先级最低 } } private static void handleCallback(Message message) {//处理消息方式1,优先级最高 message.callback.run();//执行post的Runnable参数地run回调方法,因此Runnable run里可以有一些更新UI的操作 } public interface Callback {//优先级次之 public boolean handleMessage(Message msg);//处理消息方式2,在调用Handler(Callback)构造器实例化Handler时实现该方法 } //Handler子类必须实现这个空方法来接收消息 public void handleMessage(Message msg) {//处理消息方式3,优先级最低 }
- 有两类发送消息的方式:
sendMessage(msg)
方式和post(Runnable r)
方式; - 处理消息方式有三种:
handleCallback
方式、callback.handleMessage
方式、handler.handleMessage
方式;并且优先级递减;
6 四要素之间的关系
6.1 四要素之间的关系
- 一个线程中可创建多个Handler对象;但是一个线程中只能创建一个Looper对象(因为一个线程中只能调用一次
Looper.preapre()
,否则会报异常); - 一个Looper对象可以对应多个线程,比如主线程的mainLooper,供主线程和所属子线程(
Looper.getMainLooper()
)共同使用; - Looper类中有一个
final MessageQueue mQueue
属性; - Handler类中有一个属性
final Looper mLooper
属性,Handler关联的消息队列通过Looper获取; - 一个消息队列中有多个Message对象,不同消息的target可以不同,所以消息队列中的消息可以来自不同的Handler对象;
- Message类有一个
Handler target
属性,这是消息对象关联的Handler对象;
6.2 异步消息处理机制的原理(图非常重要)
以下面应用场景为例:在主线程里实例化Looper和Handler;在子线程(工作线程)处理耗时任务,由于要将任务执行结果在UI上展示,需要更新UI;在子线程中创建一个更新UI的Message对象,并使用Handler对象的引用发送该消息;最后在主线程里处理消息,更新UI;下面是sample代码:
public class MainActivity extends AppCompatActivity { @BindView(R.id.textView) TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); ButterKnife.bind(this); } @OnClick(R.id.button) public void getData() { new Thread(new Runnable() { @Override public void run() { //执行耗时操作... Message msg=new Message();msg.what=7;msg.obj= "网络数据"; //处理消息时回调handler.handleMessage// handler1.sendMessage(msg); //处理消息时回调callback.handleMessage// handler2.sendMessage(msg); //此处使用handler1,2,3 post消息都可以,但是不会执行handleMessage handler3.post(new Runnable() { @Override public void run() { //不会开启新线程执行,handleCallback执行run里的代码,所以不会报错 textView.setText("耗时操作处理结果"); } }); } }).start(); } Handler handler3=new Handler(); //匿名内部类向上转型Handler()方式,派生子类 Handler handler1=new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case 7: textView.setText(msg.obj.toString());//更新UI break; } } }; //Handler(Callback)方式,不派生子类 Handler handler2=new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { textView.setText(msg.obj.toString());//更新UI return true;//修改为true } });}
7 常见面试问题
(1) 为什么创建 Message 对象推荐使用 Message.obtain()获取而不是new方式?
Handler 机制在 Android 系统中使用太频繁,为了提神效率,为Message设置了一个静态的消息池,当消息被处理完或移除后,会放入到消息池;下次需要使用Message时从消息池中取出消息进行复用;
(2) 简述MessageQueue 如何入队和出队?
- 消息入队:调用
enquueMessage(msg,when)
;如果消息没设置when或者when是0,直接将消息放到队列头部;否则遍历队列链表,找到第一个大于当前消息when的消息结点,插入到该节点前面;最后会形成一个按when升序的单链表; - 消息出队:调用
next()
方法,直接取出队列头部消息并返回;
(3) 发送消息两种主要方式 sendMessage(msg)
方式和post(Runnable r)
方式的区别?
post(runnable)
方式和sendMessage(msg)
方式发送消息的联系和区别:
- 联系:调用链都是
handler.sendMessageAtTime()
->messageQueue.enqueueMessage()
将Message发送到消息队列; - 区别:
- 消息内容不同:
sendMessage
发送的消息侧重于传数据,而handleCallback
侧重于传任务(Runnable); - 处理消息的方式不同:一般情况,
sendMessage
发送的消息最终会调用handler或callback的**handleMessage
方法来处理;而post(runnable)
发送的消息最终会调用handler.handleCallback
**方法来处理;
- 消息内容不同:
(4) 处理消息有哪几种方式,他们之间优先级?
- 处理消息方式有三种:
handler.handleCallback
方式、callback.handleMessage
方式、handler.handleMessage
方式;并且优先级递减;
(5) Handler发送、处理消息有哪几种方式?
- 结合有以下三种常见的 Handler发送、处理消息 的方式:
//方式1:send+派生方式//发送消息sendMessage;构造器Handler();处理消息handler.handleMessage;//注意这种方式由于存在Handler子类内部类,可能存在内存泄漏的情况,需要处理这种情况handler1.sendMessage(msg);//匿名内部类向上转型Handler()方式,需要派生子类 Handler handler1=new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case 1: textView.setText(msg.obj.toString());//更新UI break; } } }; //方式2:send+Callback方式 //发送消息sendMessage;构造器Handler(Callback);处理消息callback.handleMessage; handler2.sendMessage(msg); //Handler(Callback)方式,不派生子类 Handler handler2=new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { textView.setText(msg.obj.toString());//更新UI return true;//修改为true } }); //方式3:post(Runnable r)方式 //发送消息post(Runnable r);构造器Handler();处理消息handleCallback; //此处使用handler1,2,3 post消息都可以,但是不会执行handleMessage handler3.post(new Runnable() { @Override public void run() { //不会开启新线程,handleCallback执行run里的代码,所以不会报错textView.setText("耗时操作处理结果"); }}); Handler handler3=new Handler();
(6) 异步消息处理机制的原理(Handler发送消息、处理消息的流程)?(高频题也是本章核心和概要,很重要)
- Handler消息处理器 作用:发送消息(任务),处理消息;
- Looper消息泵 作用:调用
Looper.loop()
方法进行消息轮询; - MessageQueue消息队列 作用:存放消息的场所,是一个按when值递增的单链表;
queue.enqueue(msg,when)
用于消息入队;queue.next()
方法用于消息出队,取队首消息;
(7) post(Runnable r)
方式是否会开启新线程?
这种方式不会开启新线程;
- Runnable对象会包装成Message对象,r作为Message对象的callback属性;
- 然后调用
handler.sendMessageDelay()
->handler.sendMessageAtTime()
->messageQueue.enqueueMessage()
将Message对象发送到消息队列; - Looper在开启消息轮询后,到一定时间会从消息队列中取出该消息对象,交给对应的target(Handler对象)进行消息分发;
- 然后调用
handler.handleCallback()
方法处理消息,在这个方法里Runnable任务会得到执行;
(8) 区分两个callback?区分两个handleMessage
?
两个callback:
Message
的Runnable callback
属性:使用post(Runnable r)
里的Runnable对象初始化;Handler
的final Callback mCallback
属性:在Handler(Callback callabck)
构造器进行初始化;
两个handleMessage
方法,对应处理消息的两种方式:
handler.handleMessage
:send+派生方式调用该方法来处理消息;callback.handleMessage
:send+Callback方式调用该方法来处理消息;
(9) 为什么Handler会造成内存泄漏?如何解决?
(i) 内存泄漏的原因?
- 一般造成内存泄漏的原因:长生命周期对象引用短生命周期对象;
- Handler造成Activity内存泄漏的原因:Handler生命周期比Activity长,非静态内部类默认持有外部类的引用,导致Activity对象无法回收(Activity对象先回收时,Handler对象可能还在处理消息,此时Handler对象还持有Activity对象的引用,导致Activity对象无法回收)。
(i) 解决办法?
把Handler子类定义为静态(static)内部类;同时用WeakReference包装外部类的对象activity;
- 为什么Handler子类要定义为静态(static)内部类?
因为静态内部类不持有外部类的引用,所以使用静态的Handler不会导致Activity内存泄露。 - 为什么Handler子类定义为静态(static)内部类同时,还要用WeakReference包装外部类的对象activity ?
因为我们需要访问外部类的非静态成员,可以通过强引用"activity. "访问,如果直接使用强引用访问,显然会导致activity泄露。
MyHandler mHandler=new MyHandler(this); private static class MyHandler extends Handler{ //static和WeakReference是为了解决内存泄漏 private WeakReference<MainActivity> weakReference; public MyHandler(MainActivity activity) { weakReference = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { switch (msg.what){ case 1: MainActivity activity=weakReference.get(); if(activity != null){//判空是为了避免空引用异常 activity.textView.setText(msg.obj.toString()); } break; } } }
(10) 为何Looper.loop()
死循环不会造成应用卡死?
Looper.loop()
不会造成应用卡死,因为里面使用了Linux 的epoll机制;
(11) 创建Handler前要注意什么?
创建Handler对象前必须调用Looper.prepare()
方法创建一个Looper对象;值得一体的是,子线程中必须手动调用Looper.prepare()
方法,而主线程中可以不调用;因为主线程ActivityThread
的main方法中默认调用了Looper.prepareMainLooper()
方法,这个方法会调用Looper.prepare()
方法创建一个主线程Looper对象;
(12) 异步消息处理机制是如何保证消息处理器的唯一性(即某条消息的发送者和处理者是同一Handler对象)?
在Handler的enqueueMessage
方法中会把自引用赋值给被发送的Message的target属性;而在Looper的loop
方法中会调用msg.target.dispatchMessage(msg)
来分发、处理消息;
//Handler.java private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this;//... } //Looper.java public static void loop() { //... msg.target.dispatchMessage(msg); //... }
(13) 子线程中是否可以创建Handler对象?
子线程中可以创建Handler对象,但是子线程在创建Handler对象前必须手动调用Looper.prepare()
方法创建Looper对象;
new Thread(new Runnable() { @Override public void run() { if(Looper.myLooper()==null){//保证一个线程只有一个Looper Looper.prepare();//创建Looper } Handler handler1=new Handler(); Looper.loop();//开启消息轮询 //主线程Looepr对象早已创建,并早已开启消息轮询 Handler handler2=new Handler(Looper.prepareMainLooper()); }}).start();
ps:如果在主线程中使用子线程中创建的Handler对象的引用发送消息,最后消息是在子线程中处理的;这样就实现了主线程向子线程发送消息,而在本文6.2节中的sample代码中实现了子线程向主线程发送消息;所以两个线程可以通过Handler进行双向通信;
(14) Handler 与 Looper 是如何关联的?
- 对于有Looper参数的构造器Handler(looper):直接通过构造器参数设置关联的Looepr对象;
- 对于无Looper参数的Handler构造器:无论是
Handler()
还是Handler(callback)
都会调用下面的构造器,在这个构造其中会为当前Handler关联当前线程的Looper对象;
public Handler(Callback callback, boolean async) {//... mLooper = Looper.myLooper();//获取当前线程的Looepr对象//... } public static @Nullable Looper myLooper() { return sThreadLocal.get();//获取当前线程的本地变量 }
(15) Thread 与 Looper 是如何关联的?
Looper 与 Thread 之间是通过 ThreadLocal
关联的,这个可以看 Looper.prepare(quitAllowed)
方法:
// Looper.java:93private static void prepare(boolean quitAllowed) {//... sThreadLocal.set(new Looper(quitAllowed));//设置当前线程本地变量}
Looper 类有一个 ThreadLocal 类型的 sThreadLocal静态属性,Looper通过它的 get 和 set 方法来赋值和取值;
由于 ThreadLocal是与当前线程是绑定的,所以我们只要把 Looper 与 ThreadLocal 绑定了,那 Looper 和 Thread 也就关联上了;
(16) 如何在子线程中获取当前线程的 Looper?
Looper.myLooper();//获取当前线程的Looepr对象
内部原理就是sThreadLocal.get()
:
// Looper.java:203public static @Nullable Looper myLooper() { return sThreadLocal.get();}
(16) 如何在任意线程获取主线程的 Looper?
Looper.getMainLooper();//获取主线程的Looper对象
这个在我们开发 API时特别有用,毕竟你不知道开发者在使用你的API时会在哪个线程初始化Looper,所以我们在创建 Handler 时每次都通过指定主线程的 Looper 的方式保证API正常运行。所以一般使用主线程Looper来进行异步消息处理;
(17) 如何判断当前线程是不是主线程?
//方式1Looper.myLooper() == Looper.getMainLooper();//方式2Looper.getMainLooper().getThread() == Thread.currentThread();//方式3:方式2简化版Looper.getMainLooper().isCurrentThread();
(18) Looper.loop()
方法会退出吗?
不会自动退出,但是我们可以手动调用 looper.quit()
或looper.quitSafely()
方法退出消息轮询。
这两个方法都会调用MessageQueue#quit(boolean)
方法让MessageQueue#mQuitting
属性置为true
,标记消息队列已退出;消息队列退出后,MessageQueue#next()
方法发现已经调用过 MessageQueue#quit(boolean) 时会 return null ;然后Looper.loop()
方法退出消息轮询;
如果looper.quit()
,looper.quitSafely()
,MessageQueue#quit(boolean)
都不手动调用并且消息队列为空,消息队列不会退出;next()
方法会一直死循环(有的说法称为next方法阻塞),loop()
方法会在Message msg = queue.next();
处阻塞等待新消息到达消息队列,继续消息轮询。所以建议当所有Message都被处理完之后手动调用looper.quit()
或looper.quitSafely()
方法退出消息轮询,避免loop()
方法一直阻塞等待。
//Looper.java#322 public void quit() {//Looper退出 mQueue.quit(false); } //Looper.java#338 public void quitSafely() { mQueue.quit(true); } //Looper.java#137 public static void loop() {//开启消息轮询 //... for (;;) { Message msg = queue.next(); // 当消息队列为空且未退出时,next方法会阻塞 if (msg == null) { //next返回null表明消息队列已退出 return;//结束轮询,loop方法唯一出口 }//... msg.target.dispatchMessage(msg); //... } } //MessageQueue.java#416 void quit(boolean safe) { //... mQuitting = true; //... } //MessageQueue.java#310 Message next() { //... for (;;) { synchronized (this) {//... Message msg = mMessages;//... if (msg != null) { if (now < msg.when) { //... }else{ //... return msg;//取到非空消息退出 } }else{ // No more messages. nextPollTimeoutMillis = -1; } //若消息队列已退出,返回true退出死循环 if (mQuitting) { dispose(); return null; } }
(19) MessageQueue#next()
方法在消息队列为空时会阻塞,如何恢复?
使用Handler的sendMessage、post 等一系列方法发送消息,这些发送消息的方法会调用MessageQueue#enqueueMessage
将新消息入队,从而使得next()
方法不再阻塞;
(20) IdleHandler作用和使用场景?
把页面启动时的复杂逻辑交给IdleHandler去处理,这样可以让主线程Handler先处理完相关UI逻辑后再去处理复杂逻辑,可以减少页面启动白屏时间,从而优化页面启动;
(21) 子线程为何不能访问UI?
- 源码角度:当访问UI时,ViewRootImpl会调用checkThread()方法检查当前线程是哪个线程,如果不是UI线程会抛出异常;
- 线程安全角度:访问UI不是线程安全的;
- 访问UI为什么不加锁:逻辑复杂、效率低;
更多相关文章
- Android -- SharedPreferences保存基本数据、序列化对象、List数
- android 线程中的ui问题 Handler的基本使用 关于获取动态时间在u
- android UI线程向子线程发送Message
- Android webview与js交换JSON对象数据示例
- 探究Android异步消息的处理之Handler详解
- Android的子线程能更新UI吗?
- android 线程大集合
- [原]采用MQTT协议实现Android消息推送
- Android中Intent传递对象的两种方法(Serializable,Parcelable)