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精编内核解析》,可以在评论区留言或私信我获取!