Handler相关看这篇就够了
说起Handler,很容易就想起了Handler、Looper、Message、MessageQueue这4个东东,下面,我们通过几个问题来加深对Handler的了解和学习:
首先,我们先做一下梳理:
1、一个Thread里可以有多个Handler
这个没什么好说的,你想在一个Thread里面创建多少个Handler都行,只是有没有这个必要而已。
2、一个Thread只能对应一个Looper
我们都知道使用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));
}
很明显,prepare()调用了prepare(boolean quitAllowed)方法,其中的sThreadLocal是一个ThreadLocal<Looper>对象,也就是说,使用了ThreadLocal来为每一个线程创建一个Looper副本,而且是有且只有一个,从以上代码也可以直观的说明这一点,如果已经有Looper了,再调用prepare()是会报运行时错误的。(ThreadLocal相关请参考这个)
3、一个Looper只能管理着一个MessageQueue
这个也没什么好说的,源码中有且只有一个,而Handler中的MessageQueue用的是Looper中的MessageQueue
4、由以上就可以得出,一个Thread中的多个Handler共享了一个Looper和一个MessageQueue
比如在同一Thread有Handler a和Handler b,那么调用a.sendMessage(msg1)和b.sendMessage(msg2)后,msg1和msg2都会进入到同一个MessageQueue中,然后由同一个Looper进行分发给自己的Handler进行消费。
5、为什么在主线程中新建Handler不用调用Looper.prepare()和Looper.loop()方法呢?
是的,如果我们在主线程使用Handler的话,那么我们直接Handler handler=new Handler();就可以用了,但是如果你在子线程这样的话是不行的,因为Looper都没创建,必须先调用Looper.prepare()为子线程创建Looper对象,并且之后调用Looper.loop()进行消息队列的循环。
那么为什么在主线程可以不用呢?其实最终还是调用了,只是不用我们自己显示编码而已,我们看一下主线程的入口类ActivityThread的源码,如下:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();//在这里!!!
ActivityThread thread = new ActivityThread();
thread.attach(false);
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");
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
源码已经很清晰的说明这一点了(文中中文注释),这里就不做过多的说明了。
6、HandlerThread又是什么呢?与Handler有什么关系呢?
首先,HandlerThread继承于Thread,也就是说,它是一个线程,那么,它肯定有run方法,我们直接看一下其源码:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();//在这里!!!
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();//在这里!!!
mTid = -1;
}
注意上面源码中的中文注释,也就是说,HandlerThread帮我们完成了Looper的创建及相关实现什么的,这样我们就可以在子线程中轻松愉快的使用Handler了,使用起来的代码大概是这样子的:
HandlerThread handlerThread=new HandlerThread("myThread");
handlerThread.start();
Handler handler=new Handler(handlerThread.getLooper());
使用是很简单,但我们为什么要用HandlerThread呢?
首先,如果是长时间多任务的话,肯定是不能放在主线程进行的,因为这样会阻塞主线程,导致触摸啊输入啊等一些动作的响应,甚至会出现ANR。
于是,对于此类任务,我们肯定是要在子线程中去完成的,而HandlerThread让我们可以像在主线程中使用Handler那样,在子线程中方便快捷的使用Handler,所以,我们为何不用呢?
当然,请注意一点,在使用完HandlerThread后,请调用handlerThread.quit()来释放Looper。
7、为什么在主线程中调用了Looper.loop()这个死循环不会导致ANR呢?
这个经常是作为一个面试题来着的,如果你平常没思考过的话,很容易会被问懵圈的,但当你想通了之后,你会发现,这个问题其实并不是个问题啊!
首先,我们上面第5点已经说明了,我们在主线程的入口类ActivityThread调用了Looper.loop()进行了消息循环,而如果一旦退出消息循环的话,你的应用也就退出了。
而Android 是由事件驱动的,Looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。
另一方面,主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
当然,如果某个消息处理时间过长,比如你在onCreate(),onResume()里面处理耗时操作,那么下一次的消息比如用户的点击事件不能处理了,整个循环就会产生卡顿,时间一长就成了ANR。
因此,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。(参考这里)