问题1 子线程里可以创建Handler吗
子线程里没prepare looper时创建Handler会抛出RuntimeException,prepare looper后可以创建
查看Handler构造函数,因为获得的looper为空,所以才抛出上面的异常
怎样获得Looper呢?通过prepare创建,looper保存在ThreadLocal中,quiteAllowed参数代表Looper是否可以退出,子线程调用prepare()时是允许退出的,下面的prepareMainLooper是主线程创建looper时调用,不允许looer退出
然后消息队列是在looper中创建的
总体结构:一个线程对应一个Looper,一个Looper对应一个MessageQueue,一个Looper可以对应多个Handler,Handler发消息到messageQueue,messageQueue中通过target字段分发消息到别的Handler
MessageQueue在native初始化:从当前线程的缓存中拿到Looper,如果looper是null的话,先new一个looper,再将looper放到线程缓存中
native层Looper的构造函数:创建eventfd,然后监听eventfd的读事件
什么时候往eventfd中写东西呢?其他线程往当前线程的消息队列中插入新的消息,write函数就会往eventfd里写东西,当前线程如何监听eventfd的读事件?在pollOnce函数中,不停的循环调用pollInner来监听
用awoken来消化读取事件
问题2 主线程的Looper和子线程的Looper的区别
1 应用进程启动后,主线程的Looper就已经准备好了,子线程的looper得prepare函数去创建
2 子线程的looper可以退出的,主线程的looper不能退出,
问题3 Looper和MessageQueue有什么关系
1 java层Looper里包含一个MessageQueue,是一对一的关系
2 native层的messageQueue中包含一个Looper,也是一对一的关系
问题4 MessageQueue是怎么创建的
java的MessageQueue对象创建的时候调用native的MessageQueue方法,然后会创建一个looper,looper里会创建一个eventfd,并且添加可读事件
问题5 idleHandler
线程空闲(messageQueue中消息为空或消息延时处理)时调用
在queueIdle方法中可以处理一些事情,并返回一boolean值,返回true代表当线程idle(空闲)时,可以一直回调queueIdle这个方法,return false的话则只能收到一次这个queueIdle方法回调
messageQueue里的addIdleHandler方法,就是往idleHandler的列表中加入这个idleHandler,
当消息队列中没消息可处理时,则会将所有的idleHandlers转为数组,然后遍历这个数组,取出每个idleHandler,判断idleHandler的queueIdle方法为false,也就是idleHandler只要执行一次,就会将这个idleHandler移除,否则不移除,在下次循环中继续执行idleHandler中的任务
如上图,首先往主线程中通过addIdleHandler添加了一个idleHandler, 然后这个idleHandler实现方法是创建类实现idleHandler接口,重写queueIdle方法,在方法中添加任务。
IdleHandler的适用场景:
使用场景:
1 延时执行: 当界面启动时,需要处理很多逻辑,可能导致界面加载较慢,这时把一些不重要的逻辑延时执行,如上图所示,但上图并非很理想解决方案,此时就能适用IdleHandler
2:批量任务:批量任务的特点是任务密集,且只关注最终结果,比如打开app,收到一大堆推送,然后刷新界面,如果来一条就刷新一次界面不太好,此时可以开个工作线程,来一个推送则封装成一个消息丢到工作线程中处理,当把所有的消息都处理完的时候,工作线程空闲下来,再去绘制结果刷新界面,这种场景就适合用IdleHandler去做
问题6 主线程进入Loop循环,为什么没有ANR
考点:
1 ANR是什么
上图是导致ANR的场景,概括就是应用在规定的时间内没有处理完指定的任务
主线程中的loop无限循环不能退出,因为退出后应用就退出了,loop无限循环里不断从消息队列中取出消息然后分发消息,通过next()函数取出消息,当另一个线程往描述符写东西,nativePollOnce就会被唤醒,唤醒后再去检查消息队列有没有可处理的消息,有的话就把这个消息return
当消息队列为空的时候主线程进入阻塞,其他线程发过来消息时,主线程又被激活
总结:
1 进入for死循环,依然可以处理AMS发过来的任务,AMS发过来的任务,都会封装成消息丢到消息队列去处理,
2 没在规定的时间内完成一般指两点原因,主线程里有别的耗时任务,AMS发过来的任务就被delay了,或者AMS发过来的任务本身就很耗时,所以和loop的死循环无关
问题7 ThreadLocal的原理,以及在Looper是如何应用的
1 ThreadLocal的原理
Looper就是保存在了threadLocal中,通过myLooper方法获得当前线程的threadLocal中保存的Looper,在不同的线程中,通过myLooper方法获得的Looper也就不同
每个线程里都有一个Thread对象,Thread对象中有一张表,这个表其实是一个数组,存放了key和value,key就是指ThreadLocal的weakRefrence,value就是指ThreadLocal中存放的东西,一个应用中可以有多个ThreadLocal,每个ThreadLocal都有对应的Hash值,这个Hash值是根据hashCounter递增算出来的,然后用这个Hash值对数组的长度取余数,就能算出来ThreadLocal对应的key\value对在数组中所在的位置,如果发生hash冲突,则从当前的index开始往后遍历,看哪个是空的就存在哪里
ThreadLocal原理总结:
1 同一个ThreadLocal对象,在不同的线程get返回不同的value
2 ThreadLoacl中有张表,数组的形式保存ThreadLoacl到value的映射关系
3 这张表其实是数组形式,存放的key是ThreadLocal的weakReference,value是ThreadLocal中存放的东西,每个ThreadLocal都有对应的Hash值,这个hash值用hashCounter递增的方式算出,再用hash值对数组的长度取余数算出key/value对在数组中的位置
4 hash冲突时,继续往数组后面遍历,直到找到空的位置存放
问题8 Looper其他作用
epoll_wait等可以处理的事件,然后遍历每个事件的fd,如果等于其他线程发来的mWakeEventFd,会向消息队列写消息让主线程进行处理,如果不等于的话,在else分支里,。。。。未完待续
问题9 handler的消息延时怎么实现的
消息队列其实是一个单链表,新消息到来时,按照消息触发时间的顺序来插入单链表的,如果消息队列是空的,或者新来的消息比消息队列的第一个时间还早的话,就插到第一个消息前面来作为新的消息的头节点,不然的话,按照消息的时间先后顺序插入到链表的对应位置当中,所以延时机制其实是指延时处理消息,而不是延时发送消息,发送消息会马上把消息插入到消息队列当中,只不过处理的时候是按照时间的先后顺序处理的,最下面的nativeWake函数,native层有一个native层的MessageQueue对象,然后会调用到native层的messageQueue的wake函数,然后线程收到eventFd事件就会被唤醒
插入到消息队列后,如何保证一定按照预期的时间处理消息的呢,在loop()函数中,如果消息队列中没有要处理的消息next函数就会阻塞一直等待,直到有可用的消息,取到消息之后就可以调用dispatchMessage分发到消息对应的Handler,next函数有可用的消息才会返回,可用是指处理时间到了,如果当前的时间早于消息队列的第一个时间,epoll_wait就会在消息队列第一个时间减去当前时间得到的时间差后返回,不然当前时间大于消息队列的第一个时间,说明时间到了,就会把这个消息取出来返回,分发处理这个消息
总结:1 消息队列按照消息触发时间先后顺序排列
2 计算好消息的触发时间,当做epoll_wait的超时时间,超时时间到了线程就会被唤醒,去检查消息队列里有没有消息可以处理
3 延时精度不高,消息可能处理比较耗时,导致后面的消息延时处理了
网友评论