美文网首页
Handler机制常见问题

Handler机制常见问题

作者: LanceHsiung | 来源:发表于2020-06-01 15:56 被阅读0次

概念:

MessageQueue是一个优先级队列,messge中的when字段有其执行的时间,新加入的message会与已有的消息比较时间,较早的在前面。
Message是消息实体,通常需要用到what和obj字段传递消息,内部还有when字段用于存储消息执行的时间,target用来存储发送Message的Handler对象。
Looper是轮询器。
Handler是消息发送器。

流程:

Handler通过sendMessage()将Message发送到MessageQueue中,这是存消息的过程;
Looper通过调用MessageQueue的next()方法取出Message对象,由于这是一个时间优先级的队列,所以会取到队头的Message,然后拿当前时间去跟它的执行时间,也就是when参数去对比,如果时间还没到,就先不会执行;

1、一个线程有几个Handler?

多个,通常我们开发过程中就会new出不止一个Handler。

2、一个线程有几个Looper?如何保证?

1个。
Looper的构造是私有的,只有通过其prepare()方法构建出来,当调用了Looper的prepare()方法后,会调用ThreadLocal中的get()方法检查ThreadLocalMap中是否已经set过Looper,如果有,则会抛出异常,提示每个线程只能有一个Looper,如果没有,则会往ThreadLocalMap中set一个new出来的Looper对象。这样可以保证ThreadLocalMap和Looper一一对应,即一个ThreadLocalMap只会对应一个Looper。而这里的ThreadLocalMap是在Thread中的一个全局变量,也只会有一个,所以就可以保证一个Thread中只有一个Looper。

3、Handler内存泄漏的原因?为什么其他的内部类没有说过有这个问题?

内部类持有外部的引用。
Handler原理:由于Handler可以发送延迟消息,所以为了保证消息执行完毕后,由同一个Handler接收到,所以发送出去的Message中会持有Handler的引用,这个引用存在Message的target字段中,是Handler所有的sendMessage()方法最后都会调用enqueueMessage(),而在enqueueMessage()中会给Message的target字段赋值this。
因此Message持有Handler的引用,Handler又持有Activity的引用,所以在Message处理完之前,如果Activity被销毁了,就会造成内存泄漏。
解决?可以使用static修饰Handler对象。

4、为何主线程可以new Handler?如果想要在子线程中new Handler要做些什么准备?

因为在ActivityThread中的main()已经对Looper进行了prepar()操作,所以可以直接在主线程new Handler。
如果想在子线程中new Handler,则需要先手动调用Looper的prepare()方法初始化Looper,再调用Looper的loop()方法使Looper运转。

5、子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?

如果不处理的话,会阻塞线程,处理方案是调用Looper的quitSafely();
这个方法会调用MessageQueue的quit()方法,清空所有的Message,并调用nativeWake()方法唤醒之前被阻塞的nativePollOnce(),使得方法next()方法中的for循环继续执行,接下来发现Message为null后就会结束循环,Looper结束。如此便可以释放内存和线程。

6、既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同线程),那它内部是如何确保线程安全的?Handler的delay消息(延迟消息)时间准确吗?

添加消息的方法enqueueMessage()中有synchronize修饰,取消息的方法next()中也有synchronize修饰。
由于上述的加锁操作,所以时间不能保证完全准确。

7、我们使用Message时应该如何创建它?

使用Message的obtain()方法创建,直接new出来容易造成内存抖动。
内存抖动是由于频繁new对象,gc频繁回收导致,而且由于可能被别的地方持有导致无法及时回收所以会导致内存占用越来越高。
使用obtain()对内存复用,可以避免内存抖动的发生。其内部维护了一个Message池,其是一个链表结构,当调用obtain()的时候会复用表头的Message,然后会指向下一个。如果表头没有可复用的message则会创建一个新的对象,这个对象池的最大长度是50。

8、使用Handler的postDelay后消息队列会有什么变化?

如果此时消息队列为空,不会执行,会计算消息需要等待的时间,等待时间到了继续执行。

9、Looper死循环为什么不会导致应用卡死?

卡死就是ANR,产生的原因有2个:
1、在5s内没有响应输入的事件(例如按键,触摸等),2、BroadcastReceiver在10s内没有执行完毕。
事实上我们所有的Activity和Service都是运行在loop()函数中,以消息的方式存在,所以在没有消息产生的时候,looper会被block(阻塞),主线程会进入休眠,一旦有输入事件或者Looper添加消息的操作后主线程就会被唤醒,从而对事件进行响应,所以不会导致ANR
简单来说looper的阻塞表明没有事件输入,而ANR是由于有事件没响应导致,所以looper的死循环并不会导致应用卡死。

其他问题:

1、两个时间相等的消息怎么处理?

由于Handler发送消息时,最终会调用sendMessageDelayed(),这个方法会调用sendMessageAtTime(),在这个方法里会用系统时间加上传入的delayed值,而系统时间是每时每刻都在变化的,所以几乎不可能会有两个时间相等的消息。

2、ThreadLocal是什么?

ThreadLocal里存的是<key,value>的键值对,有点像SparseArray,但是与之不同的是SparseArray内部维护了2个数组,而ThreadLocal内部只有一个数组,它存数据都是成对的往里存,key存到index中,value就存在了index+1的位置。其中key存的是ThreadLocal,value存的是Looper。

3、ThreadLocalMap是什么?

它是ThreadLocal中的一个内部类,每个Thread中都会有一个全局的ThreadLocalMap对象。

4、Message使用了享元设计模式。

5、App的启动流程?

在Launcher界面点击应用图标 -》 启动Application -》zygote为应用程序分配一个虚拟机 -》 启动ActivityThread -》在ActivityThread中的main()函数对主线程的looper初始化并运行。
*ActivityThread中存在一个main函数,是应用的入口,所以首先启动的是ActivityThread。在

相关文章

网友评论

      本文标题:Handler机制常见问题

      本文链接:https://www.haomeiwen.com/subject/fglszhtx.html