> 文章列表 > Android消息机制-Handler小结

Android消息机制-Handler小结

Android消息机制-Handler小结

Android消息机制-Handler小结

讲一下Handler原理?

Handler主要有这么几个角色:Handler、Message、MessageQueue、Looper;

  • 当Handler发送消息时,Message会按照时间的先后顺序被保存到MessageQueue里面;

  • 然后Looper不断的从消息队列中取出消息,交给Handler去处理

延迟消息实现原理

当Handler发送延迟消息时,会根据延迟的时间计算出需要处理消息的时间,然后根据时间先后顺序插入到消息队列中;当Looper从消息队列中获取消息时,如果当前没有要处理的消息,则会计算下一个延迟消息执行的时间,在消息队列中调用nativePollOnce方法,这个方法会阻塞当前线程,并在设置的时候后唤醒当前线程;当时间到了后线程被唤醒,则会继续从消息队列中取出消息进行处理;

nativePollOnce底层是通过Epoll机制实现,Epoll是linux层IO复用模型,通过注册监听FD的IO状态,当可读或者可写时会通过调用注册的回调方法去通知应用层

为什么主线程Looper不会ANR?

因为Android是事件驱动的,Looper就是需要不断的循环去处理消息,才能让主线程不会运行结束而导致APP应用进程结束;

而ANR的产生是因为主线程Looper在分发消息之前会发送一个延迟消息,这个延迟消息一旦执行就会抛出ANR异常,当消息在规定时间内完成时会移除这个延迟消息,超时则会执行并抛出ANR异常,所以只有在主线程处理消息时占用太多时间才会导致ANR,死循环是不会导致ANR异常的。

而且当消息队列为空时,会调用nativePollonce方法阻塞当前队列,直到有新的消息或者延迟消息时间到了才会唤醒当前线程

使用Handler的postDelay消息队列有什么变化

延迟消息会根据当前时间和延迟的时间计算出最终要处理消息的绝对时间,然后消息队列根据执行时间先后顺序将新加入的消息插入到队列的合适位置

什么是Epoll机制?

Epoll机制是Linux系统中用于IO多路并发的机制,它通过对FD文件进行注册监听,当FD文件的可读可写状态发生改变的时候,会唤醒阻塞的应用线程并调用回调函数达到通知的目的;它相比poll和socket等机制,使用红黑树来保存fd文件节点,使得查找节点时的效率高了很多,而且它通过注册回调函数的方式,使得用户层和内核层之间拷贝的次数减少了

如何保证多个handler线程安全?

Handler在发送消息,消息插入到消息队列时使用了Synchronized来保证线程安全

关于ThreadLocal,谈谈你的理解?

ThreadLocal变量的特点是每一个线程都会有一个独立的对象,分别保存在不同线程Thread类里面,它的生命周期是跟所在Thread一样长的,由于不同线程修改ThreadLoca变量时改的不是同一个对象,所以不会有线程安全问题。

ThreadLocal的实现原理:当ThreadLocal去设置一个值的时候,它会先从当前Thread里拿到ThreadLocalMap对象,然后以当前ThreadLocal自身作为key,设置的值作为value,保存到ThreadLocalMap中,在ThreadLocalMap里会将key和value封装成一个Entry对象,这个Entry对象继承自弱引用WeakReference,这样可以避免内存泄漏;而Entry对象又不是保存到数组中;

当ThreadLocal去取值的时候,也是通过当前线程的Thread类拿到ThreadLocalMap,然后以当前ThreadLocal对象作为key从ThreadLocalMap中获取到对应的值

如何创建Message对象?

可以通过Handler的obtainMessage方法或者Message的obtain方法,通过这两个方法获取对象,可以充分利用已经缓存了的对象,避免对象频繁创建;Message对象本身就是一个单向链表实现的队列,使用了享元模式,构建了一个缓冲池的设计,当消息处理完之后会被回收到缓冲池中

为什么不能用java的阻塞队列?

