面试官还问Handler?那我要给你讲个故事

总结

一家店可以有多个点餐员,但是厨师长只能有一个。打单机也只能有一个。

映射到以上场景中

  • 一家店就好比一个Thread

  • 而一个Thread中可以有多个Handler(点餐员)

  • 但是一家店只能有一个Looper(厨师长),一个MessageQueue(打单机),和多个Message(订单)。


面试官,我差不多吹完了,你要还不信?

根据以上的例子我们类比看下源码,充分研究下整个机制的流程,和实现原理。

Looper的工作流程

ActivityThread.main();//初始化入口

  1. 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();

  1. Looper.loop();

2.1.myLooper().mQueue.next(); //循环获取MessageQueue中的消息

nativePollOnce(); //阻塞队列

native -> pollInner() //底层阻塞实现

native -> epoll_wait();

2.2.Handler.dispatchMessage(msg);//消息分发

myLooper().mQueue.next()实现原理

    1. 通过myLooper().mQueue.next() 循环获取MessageQueue中的消息,如遇到同步屏障 则优先处理异步消息.
    1. 同步屏障即为用Message.postSyncBarrier()发送的消息,该消息的target没有绑定Handler。在Hnandler中异步消息优先级高于同步消息。
    1. 可通过创建new Handler(true)发送异步消息。ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保证UI绘制优先执行。

Handler.dispatchMessage(msg)实现原理

    1. 优先回调msg.callback。
    1. 其次回调handler构造函数中的callback。
    1. 最后回调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()方法实现原理

    1. 如果消息队列被放弃,则抛出异常。
    1. 如果当前插入消息是即时消息,则将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,并通过needWake唤醒Looper线程。
    1. 如果消息为异步消息则通过Message.when长短插入到队列对应位置,不唤醒Looper线程。

接下来该面试官问了

经常有人问为什么主线程的Looper阻塞不会导致ANR?

    1. 首先我们得知道ANR是主线程5秒内没有响应。
    1. 什么叫5秒没有响应呢?Android系统中所有的操作均通过Handler添加事件到事件队列,Looper循环去队列去取事件进行执行。如果主线程事件反馈超过了5秒则提示ANR。
    1. 如果没有事件进来,基于Linux pipe/epoll机制会阻塞loop方法中的queue.next()中的nativePollOnce()不会报ANR。
    1. 对于以上的例子来说,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进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

跨平台开发:Flutter.png

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

ve+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。**

[外链图片转存中…(img-GoPVQ2w9-1714723368198)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值