万人收藏!关于Android Handler源码解析,看这一篇就够了!
Posted 陈京大帅比
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了万人收藏!关于Android Handler源码解析,看这一篇就够了!相关的知识,希望对你有一定的参考价值。
Handler作用
- 任务调度:即通过
post()
和send()
等方法来指定某个任务在某个时间执行 - 线程切换:执行耗时的操作,比如网络请求,IO操作等,需要在子线程中运行,不然会阻塞主线程。 而执行完网络请求等耗时操作后通常需要更新UI,如果在子线程中更新UI,那么程序会崩溃。因为android的UI是线程不安全的。 在Android中使用Rxjava,还要配合RxAndroid来使用,RxAndroid 内部就使用 Handler 来实现线程切换。
常见错误
常见的子线程中更新UI,复现代码。
textView = (TextView) findViewById(R.id.txt);
new Thread(new Runnable() {
public void run() {
SystemClock.sleep(3000);//这句不加不会报错,具体分析见上面链接
textView.setText("from来自子线程");
}
}).start();
复制代码
运行异常信息
ErrorInfo: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6903)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1050)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.widget.TextView.checkForRelayout(TextView.java:7368)
at android.widget.TextView.setText(TextView.java:4480)
at android.widget.TextView.setText(TextView.java:4337)
at android.widget.TextView.setText(TextView.java:4312)
复制代码
可以看到错误发生在android.view.ViewRootImpl#checkThread
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
复制代码
可见此处会判断mThread
是不是等于当前线程 看下mThread
到底是啥,在何处赋值的
public ViewRootImpl(Context context, Display display) {
...
mThread = Thread.currentThread();
...
}
复制代码
在构造方法中被赋值的,也就是说是创建ViewRootImpl
时所在的线程 ViewRootImpl
又是在哪里被创建的呢?这里不深入讲了,是在main线程。
基础用法
android.os.Handler handler = new Handler(){//在主线程中获取handler
@Override
public void handleMessage(final Message msg) {
//这里接受并处理消息
}
};
new Thread(() -> {
try {
Thread.sleep(2000);//子线程中执行耗时操作
//发送消息
Message message = Message.obtain();
message.what=1;
message.obj=new Object();
handler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Handler().post(new Runnable() {
@Override
public void run() {
//doSomething
}
});
复制代码
实例化一个 Handler
重写handleMessage
方法 ,然后在需要的时候调用它的 send
以及 post
系列方法就可以了,非常简单易用,并且支持延时消息。(更多方法可查询 API 文档)
但是我们并没有看到Handler
是如何与MessageQueue
以及Looper
关联起来的,下面我们进入源码分析下
Handler 源码分析
Handler 实例化
从构造函数开始,我们通常从主线程中创建,先看下Handler的构造函数有哪些
-
Handler()
-
Handler(Callback callback)
-
Handler(Looper looper)
-
Handler(Looper looper, Callback callback)
-
Handler(boolean async)
-
Handler(Callback callback, boolean async)
-
Handler(Looper looper, Callback callback, boolean async)
看最后两个构造方法就行,因为前面的几个也是依次调用到后的方法
先看Handler(Callback callback, boolean async)
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
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(Looper looper, Callback callback, boolean async)
与上面的区别就是Looper
是赋值进去的。
Looper 实例化
由上面可以看到调用Looper#myLooper
方法获取到Looper对象, 如果mLooper == null的话,会抛出异常
Can’t create handler inside thread that has not called Looper.prepare()
这个错误我们应该也见过。实际上我们在实例化 Handler
的时候 会去检查当前线程的 Looper
是否存在,如果不存在则会报异常,也就是说在创建 Handler 之前一定需要先创建 Looper
。 我们平时一般不会遇到这个错,因为我们大多数都是在主线程创建Handler
的,而为什么在主线程就不要自己创建Looper
,我们待会再看,目前只需要知道如果Looper.myLooper()
没有获取到Looper
对象的话就会报这个错。
我们跟踪Looper#myLooper
方法进去,解决为什么会抛出这个异常。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
复制代码
只有一行代码,从线程中取出Looper
对象,那么我们有理由相信,这个ThreadLocal
是通过set方法把Looper
对象设置进去的。
想一想ThreadLocal在哪里把Looper对象设置进去了呢。回到刚才想要解决的问题:Can’t create handler inside thread that has not called Looper.prepare() 。那会不会是Looper的prepare方法呢?
public static void prepare() {
prepare(true);
}
//调用私有构造方法
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));
}
复制代码
ThreadLocal
确实是在Looper#prepare
方法里把Looper
对象设置进去的,而且从第一行的判断可以知道,一个线程只有一个Looper
对象。
所以,要创建Handler
,那么Looper.myLooper()
就必须非空,上面分析得出要非空,要先调用Looper.prepare()
。
到了这里,Looper
与ThreadLocal
建立起了关联。
MessageQueue 实例化
接着上面继续看下Looper
的构造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
复制代码
每当我们实例化一个 Looper
的时候会调用它的构造方法,并在其中实例化一个 MessageQueue
,相比于 Looper
和 Handler
,MessageQueue
就显得相对复杂一些。因为内部用到了 JNI 编程。初始化、销毁和入队等事件都用到了 native
的方法。
我们接着看Handle
构造函数里的
mQueue = mLooper.mQueue
我们知道消息是存放在MessageQueue
消息队列中的,而MessageQueue
就是在上面Looper
构造函数中new出来的,至此Handler
通过Looper
与MessageQueue
也建立起了关联。
总结一下,创建Handler
,他的构造函数中会先调用Looper.myLooper()
获取Looper,也即是从ThreadLocal
中获取,而ThreadLocal
中要想获取到,要先调用Looper.prepare()
来set值,那么问题又来了,我们写程序时好像没有手动调用Looper.prepare()
吧,也不会抛出异常。其实这是一个特殊情况,我们通常都是在主线程,也就是UI线程中创建handler的。而在主线程中,系统已经为我们创建了一个Looper
对象,所以不会抛出异常了,而那些会抛出异常报错的情况,是在子线程中创建的Handler
,但是又没有调用Looper.prepare()
去创建Looper
对象。 继续看,主线程在什么时候创建了Looper
对象吧。
在ActivityThread
的main方法,这个方法是应用程序的入口。
public static void main(String[] args) {
...
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
复制代码
Looper.prepareMainLooper();
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
复制代码
可以看到第一行还是调用了prepar(false)
方法的(false代表不可退出)。所以主线程是已经创建了一个Looper
对象的。
Handler
的创建过程分析完毕,现在总算搞明白了。
Handler、MessageQueue 和 Looper 之间的关系
最后再总结一下,Handler
的创建是依赖于Looper
的。而主线程是默认创建了一个Looper
对象的。每一个Looper
会关联一个线程(ThreadLocal
中封装了Looper
)。每一个Looper
中又会封装一个消息队列。 这样一来,Handler
,Looper
,MessageQueue
,Thread
四个角色就关联了起来。 Handler
在主线程中创建,是因为要和主线程的消息队列关联起来,那样Handler#handleMessage
方法才会在主线程中执行,那么这样在更新UI就是线程安全的了。
Handler 发送消息过程
回想开头我们基础用法里提到 Handler
一般是通过一下2个方法发送的
handler.sendMessage(message); handler.post(runnable);
发送过程
我们先从第一个开始分析 handler.sendMessage(message)
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);
}
复制代码
sendMessage
会调用sendMessageDelayed
方法并将message
对象传进去,第二个参数是延时时间,使用sendMessage
方法时默认为0的,最后都会调用sendMessageAtTime
。 上面分析了,在创建Looper
对象的时候,会创建一个MessageQueue
,所以只要Looper
是正常创建的话,消息队列是不为空的。 那么到最后一行的enqueueMessage
方法,源码如下
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码
将handler
本身赋值给msg.target
msg.setAsynchronous(true
设置message是否是异步的,这是message的一个属性。同一个Thread只有一个Looper,一个MessageQueue,但是可以有很多个Handler,如果Handler
初始化的时候async参数是true,那么这个Handler
所post的所有的message都会带上异步的属性。可以通过MessageQueue``的postSyncBarrier(long when)
来向队列中插入一个同步分割栏,同步分割栏是一个特殊的message,这种message的target=null,就像一个卡子,当他被插入时,会卡住在这之后的所有的同步的message,只会摘取异步的message。当然也可以通过MessageQueue的removeSyncBarrier(int token)来移除这个同步分割栏,token就是postSyncBarrier方法的返回值。但是目前这两个方法都被hide了。所以大家一般用到的都只是普通的Message。
然后最终调用queue.enqueueMessage
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) {
//很明显enqueueMessage需要同步,因为存在多个线程往一个Loop线程的MessageQueue中插入消息的场景。
//这里其实是将Message根据延时插入到特定的地方,先看下关键点1,mMessages其实代表消息队列的头部,如果mMessages为空,说明还没有消息,如果当前插入的消息不需要延时,或者说延时比mMessages头消息的延时要小,那么当前要插入的消息就需要放在头部
//至于是否需要唤醒队列,则需要根据当前的Loop线程的状态来判断,后面讲Loop线程的时候再回过头说;
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//再来看下关键点2,这个时候需要将消息插入到队列中间,其实就是找到第一个Delay事件小于当前Message的非空Message,并插入到它的前面,往队列中插入消息时,如果Loop线程在睡眠,是不应该唤醒的,异步消息的处理会更加特殊一些,先不讨论。
//最后看关键点3,如果需要唤醒Loop线程,通过nativeWake唤醒,以上,就是普通消息的插入。
// 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;
}
复制代码
Messagequeue
中有一个对象mMessage
用于指向当前传进的msg
,即最新的消息。而刚才的sendMessageAtTime(Message msg, long uptimeMillis)
方法,第二个参数指定了时间,然后在这里按照这个uptimeMillis
来进行消息的排序,这样每一个消息都是按照时间的排序关联了起来,排在前面的消息指向了排在后面的消息。
以上是进入消息队列的分析,Handler
调用sendMessage
方法的最终将message
对象传进Messagequeue
。
取出消息
那么消息是怎么从消息队列出来的呢? 这时我们要回看ActiviryThread
的main方法,去寻找点线索。源码在上面已贴出。 发现了倒数第二行的Looper.loop()
,简单理解就是消息执行循环操作。 android.os.Looper#loop
public static void loop() {
//确保MessageQueue准备好
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 (;;) {
//for 无限循环,阻塞于消息队列的 next() 方法;
//不断从队列中读取消息并移除,如果队列为空,阻塞等待
Message msg = queue.next(); // might block
if (msg == null) {//跳出循环,looper退出就是利用了这点
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);
...
} finally {
...
}
...
//清理,回收到缓存池
msg.recycleUnchecked();
}
}
复制代码
loop方法是个死循环,但是为什么不会卡死主线程呢?
loop内容有点复杂,借用一张图来看下
当我们调用 Looper#loop()
方法之后整个 Looper
循环就开始不断地处理消息了。在上图中就是我们用绿色标记的一个循环。当我们在循环中调用 MessageQueue#next()`` 方法来获取下一个消息的时候,会调用 nativePollOnce()
方法,该方法可能会造成线程阻塞和非阻塞,当线程为非阻塞的时候就会从 Native 层回到 Java 层,从 MessageQueuue
中取得一个消息之后给 Looper
进行处理。如果获取的时候造成线程阻塞,那么有两种情况会唤醒阻塞的线程,一个是当一个新的消息被加入到队列中,并且将会早于之前队列的所有消息被触发,那么此时将会重新设置超时时间。如果达到了超时时间同样可以从睡眠状态中返回,也就回到了 Java 层继续处理。所以,Native 层的 Looper
的作用就是通过阻塞消息队列获取消息的过程阻塞 Looper
。
再看下关键的Message msg = queue.next()
Message next() {
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//是否需要阻塞等待,第一次一定不阻塞
// 调用 Native 层的 nativePollOnce() 方法进行精准时间的阻塞。
// 在 Native 层,将进入 pullInner() 方法,使用 epoll_wait 阻塞等待以读取管道的通知。
// 如果没有从 Native 层得到消息,那么这个方法就不会返回。此时主线程会释放 CPU 资源进入休眠状态。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {//互斥同步
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//是否存在barier
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
//存在同步分隔栏,找到后面异步属性的msg
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) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
//需要无限等待
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
//没有可以即刻执行的Message,查看是否存在需要处理的IdleHandler,如果不存在,则返回,阻塞等待,如果存在则执行IdleHandler
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
// 如果目前没有消息,已经处在空闲状态,则执行 idler.queueIdle
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
//处理完IdleHandler ,需要重新判断Message队列 nextPollTimeoutMillis赋值为0
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
复制代码
上面分析过msg.target
就是handler
,所以loop
循环的时候又把消息取出扔给handler#dispatchMessage
方法了,我们来看下
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
复制代码
由于这种方法没有传callback
,所以最终调用handleMessage
,我们来看下
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
复制代码
看到这里,相信大家应该很熟悉了,这就是我们重写的方法。
我们再看看另一个发送消息的方法 handler.post(runnable)
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
复制代码
接收一个实现了Runable
接口的对象,然后将其传进getPostMessage()
方法。跟进getPostMessage()
方法看看
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
复制代码
其实就是将Runable
包装成message的callback
嘛。 所以,如果我们使用post
方法发送消息,在执行dispatchMessage
的时候,callback
字段是不为空的,那么就会执行handleCallback()
方法,而不是执行handleMessage
方法了。
private static void handleCallback(Message message) {
message.callback.run();
}
复制代码
空闲处理者的添加与处理
什么是空闲处理者
通过上面的分析可知 MessageQueue
通过 next
方法通过死循环获取下一个要处理的 Message, 若当前时刻不存在要处理的消息, 下次循环会进行睡眠操作
- 在没有取到可执行消息 —> 下次 for 循环进行睡眠 之间的时间间隔, 称之为空闲时间
- 在空闲时间处理事务的对象, 称之为空闲处理者
空闲处理者的添加
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
复制代码
通过上述代码可以得到以下的信息
- 空闲处理者使用 IdleHandler 接口描述
- 空闲处理者通过 MessageQueue.addIdleHandler() 添加
- 空闲处理者使用 MessageQueue.mIdleHandlers 维护
空闲消息的处理
public final class MessageQueue {
// 空闲消息集合
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
// 空闲消息处理者的数组
private IdleHandler[] mPendingIdleHandlers;
Message next() {
......
for (;;) {
......
synchronized (this) {
// 省略获取 msg 的代码
......
// 1\\. 从空闲消息集合 mIdleHandlers 中获取 空闲处理者 数量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 2 若无空闲处理者, 则进行下一次 for 循环
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
......
// 3\\. 将空闲消息处理者集合转为数组
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 4\\. 处理空闲消息
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];// 获取第 i 给位置的空闲处理者
mPendingIdleHandlers[i] = null; // 置空
boolean keep = false;
try {
// 4.1 处理空闲消息
keep = idler.queueIdle();
} catch (Throwable t) {
......
}
if (!keep) {
synchronized (this) {
// 4.2 走到这里表示它是一次性的处理者, 从 mIdleHandlers 移除
mIdleHandlers.remove(idler);
}
}
}
......
}
}
}
复制代码
好的, 可以看到 MessageQueue.next 在获取不到 msg 时, 会进行一些空闲消息的处理
- 从空闲消息集合 mIdleHandlers 中获取 空闲处理者 数量
- 若无空闲处理者, 则进行下一次 for 循环
- 若存在空闲处理者, 则空闲消息处理者集合转为数组 mPendingIdleHandlers
- for 循环处理空闲消息
- 调用 IdleHandler.queueIdle 处理空闲消息
- 返回 true, 下次再 MessageQueue.next 获取不到 msg 的空闲时间会继续处理
- 返回 false 表示它是一次性的处理者, 从 mIdleHandlers 移除
- 调用 IdleHandler.queueIdle 处理空闲消息
总结
我们发现不管是使用post
方法还是sendMessage
方法来发送消息,最终都会调用sendMessageDelayed
方法。handler
将消息追加到消息队列中的过程都是一样的,然后Looper
不断的从MessageQueue
中取出消息,并由handler
去分发消息,处理消息,这样就构成了完善的Android消息机制体系。
Handler扩展
Handler
虽然简单易用,但是要用好它还是需要注意一点。
由于 Handler
的特性,它在 Android 里的应用非常广泛,比如: AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等。
常见内存泄漏
Handler
允许我们发送延时消息,如果在延时期间用户关闭了 Activity
,那么该 Activity
会泄露。
这个泄露是因为Message
会持有Handler
,而又因为 Java 的特性,内部类会持有外部类,使得 Activity
会被 Handler
持有,这样最终就导致 Activity 泄露。
解决该问题的最有效的方法是:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并及时移除所有消息。
示例代码如下:
private static class SafeHandler extends Handler {
private WeakReference<HandlerActivity> ref;
public SafeHandler(HandlerActivity activity) {
this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
HandlerActivity activity = ref.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
复制代码
并且再在 Activity.onDestroy()
前移除消息,加一层保障:
@Override
protected void onDestroy() {
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
复制代码
这样双重保障,就能完全避免内存泄露了。
注意:单纯的在 onDestroy 移除消息并不保险,因为 onDestroy 并不一定执行。
Handler 里的 Callback 用处
在 Handler
的构造方法中有几个 要求传入 Callback
,那它是什么,又能做什么呢?
来看看 Handler.dispatchMessage(msg)
方法:
public void dispatchMessage(Message msg) {
//这里的 callback 是 Runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
复制代码
可以看到 Handler.Callback
有优先处理消息的权利 ,当一条消息被 Callback
处理并拦截(返回 true),那么 Handler
的 handleMessage(msg)
方法就不会被调用了;如果 Callback
处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。
这个就很有意思了,这有什么作用呢?
我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息!
场景:Hook ActivityThread.mH
, 在 ActivityThread
中有个成员变量 mH
,它是个 Handler
,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。
创建 Message 实例的最佳方式
由于 Handler
极为常用,所以为了节省开销,Android 给 Message
设计了回收机制,所以我们在使用的时候尽量复用 Message
,减少内存消耗。
方法有二:
- 通过
Message
的静态方法Message.obtain();
获取; - 通过
Handler
的公有方法handler.obtainMessage();
。
妙用 Looper 机制
我们可以利用 Looper
的机制来帮助我们做一些事情:
-
将
Runnable
post 到主线程执行Activity.runOnUiThread(Runnable)
public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } } 复制代码
View.post(Runnable)
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { //直接通过handler发送Post消息 return attachInfo.mHandler.post(action); } //先加入队列,等attachInfo被赋值时,会通过handler发送消息. getRunQueue().post(action); return true; } 复制代码
-
利用
Looper
判断当前线程是否是主线程public final class MainThread { private MainThread() { } private static final Handler HANDLER = new Handler(Looper.getMainLooper()); public static void run(@NonNull Runnable runnable) { if (isMainThread()) { runnable.run(); }else{ HANDLER.post(runnable); } } public static boolean isMainThread() { return Looper.myLooper() == Looper.getMainLooper(); } } 复制代码
Looper 和 Handler 一定要处于一个线程吗?子线程中可以用 MainLooper 去创建 Handler吗?
Looper
和 Handler
不需要再一个线程中,默认的情况下会从ThreadLocal
中取当前线程对应的 Looper
,但我们可以通过显式地指定一个 Looper
的方式来创建 Handler
. 比如,当我们想要在子线程中发送消息到主线程中,那么我们可以
Handler handler = new Handler(Looper.getMainLooper());
复制代码
子线程中进行UI操作的方法
-
Handler的post()方法
-
View的post()方法
-
Activity的runOnUiThread()方法
如何理解Handler的异步
MessageQueue.next() 会因为发现了延迟消息,而进行阻塞。那么为什么后面加入的非延迟消息没有被阻塞呢? MessageQueue.next() 方法内部的原理?
调用 MessageQueue.next()
方法的时候会调用 Native 层的 nativePollOnce()
方法进行精准时间的阻塞。在 Native 层,将进入 pullInner()
方法,使用 epoll_wait
阻塞等待以读取管道的通知。如果没有从 Native 层得到消息,那么这个方法就不会返回。此时主线程会释放 CPU 资源进入休眠状态。
当我们加入消息的时候,会调用 MessageQueue.enqueueMessage()
方法,添加完 Message 后,如果消息队列被阻塞,则会调用 Native 层的 nativeWake()
方法去唤醒。它通过向管道中写入一个消息,结束上述阻塞,触发上面提到的 nativePollOnce()
方法返回,好让加入的 Message 得到分发处理。
MessageQueue.enqueueMessage()
使用 synchronized 代码块去进行同步。
Looper 的退出方法?
quit() 和 quitSafely() 有什么区别 子线程中创建了 Looper,在使用完毕后,终止消息循环的方法? quit() 和 quitSafely() 的本质是什么?
quit()
和 quitSafely()
的本质就是让消息队列的 next()
返回 null
,以此来退出Looper.loop()
。 quit()
调用后直接终止 Looper
,不在处理任何 Message
,所有尝试把 Message
放进消息队列的操作都会失败,比如 Handler.sendMessage()
会返回 false,但是存在不安全性,因为有可能有 Message
还在消息队列中没来的及处理就终止Looper
了。 quitSafely()
调用后会在所有消息都处理后再终止 Looper
,所有尝试把 Message
放进消息队列的操作也都会失败。
知识点汇总
由前文可得出一些知识点,汇总一下,方便记忆。
Handler
的背后有Looper
、MessageQueue
支撑,Looper
负责消息分发,MessageQueue
负责消息管理- 在创建
Handler
之前一定需要先创建Looper
Looper
有退出的功能,但是主线程的Looper
不允许退出- 异步线程的
Looper
需要自己调用Looper.myLooper().quit();
退出 Runnable
被封装进了Message
,可以说是一个特殊的Message
Handler.handleMessage()
所在的线程是Looper.loop()
方法被调用的线程,也可以说成Looper
所在的线程,并不是创建Handler
的线程- 使用内部类的方式使用
Handler
可能会导致内存泄露,即便在 Activity.onDestroy 里移除延时消息,必须要写成静态内部类
由于篇幅原因,上文一些知识点,有些没有详细深入的去讲,甚至省略了。想进一步深入学习的同学可以详见我整理编写的《Android Framework精编内核解析》,点击蓝色字体即可获取!
以上是关于万人收藏!关于Android Handler源码解析,看这一篇就够了!的主要内容,如果未能解决你的问题,请参考以下文章