因为Android的消息不仅仅来自应用层,还有系统native层也会需要发一些消息

为什么不能在子线程更新UI?

假如可以在子线程更新UI,那么多线程环境下一定会需要处理线程安全问题,线程同步会导致UI响应比较慢,降低流畅度,所以在ViewRootImpl中更新UI操作时会先判断当前线程是否在主线程,如果不是的话则会抛出异常

子线程一定不能更新UI吗?

也不一定,因为检查是否在主线程更新UI是在ViewRootImpl类里面判断的,而ViewRootImpl对象是在onResume方法调用后,makeVisible方法里,将DecorView添加到WindowManager时才创建的,所以只要在创建ViewRootImpl对象之前子线程更新UI都可以,但是这样做没有太大意义,因为这个时候整个界面都还没显示出来

子线程中维护Looper在消息队列无消息的时候处理方案是怎么样的?

消息队列没有消息的时候,会调用nativePollOnce方法,这个方法会阻塞当前线程,当有新的消息来的时候,或者延迟消息时间到了的时候,底层的epoll机制会唤醒当前线程,然后Looper就会继续从消息队列中取出消息继续执行

一个线程有几个looper? 如何保证可以,又可以有几个Handler?

一个线程只有Looper,是通过使用ThreadLocal来保证每个线程只有一个Looper的,同时为了保证Looper不会被重复创建,在调用Looper.prepare方法时,会判断是否已经创建过Looper,如果已经创建过则会抛出异常;

可以有多个Handler,创建Handler的时候可以指定使用哪个线程的Looper,使用哪个线程Looper,最后消息就在哪个线程执行

handler内存泄漏的原因,其他内部类为什么没有这个问题?

Handler之所以会内存泄漏,是因为内部类默认会引用外部类,比如handler如果创建的时候是内部类,那么它会默认引用Activity,而Handler在发送消息的时候,Handler会被Message持有,Message又被MessageQueue持有,MessageQueue又被Looper持有,Looper又被Thread持有,所以间接地Activity也被Thread线程持有,而Thread的生命周期明显要比Activity长很多,当队列中有消息没有处理完,Activity又需要退出的时候,Activity因为被Thread间接持有了导致不能及时释放资源,从而造成内存泄漏;解决的办法就是把Handler声明为静态内部类,然后退出的时候把未处理完的消息移除,当需要引用Activity时,可以使用弱引用持有

为什么主线程可以new Handler 其他子线程可以吗 怎么做?

其他子线程默认是不可以直接创建Handler的,因为Handler创建需要有Looper,而子线程默认是没有创建Looper的,需要先手动调用Looper.prepare进行创建,然后才能创建Handler,主线程之所以可以,是因为在主线程刚运行时,在ActivityThread中有调用Looper.prepareMainLooper方法创建了主线程的Looper

Handler中的生产者-消费者设计模式你理解不?

Handler作为生产者不断的往消息队列中发送消息;Looper作为消费者不断的从消息队列中取出消息,最终分发给Handler去执行

既然存在多个Handler往MessageQueue中添加数据(发消息时各个Handler处于不同线程),内部如何保证安全

消息队列在添加消息时使用了Synchronized关键字进行同步来保证线程安全的,Synchronized的实现是使用了偏向锁、轻量级锁、重量级锁、其中重量级锁是通过Monitor锁实现

Handler所发送的Delayed消息时间准确吗?

不一定准,但是基本准确,主要影响因素有两个:

1、当调用nativePollonce线程被阻塞后,下次唤醒需要等待cpu调度才能及时处理延迟消息,所以可能会存在一定延时

2、ViewRootImpl在通过编舞者获取垂直同步信息准备绘制UI之前会通过消息队列发送一个同步屏障消息,在返回垂直同步信号这个时间段内,如果我们发送的延迟消息不是异步的,就不能被立刻执行,需要等待垂直信号到来,ViewRootImpl发送并处理了异步消息后,同步屏障才会消除;所以这个时候延迟消息也不一定会准时执行

Handler同步屏障实现原理

