参考文献:
- 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 sThreadLocal = new ThreadLocal();
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;
//主线程中不需要自己创建Looper
public 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 主要属性
//是否发现潜在的内存泄漏,默认为false
private static final boolean FIND_POTENTIAL_LEAKS = false;
private static final String TAG = "Handler";
//静态全局变量,主线程的Handler
private 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 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:93
private 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:203
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
(16) 如何在任意线程获取主线程的 Looper?
Looper.getMainLooper();//获取主线程的Looper对象
这个在我们开发 API时特别有用,毕竟你不知道开发者在使用你的API时会在哪个线程初始化Looper,所以我们在创建 Handler 时每次都通过指定主线程的 Looper 的方式保证API正常运行。所以一般使用主线程Looper来进行异步消息处理;
(17) 如何判断当前线程是不是主线程?
//方式1
Looper.myLooper() == Looper.getMainLooper();
//方式2
Looper.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为什么不加锁:逻辑复杂、效率低;