总结
一家店可以有多个点餐员,但是厨师长只能有一个。打单机也只能有一个。
映射到以上场景中
-
一家店就好比一个Thread
-
而一个Thread中可以有多个Handler(点餐员)
-
但是一家店只能有一个Looper(厨师长),一个MessageQueue(打单机),和多个Message(订单)。
面试官,我差不多吹完了,你要还不信?
根据以上的例子我们类比看下源码,充分研究下整个机制的流程,和实现原理。
Looper的工作流程
ActivityThread.main();//初始化入口
- Looper.prepareMainLooper(); //初始化
Looper.prepare(false); //设置不可关闭
Looper.sThreadLocal.set(new Looper(quitAllowed)); //跟线程绑定
1.1.Looper.mQueue = new MessageQueue(quitAllowed); //Looper和MessageQueue绑定
1.2.Looper.mThread = Thread.currentThread();
- Looper.loop();
2.1.myLooper().mQueue.next(); //循环获取MessageQueue中的消息
nativePollOnce(); //阻塞队列
native -> pollInner() //底层阻塞实现
native -> epoll_wait();
2.2.Handler.dispatchMessage(msg);//消息分发
myLooper().mQueue.next()实现原理
-
- 通过myLooper().mQueue.next() 循环获取MessageQueue中的消息,如遇到同步屏障 则优先处理异步消息.
-
- 同步屏障即为用Message.postSyncBarrier()发送的消息,该消息的target没有绑定Handler。在Hnandler中异步消息优先级高于同步消息。
-
- 可通过创建new Handler(true)发送异步消息。ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保证UI绘制优先执行。
Handler.dispatchMessage(msg)实现原理
-
- 优先回调msg.callback。
-
- 其次回调handler构造函数中的callback。
-
- 最后回调handler handleMessage()。
Hander发送消息的流程
1.Handler handler = new Handler();//初始化Handler
1.Handler.mLooper = Looper.myLooper();//获取当前线程Looper。
2.Handler.mQueue = mLooper.mQueue;//获取Looper绑定的MessageQueue对象。
2.handler.post(Runnable);//发送消息
sendMessageDelayed(Message msg, long delayMillis);
sendMessageAtTime(Message msg, long uptimeMillis);
Handler.enqueueMessage();//Message.target 赋值为this。
Handler.mQueue.enqueueMessage();//添加消息到MessageQueue。
MessageQueue.enqueueMessage()方法实现原理
-
- 如果消息队列被放弃,则抛出异常。
-
- 如果当前插入消息是即时消息,则将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,并通过needWake唤醒Looper线程。
-
- 如果消息为异步消息则通过Message.when长短插入到队列对应位置,不唤醒Looper线程。
接下来该面试官问了
经常有人问为什么主线程的Looper阻塞不会导致ANR?
-
- 首先我们得知道ANR是主线程5秒内没有响应。
-
- 什么叫5秒没有响应呢?Android系统中所有的操作均通过Handler添加事件到事件队列,Looper循环去队列去取事件进行执行。如果主线程事件反馈超过了5秒则提示ANR。
-
- 如果没有事件进来,基于Linux pipe/epoll机制会阻塞loop方法中的queue.next()中的nativePollOnce()不会报ANR。
-
- 对于以上的例子来说,ANR可以理解为用户进行即时点餐后没按时上菜(当然未按时上菜的原因很多,可能做的慢(耗时操作IO等),也可能厨具被占用(死锁),还有可能厨师不够多(CPU性能差)等等。。。),顾客发起了投诉,或差评。但如果约定时间还没到,或者当前没人点餐,是不会有差评或投诉产生的,因此也不会产生ANR。
以上的所有内容均围绕原理,源码,接下来我们举几个特殊场景的例子
- 1.为什么子线程不能直接new Handler()?
new Thread(new Runnable() {
@Override
public void run() {
Handler handler = new Handler();
}
}).start();
以上代码会报以下下错误
java.lang.RuntimeException: Can’t create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
at android.os.Handler.(Handler.java:207)
at android.os.Handler.(Handler.java:119)
at com.example.test.MainActivity$2.run(MainActivity.java:21)
at java.lang.Thread.run(Thread.java:919)
- 通过报错提示 “not called Looper.prepare()” 可以看出提示没有调用Looper.prepare(),至于为什么我们还得看下源码
public Handler(Callback callback, boolean async) {
…省略若干代码
//通过 Looper.myLooper()获取了mLooper 对象,如果mLooper ==null则抛异常
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;
}
public static @Nullable Looper myLooper() {
//而myLooper()是通过sThreadLocal.get()获取的,那sThreadLocal又是个什么鬼?
return sThreadLocal.get();
}
//可以看到sThreadLocal 是一个ThreadLocal对象,那ThreadLocal值从哪赋值的?
static final ThreadLocal sThreadLocal = new ThreadLocal();
//sThreadLocal 的值就是在这个方法里赋值的
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));
}
- 通过以上的源码注释,完全明白了报错的日志的意思,报错日志提示我们没有调用Looper.prepare()方法,而Looper.prepare()方法就是sThreadLocal赋值的位置。
那子线程怎么创建Handler呢?只需在new Handler()之前调用下Looper.prepare()即可。
-
2. 为什么主线程可以直接new Handler?
-
子线程直接new Handler会报错,主线程为什么就不会报错呢?主线程我也没有调用Looper.prepare()啊?那么我们还得看下源码了。
//我们看下ActivityMain的入口main方法,调用了 Looper.prepareMainLooper();
public static void main(String[] args) {
…
Looper.prepareMainLooper();
…
}
//看到这一下就明白了,原来主线程在启动的时候默认就调用了prepareMainLooper(),而在这个方法中调用了prepare()。
//提前将sThreadLocal 进行赋值了。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException(“The main Looper has already been prepared.”);
}
总结
写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于Flutter的学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
ve+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。**
[外链图片转存中…(img-GoPVQ2w9-1714723368198)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!