同步屏障是指通过往消息队列中插入一个Target==null的消息,当处理到这个target为null的消息的时候,会开启一个while循环,查找队列中是否有异步消息需要处理,直到找到一个异步消息才会跳出循环;当我们有个优先级比较高的消息要优先处理时,可以先插入同步屏障,然后发送异步消息就会优先执行,但是发送同步屏障的方法需要用到反射才能调用

多个Handler发送消息;Looper怎么知道要把消息给哪个Handler处理?

在Handler发送消息的时候,会把当前Handler保存到Message的target变量里,当处理消息的时候,就可以直接取出Handler并处理消息;

Handler处理消息时会先判断Message里是否有设置CallBack(Runnable),如果有设置则执行并终止;

如果没有则判断Handler是否有设置CallBack(Callback),如果有则执行,并根据返回值判断,如果返回true,则终止;

如果返回false,则继续回调Handler的handleMessage方法继续处理该消息

为什么Handler可以切换线程,如何切换的?

Handler之所以可以切换线程是因为Looper因为ThreadLocal,保证了每个线程最多有且只有一个Looper,当Handler跟哪个线程Looper关联起来后,Handler发送的所有消息后在Looper所在线程中执行,所以只需要创建多个Handler,并传入不同线程的Looper,就可以实现切换了。

为什么一个线程中Looper.loop只能调用一次?

因为Looper.loop方法里是个死循环,当调用了之后,在它之后的代码就没有机会执行了;当然我们也可以通过多次调用Looper.loop来捕获全局异常,并重启消息循环,避免app被杀死;具体的做法是,在主线程通过Handler发送一个消息,在消息处理时执行一个while循环,在循环里调用Looper.loop方法,并用trycatch包裹主,当发生异常时Looper会重新调用loop方法继续循环处理消息,从而避免app崩溃

子线程的Looper和主线程的Looper有什么区别?

主线程的looper不能退出,而子线程的Looper是可以退出的;

主线程的Looper在app启动时,ActivityThread中就已经创建好了;而子线程中Looper默认是没有创建的,需要主动调用Looper.prepare方法去创建

Handler 中的方法 sendMessage 和 post 的区别?

sendMessage传入的是Message对象,而Post方法传入的是Runnable对象;post方法传入的Runnable方法最终会保存到Message对象的CallBack变量里

Handler 是如何发送一个延时消息的?

Handler的postDelayed和sendMessageDelayed来实现的,也可以用postAtTime或者sendMessageAtTime;最终都会根据当前时间和延迟时间来计算出最终执行的时间,并按照时间先后顺序加入消息队列中

使用 Handler 先发送一个延迟 10s 的消息,再发送一个延迟 5s 的消息,然后让主线程 sleep 5s,最后消息执行顺序?

sleep 5秒后延迟5秒的消息会立刻执行,接着再过5秒,延迟10秒的消息会执行

如何让 Handler 发送一个消息并让它优先执行?

主要有两种方法:

1、将消息放到队列最前面;由于消息队列是根据时间先后顺序排列的,所以只需要发送一个消息并设置执行时间是0就可以了

2、使用同步屏障,先发送一个同步屏障消息,然后设置消息为异步的,再发送异步消息就可以优先执行了

View 的绘制过程中 Handler 发送的消息和普通 Handler 发送的消息有何异同?

View在绘制过程中,在ViewRootImpl类开始绘制之前会请求一次垂直同步信号,在请求垂直同步信息之前会发送一个同步屏障消息;待接收到垂直同步信号之后,就开始发送一个异步消息开始绘制;这里使用同步屏障可以保证优先处理异步消息,使得UI的绘制过程不至于被其他普通同步消息耽误时间;普通的Handler发送的消息都是同步消息,会被同步屏障过滤掉

如何判断当前线程是主线程?

调用Looper.myLooper获取当前线程的Looper对象,然后调用Looper.getMainLooper方法获取主线程的Looper,判断两者是否相等就可以判断是否在主线程