handler解析(5)常见面试题

目录

 

1.请大致讲下handler的工作原理 

2.handler.postDelay原理

3.一个线程有几个Looper?几个Handler?

4. Handler内存泄漏原因?以及解决方案

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

6.消息退出是调用什么

7.loope死循环为什么不会造成应用卡顿

8.post跟send的区别


1.请大致讲下handler的工作原理 

handler的工作流程基本包括handler、message、message queue、looper这4个部分,

其中handler是消息的处理者,具备发生、处理、获取等功能,

message消息实体,可以带数据obj,行为标志符what和一串动作Runable,其中Runable转为了message的callback变量。

message queue消息队列,以队列的形式对外提供插入和删除操作,内部实际是一个单链表的数据结构,其中的消息的插入是根据传入的when进行插入的,when最短的插入到头部,时间最长的插入到尾部,其余消息根据when的大小对消息进行排序插入,遵循先进先出的原则。

looper,循环,它负责从消息队列中取出消息,然后把消息交给handler来处理

处理流程就是handler通过调用sendMesssage,实际是调用sendMessageAtTime发送when为0+当前手机启动到现在的时间为0到消息队列中的enqueue方法进行处理,消息队列检测到有消息后,判断当前when是否为0,或者when是否要小于于链表头个消息的when,或者当前没消息,这几种情况下就把message插入到链表头,然后唤醒looper,调用next方法获取链表头消息,分发个msg.targer进行dispatchMessage处理,然后判断msg中是否有runable,如果没有的话,就执行handlerMessage了

2.handler.postDelay原理

这块在上篇文章handler解析(2) -Handler源码解析_沙滩捡贝壳的小孩的博客-CSDN博客已经了解过了handler.postDelay本质上都是调用handler.sendsendMessageDelayed,然后调用sendMessageAtTime,最后调用enqueueMessage,然后我们知道MessageQueue会根据post delay的时间排序放入到链表中,链表头的时间小,尾部时间最大。在next取消息的时候,拿当前时间跟链表头的时间做对比,如果头部msg是有延迟的,而且延迟时间还没到的,则进行阻塞,然后等于是开了个定时器,等到时间到了的话,则进行唤醒操作,然后进行next操作取出消息,进行分发

举例:

  1. postDelay()一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;
  2. 紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;
  3. MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;
  4. Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;
  5. 直到阻塞时间到或者下一次有Message进队;

3.一个线程有几个Looper?几个Handler

这块在上篇文章handler解析(2) -Handler源码解析_沙滩捡贝壳的小孩的博客-CSDN博客也说过了,一个线程只能有一个looper,这块是由ThreadLocal中ThreadLocalMap控制的,在loop.prepare中ThreadLocal进行set一个looper对象操作,而且ThreadLocal是static final类型的,但是handler就可以有无数个了,因为只要它关联到当前线程的looper就行了,这块在handler的构造方法中进行了声明

4. Handler内存泄漏原因?以及解决方案

泄漏原因:
Handler允许我们发送延迟消息,如果在延迟期间用户关闭了Activity,那么改Activity会泄漏。这个泄漏是因为Message会持有Handler,而又因为Java的特性,内部类会默认持有外部类的引用,使得Activity会被Handler持有,这样最终就会导致Activity泄漏。

解决方法:

静态内部类 + 弱引用

   private static class MyHandler extends Handler {
        //弱引用,在垃圾回收时,activity可被回收
        private WeakReference<MainActivity> mWeakReference;

        public MyHandler(MainActivity activity) {
            this.mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    }

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

每一个handler都要对应一个looper,主线程因为在AcitivityThread中的Looper.prepareMainLooper中已经创建好looper了,所以在new Handler中会直接通过ThreadLocal.get方法获取到looper。但是子线程就没有创建好looper了,所以在new handler的时候会因为ThreadLocal.get获取不到looper报错,所以需要在new Handler之前调用Looper.prepare();在之后调用Looper.loop(); Looper.myLooper().quitSafely();

6.消息退出是调用什么

消息退出是调用Looper的quit或者quitSafely,其中quitSafely()与quit()方法的区别是,quit()会直接回收消息队列中的消息,而quitSafely()会根据当前的时间进行判断,如果消息的meesage.when比当前时间大,那么就会被回收,反之仍然被取出执行。然后这个时候在发送消息时,消息是会被回收的

7.loope死循环为什么不会造成应用卡顿

线程对于java代码来说就是一段可执行代码,当可执行代码执行完了之后,程序就终止了,所以对于主线程来说,肯定不想就这么终止,所以死循环是必要的,可以保证一直执行下去,其次应用卡顿是在生命周期中执行耗时操作导致掉帧,甚至发生ANR,looper.loop本身是不会导致应用卡死的

再者说了,主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

8.post跟send的区别

post之前文章也分析过了,本质上还是调用sendMessageAtTime发送一条消息来着,只是说这条消息的赋值对象是msg.callBack,在next方法中优先执行.callBack中的方法,所以handler.post也可以用来直接在子线程中更新UI

send跟post本质上没有区别,post是send使用的简化版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值