为什么要使用Handler
将工作线程需操作UI
的消息 传递 到主线程,使得主线程可根据工作线程的需求 更新UI
,从而避免线程操作不安全的问题
首先正常Handler写法
1,创建Handler对象
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case:
breank;
}
}
2,创建消息对象
Message msg = mHandler.obtainMessage(SYNC_CALL_LOG);
mHandler.sendMessage(msg);
第一步源码解析,Handler是如何创建的
/**
* Default constructor associates this handler with the {@link Looper} for the
* current thread.
*
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*/
/** * 源码分析:Handler的构造方法
* 作用:初始化Handler对象 & 绑定线程
* 注:
* a. Handler需绑定 线程才能使用;绑定后,Handler的消息处理会在绑定的线程中执行
* b. 绑定方式 = 先指定Looper对象,从而绑定了 Looper对象所绑定的线程(因为Looper对象本已绑定了对应线程)
* c. 即:指定了Handler对象的 Looper对象 = 绑定到了Looper对象所在的线程 */
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
// 如果打开了FIND_POTENTIAL_LEAKS开关
// 会判断是否是非静态匿名内部类、非静态成员内部类或非静态局部内部类
// 如果是,可能发生内存泄漏,发出警告
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());
}
}
mLooper = Looper.myLooper();
//指定Looper对象
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// Looper.myLooper()作用:获取当前线程的Looper对象;若线程无Looper对象则抛出异常
// 即 :若线程中无创建Looper对象,则也无法创建Handler对象
// 故 若需在子线程中创建Handler对象,则需先创建Looper对象
// 注:可通过Loop.getMainLooper()可以获得当前进程的主线程的Looper对象
mQueue = mLooper.mQueue; //绑定消息队列(MessageQueue)
mCallback = callback;
mAsynchronous = async;
}
由上所述:Handler的创建需要创建Looper对象,接下来研究Looper是如何创建的,消息队列(MessageQueue)又是怎么创建的
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
//该方法表示去创建一个Looper
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
//判断ThreadLocal不为null调用该方法的话就会抛出异常,通常面试题会问一个线程有几个Looper,由此可知,一个线程只有一个Looper
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); //创建了一个消息队列
mThread = Thread.currentThread(); //并且使用的线程是当前的线程
}
首先我再主线程中创建Handler之前执行Looper.prepare()发现报错了
java.lang.RuntimeException: Only one Looper may be created per thread
说明主线程创建Handler就已经创建了Looper 那么主线程的loop是如何创建的
发现在创建主线程的时候,之前会调用Looper.prepareMainLooper();
public static void prepareMainLooper() {
prepare(false); //在这就创建了一个Looper
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
由此可知,主线程创建的时候就已经创建了Looper 无需再创建Looper,子线程创建Handler是需要重新去创建Looper
(具体创建子线程ThreadLocal.get()为何反馈null后续会进一步了解ThreadLocal源码解析)
第二步创建完Looper之后,当前Looper是怎么运作的
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
//获取Looper的消息队列,通过ThreadLocal存储的Looper实例
final Looper me = myLooper();
if (me == null) {
//当前的Looper为null抛出该异常,所以在执行loop()方法之前,一定要先执行looper.prepare()
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//获取looper消息队列的消息对象
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,确保这个线程的标识是本地进程的标识,
// and keep track of what that identity token actually is. 并记录下身份标识到底是什么
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//死循环
for (;;) {
Message msg = queue.next(); // might block 取出消息队列的消息,可能会被阻塞
if (msg == null) {
//如果当前的消息是空的话就会被阻塞
// No message indicates that the message queue is quitting.
return;
}
//派发消息到对应的Handler
msg.target.dispatchMessage(msg);
// Make sure that during the course of dispatching the 在调度过程中,保证调度过程的顺利进行
// identity of the thread wasn't corrupted. 线程的标识没有损坏
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
//释放消息所占用的资源
msg.recycleUnchecked();
}
}
/**
* 分析1:queue.next()
* 定义:属于消息队列类(MessageQueue)中的方法
* 作用:出队消息,即从 消息队列中 移出该消息
*/
Message next() {
...// 仅贴出关键代码
// 该参数用于确定消息队列中是否还有消息
// 从而决定消息队列应处于出队消息状态 or 等待状态
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// nativePollOnce方法在native层,若是nextPollTimeoutMillis为-1,此时消息队列处于等待状态
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 出队消息,即 从消息队列中取出消息:按创建Message对象的时间顺序
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;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// 若 消息队列中已无消息,则将nextPollTimeoutMillis参数设为-1
// 下次循环时,消息队列则处于等待状态
nextPollTimeoutMillis = -1;
}
......
}
.....
}
}// 回到分析原处
/**
* 分析2:dispatchMessage(msg)
* 定义:属于处理者类(Handler)中的方法
* 作用:派发消息到对应的Handler实例 & 根据传入的msg作出对应的操作
*/
public void dispatchMessage(Message msg) {
// 1. 若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息
// 则执行handleCallback(msg),即回调Runnable对象里复写的run()
// 上述结论会在讲解使用“post(Runnable r)”方式时讲解
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 2. 若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息(即此处需讨论的)
// 则执行handleMessage(msg),即回调复写的handleMessage(msg) ->> 分析3
handleMessage(msg);
}
}
/**
* 分析3:handleMessage(msg)
* 注:该方法 = 空方法,在创建Handler实例时复写 = 自定义消息处理方式
**/
public void handleMessage(Message msg) {
... // 创建Handler实例时复写
}
(借鉴:https://www.jianshu.com/p/b4d745c7ff7a)
由上可知,Looper做了一个死循环处理,如果当前消息队列有消息的话,就会根据创建消息的时间顺序去发送消息,就会发送给对应的Handler去处理,如果消息没有的话,就会处于等待状态。
(到这我有两个问题,居然已经一直等待,会不会引起ANR?如果不会又是怎么唤醒的呢?)
1.只有主线程才会产生ANR,主线程就是UI线程;
2.必须发生某些输入事件或特定操作,比如按键或触屏等输入事件,在BroadcastReceiver或Service的各个生命周期调用函数;
3.上述事件响应超时,不同的context规定的上限时间不同
a.主线程对输入事件5秒内没有处理完毕
b.主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕
c.主线程在Service的各个生命周期函数时20秒内没有处理完毕。
(借鉴:https://blog.csdn.net/github_37130188/article/details/89609419)
其实是通过epoll机制来唤醒的:连接网址(https://www.cnblogs.com/broadview/archive/2013/01/29/2881284.html)
当触发点击事件的时候也会唤醒Looper,将looper内的资源释放掉,所以不会引起ANR
第三步,上面已经初始化完成,开始创建消息对象
public static Message obtain() {
//内部创建了一个Message池,用来复用Message消息
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
由上可知,Message消息是个链表,如果每次New消息的话都需要重新分配内存,还是这种方式最好,复用消息
第四步,从工作线程中发送消息,到主线程中
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
//发现发送消息的顺序是根据当前时间来发送的
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
//标记当前Handler,目的是为了将消息发送给对应的Handler
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
* 定义:属于消息队列类(MessageQueue)的方法
* 作用:入队,即 将消息 根据时间 放入到消息队列中(Message ->> MessageQueue)
* 采用单链表实现:提高插入消息、删除消息的效率
*/
boolean enqueueMessage(Message msg, long when) {
...// 仅贴出关键代码
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 判断消息队列里有无消息
// a. 若无,则将当前插入的消息 作为队头 & 若此时消息队列处于等待状态,则唤醒
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// b. 判断消息队列里有消息,则根据 消息(Message)创建的时间 插入到队列中
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
// 之后,随着Looper对象的无限消息循环
// 不断从消息队列中取出Handler发送的消息 & 分发到对应Handler
// 最终回调Handler.handleMessage()处理消息
借鉴:https://www.jianshu.com/p/b4d745c7ff7a
由上可知先查看消息队列是否有消息,如果没有的话,插入该消息,之后随着Looper对象消息循环,从消息队列中取出Handler发送的消息,然后分配给对应的Handler,最终回调给Handler.handleMessage()处理消息
如果当前消息队列有消息的话,就会根据创建的时间,插入到队列中。
总结:初始化创建Handler 查看本地是否有Looper,如果没有Looper,需要创建(looper.prepare()),并且Looper本身会绑定当前线程,并创建消息队列,之后调用Looper.loop()去运作准备处理消息,准备工作完成之后,只需要等待消息,通过message.obtain()创建消息,sendMessage()发送消息,如果消息队列里没有消息的话就插入消息,如果消息队列还有消息的话,就按照时间的顺序插入消息链表中,等待looper.loop遍历消息,通过dispatchMessage发送给相应的Handler,最终回调给Handler.handleMessage()处理消息