可以多次创建Looper吗?
Looper的创建是通过Looper.prepare
方法实现的,而在prepare方法中就判断了,当前线程是否存在Looper对象,如果有,就会直接抛出异常:
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)); } private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
所以同一个线程,只能创建一个Looper
,多次创建会报错。
Looper中的quitAllowed字段是啥?有什么用?
按照字面意思就是是否允许退出,我们看看他都在哪些地方用到了:
void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } } }
哦,就是这个quit
方法用到了,如果这个字段为false
,代表不允许退出,就会报错。
但是这个quit
方法又是干嘛的呢?从来没用过呢。还有这个safe
又是啥呢?
其实看名字就差不多能了解了,quit方法就是退出消息队列,终止消息循环。
- 首先设置了
mQuitting
字段为true。 - 然后判断是否安全退出,如果安全退出,就执行
removeAllFutureMessagesLocked
方法,它内部的逻辑是清空所有的延迟消息,之前没处理的非延迟消息还是需要取处理,然后设置非延迟消息的下一个节点为空(p.next=null)。 - 如果不是安全退出,就执行
removeAllMessagesLocked
方法,直接清空所有的消息,然后设置消息队列指向空(mMessages = null)
然后看看当调用quit方法之后,消息的发送和处理:
//消息发送 boolean enqueueMessage(Message msg, long when) { 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; } }
当调用了quit方法之后,mQuitting
为true,消息就发不出去了,会报错。
再看看消息的处理,loop和next方法:
Message next() { for (;;) { synchronized (this) { if (mQuitting) { dispose(); return null; } } } } public static void loop() { for (;;) { Message msg = queue.next(); if (msg == null) { // No message indicates that the message queue is quitting. return; } } }
很明显,当mQuitting
为true的时候,next方法返回null,那么loop方法中就会退出死循环。
那么这个quit
方法一般是什么时候使用呢?
- 主线程中,一般情况下肯定不能退出,因为退出后主线程就停止了。所以是当APP需要退出的时候,就会调用quit方法,涉及到的消息是EXIT_APPLICATION,大家可以搜索下。
- 子线程中,如果消息都处理完了,就需要调用quit方法停止消息循环。
Looper.loop方法是死循环,为什么不会卡死(ANR)?
关于这个问题,强烈建议看看Gityuan的回答:https://www.zhihu.com/question/34652589
我大致总结下:
- 1、主线程本身就是需要一只运行的,因为要处理各个View,界面变化。所以需要这个死循环来保证主线程一直执行下去,不会被退出。
- 2、真正会卡死的操作是在某个消息处理的时候操作时间过长,导致掉帧、ANR,而不是loop方法本身。
- 3、在主线程以外,会有其他的线程来处理接受其他进程的事件,比如
Binder线程(ApplicationThread)
,会接受AMS发送来的事件 - 4、在收到跨进程消息后,会交给主线程的
Hanlder
再进行消息分发。所以Activity的生命周期都是依靠主线程的Looper.loop
,当收到不同Message时则采用相应措施,比如收到msg=H.LAUNCH_ACTIVITY
,则调用ActivityThread.handleLaunchActivity()
方法,最终执行到onCreate方法。 - 5、当没有消息的时候,会阻塞在loop的
queue.next()
中的nativePollOnce()
方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。所以死循环也不会特别消耗CPU资源。
Message是怎么找到它所属的Handler然后进行分发的?
在loop方法中,找到要处理的Message
,然后调用了这么一句代码处理消息:
msg.target.dispatchMessage(msg);
所以是将消息交给了msg.target
来处理,那么这个target是啥呢?
找找它的来头:
//Handler private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) { msg.target = this; return queue.enqueueMessage(msg, uptimeMillis); }
在使用Hanlder发送消息的时候,会设置msg.target = this
,所以target就是当初把消息加到消息队列的那个Handler。
Handler 的 post(Runnable) 与 sendMessage 有什么区别
Hanlder中主要的发送消息可以分为两种:
- post(Runnable)
- sendMessage
public final boolean post(@NonNull Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
通过post的源码可知,其实post和sendMessage
的区别就在于:
post方法给Message设置了一个callback
。
那么这个callback有什么用呢?我们再转到消息处理的方法dispatchMessage
中看看:
public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); }
这段代码可以分为三部分看:
- 1、如果
msg.callback
不为空,也就是通过post方法发送消息的时候,会把消息交给这个msg.callback进行处理,然后就没有后续了。 - 2、如果
msg.callback
为空,也就是通过sendMessage发送消息的时候,会判断Handler当前的mCallback是否为空,如果不为空就交给Handler.Callback.handleMessage处理。 - 3、如果
mCallback.handleMessage
返回true,则无后续了。 - 4、如果
mCallback.handleMessage
返回false,则调用handler类重写的handleMessage方法。
所以post(Runnable) 与 sendMessage的区别就在于后续消息的处理方式,是交给msg.callback
还是 Handler.Callback
或者Handler.handleMessage
。
Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一样?为什么这么设计?
接着上面的代码说,这两个处理方法的区别在于Handler.Callback.handleMessage
方法是否返回true:
- 如果为
true
,则不再执行Handler.handleMessage - 如果为
false
,则两个方法都要执行。
那么什么时候有Callback
,什么时候没有呢?这涉及到两种Hanlder的 创建方式:
val handler1= object : Handler(){ override fun handleMessage(msg: Message) { super.handleMessage(msg) } } val handler2 = Handler(object : Handler.Callback { override fun handleMessage(msg: Message): Boolean { return true } })
常用的方法就是第1种,派生一个Handler的子类并重写handleMessage方法。而第2种就是系统给我们提供了一种不需要派生子类的使用方法,只需要传入一个Callback即可。
Handler、Looper、MessageQueue、线程是一一对应关系吗?
- 一个线程只会有一个
Looper
对象,所以线程和Looper是一一对应的。 MessageQueue
对象是在new Looper的时候创建的,所以Looper和MessageQueue是一一对应的。Handler
的作用只是将消息加到MessageQueue中,并后续取出消息后,根据消息的target字段分发给当初的那个handler,所以Handler对于Looper是可以多对一的,也就是多个Hanlder对象都可以用同一个线程、同一个Looper、同一个MessageQueue。
总结:Looper、MessageQueue、线程是一一对应关系,而他们与Handler是可以一对多的。
ActivityThread中做了哪些关于Handler的工作?(为什么主线程不需要单独创建Looper)
主要做了两件事:
- 1、在main方法中,创建了主线程的
Looper
和MessageQueue
,并且调用loop方法开启了主线程的消息循环。
public static void main(String[] args) { Looper.prepareMainLooper(); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
- 2、创建了一个Handler来进行四大组件的启动停止等事件处理
final H mH = new H(); class H extends Handler { public static final int BIND_APPLICATION = 110; public static final int EXIT_APPLICATION = 111; public static final int RECEIVER = 113; public static final int CREATE_SERVICE = 114; public static final int STOP_SERVICE = 116; public static final int BIND_SERVICE = 121;
IdleHandler是啥?有什么使用场景?
之前说过,当MessageQueue
没有消息的时候,就会阻塞在next方法中,其实在阻塞之前,MessageQueue
还会做一件事,就是检查是否存在IdleHandler
,如果有,就会去执行它的queueIdle
方法。
private IdleHandler[] mPendingIdleHandlers; Message next() { int pendingIdleHandlerCount = -1; for (;;) { synchronized (this) { //当消息执行完毕,就设置pendingIdleHandlerCount if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } //初始化mPendingIdleHandlers if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } //mIdleHandlers转为数组 mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // 遍历数组,处理每个IdleHandler 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); } //如果queueIdle方法返回false,则处理完就删除这个IdleHandler if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; } }
当没有消息处理的时候,就会去处理这个mIdleHandlers
集合里面的每个IdleHandler
对象,并调用其queueIdle
方法。最后根据queueIdle
返回值判断是否用完删除当前的IdleHandler
。
然后看看IdleHandler
是怎么加进去的:
Looper.myQueue().addIdleHandler(new IdleHandler() { @Override public boolean queueIdle() { //做事情 return false; } }); public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } synchronized (this) { mIdleHandlers.add(handler); } }
ok,综上所述,IdleHandler
就是当消息队列里面没有当前要处理的消息了,需要堵塞之前,可以做一些空闲任务的处理。
常见的使用场景有:启动优化
。
我们一般会把一些事件(比如界面view的绘制、赋值)放到onCreate
方法或者onResume
方法中。但是这两个方法其实都是在界面绘制之前调用的,也就是说一定程度上这两个方法的耗时会影响到启动时间。
所以我们可以把一些操作放到IdleHandler
中,也就是界面绘制完成之后才去调用,这样就能减少启动时间了。
但是,这里需要注意下可能会有坑。
如果使用不当,IdleHandler
会一直不执行,比如在View的onDraw方法
里面无限制的直接或者间接调用View的invalidate方法
。
其原因就在于onDraw方法中执行invalidate
,会添加一个同步屏障消息,在等到异步消息之前,会阻塞在next方法,而等到FrameDisplayEventReceiver
异步任务之后又会执行onDraw方法,从而无限循环。
具体可以看看这篇文章:https://mp.weixin.qq.com/s/dh_71i8J5ShpgxgWN5SPEw
HandlerThread是啥?有什么使用场景?
直接看源码:
public class HandlerThread extends Thread { @Override public void run() { Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); }
哦,原来如此。HandlerThread
就是一个封装了Looper的Thread类。
就是为了让我们在子线程里面更方便的使用Handler。
这里的加锁就是为了保证线程安全,获取当前线程的Looper对象,获取成功之后再通过notifyAll
方法唤醒其他线程,那哪里调用了wait
方法呢?
public Looper getLooper() { if (!isAlive()) { return null; } // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; }
就是getLooper
方法,所以wait的意思就是等待Looper创建好,那边创建好之后再通知这边正确返回Looper。
IntentService是啥?有什么使用场景?
老规矩,直接看源码:
public abstract class IntentService extends Service { private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } } @Override public void onCreate() { super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } @Override public void onStart(@Nullable Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); }
理一下这个源码:
- 首先,这是一个
Service
- 并且内部维护了一个
HandlerThread
,也就是有完整的Looper在运行。 - 还维护了一个子线程的
ServiceHandler
。 - 启动Service后,会通过Handler执行
onHandleIntent
方法。 - 完成任务后,会自动执行
stopSelf
停止当前Service。
所以,这就是一个可以在子线程进行耗时任务,并且在任务执行后自动停止的Service。
BlockCanary使用过吗?说说原理
BlockCanary
是一个用来检测应用卡顿耗时的三方库。
上文说过,View的绘制也是通过Handler来执行的,所以如果能知道每次Handler处理消息的时间,就能知道每次绘制的耗时了?那Handler消息的处理时间怎么获取呢?
再去loop方法中找找细节:
public static void loop() { for (;;) { // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } } }
可以发现,loop方法内有一个Printer
类,在dispatchMessage
处理消息的前后分别打印了两次日志。
那我们把这个日志类Printer
替换成我们自己的Printer
,然后统计两次打印日志的时间不就相当于处理消息的时间了?
Looper.getMainLooper().setMessageLogging(mainLooperPrinter); public void setMessageLogging(@Nullable Printer printer) { mLogging = printer; }
这就是BlockCanary的原理。
具体介绍可以看看作者的说明:http://blog.zhaiyifan.cn/2016/01/16/BlockCanaryTransparentPerformanceMonitor/
说说Hanlder内存泄露问题。
这也是常常被问的一个问题,Handler
内存泄露的原因是什么?
"内部类持有了外部类的引用,也就是Hanlder持有了Activity的引用,从而导致无法被回收呗。"
其实这样回答是错误的,或者说没回答到点子上。
我们必须找到那个最终的引用者,不会被回收的引用者,其实就是主线程,这条完整引用链应该是这样:
主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity
具体分析可以看看我之前写的这篇文章:https://juejin.cn/post/6909362503898595342
利用Handler机制设计一个不崩溃的App?
主线程崩溃,其实都是发生在消息的处理内,包括生命周期、界面绘制。
所以如果我们能控制这个过程,并且在发生崩溃后重新开启消息循环,那么主线程就能继续运行。
Handler(Looper.getMainLooper()).post { while (true) { //主线程异常拦截 try { Looper.loop() } catch (e: Throwable) { } } }
还有一些特殊情况处理,比如onCreate内发生崩溃,具体可以看看文章
《能否让APP永不崩溃》https://juejin.cn/post/6904283635856179214
总结
大家应该可以发现,有一个问题常被问,但是全篇都没有提,那就是:
Hanlder机制的运行原理。
之所以不提这个问题,是因为要回答好这个问题需要大量知识储备,希望屏幕前的你在读完这篇之后,再结合自己的知识库,形成自己的“完美答案”
。
Hanlder,I Got it!
参考
《Android开发艺术探索》
https://www.zhihu.com/question/34652589
https://segmentfault.com/a/1190000003063859