目录

一 主线程为什么没有被loop阻塞

二 Message对象创建的方式有哪些 & 区别

三 一个线程可以有几个Handler,几个Looper,几个MessageQueue对象

四 Handler导致的内存泄露原因及其解决方案

五 子线程中能不能直接new一个Handler,为什么主线程可以

六 handler postDealy后消息队列有什么变化,假设先 postDelay10s, 再postDelay 1s, 怎么处理这2条消息

七 为什么Android系统不建议子线程访问UI

八 Looper.quit/quitSafely的区别

九 Looper 如何与 Thread 关联的

十 Handler的消息优先级,有什么应用场景?

十一 什么是Handler的同步屏障机制?


一 主线程为什么没有被loop阻塞

因为应用中不管是Activity,还是Service,所有的操作都是在各自的生命周期中执行的,所以它所有的操作都逃不出生命周期。所以,所有的操作都执行在ActivityThread.java中的loop()里面,所以,应用所有的操作都是在这个loop()中来管理的,也正是因为这个原因,主线程的loop()是不能够退出去的。只有一种情况,我们在一个应用的一个界面下不动,这个应用没有任何事件发生,也没有任何别的事件要处理,这个时候,我们的Looper就处于一个block状态;当点击一下这个屏幕,他就会触发唤醒这个Looper。

所以应用卡死压根与这个Looper没有关系,应用在没有消息需要处理的时候,它是在睡眠,释放线程;卡死是ANR,而Looper是睡眠。卡死是在主线程中执行一个耗时的操作,loop()会一直在处理一个消息,而for循环中有很多消息需要被处理,而这一个消息就要处理很久,这一个消息的处理时间,会转变成其他的点击事件没有响应。因为主线程在接受到其他消息的时候没有时间去响应,它的时间都在处理那一个耗时的操作,造成点击事件没有办法响应,点击事件没有办法响应就容易出现ANR。

二 Message对象创建的方式有哪些 & 区别

1.Message msg = new Message();每次需要Message对象的时候都创建一个新的对象,每次都要去堆内存开辟对象存储空间

2.Message msg = Message.obtain();obtainMessage能避免重复Message创建对象。它先判断消息池是不是为空,如果非空的话就从消息池表头的Message取走,再把表头指向 next。如果消息池为空的话说明还没有Message被放进去,那么就new出来一个Message对象。消息池使用 Message 链表结构实现,消息池默认最大值 50。消息在loop中被handler分发消费之后会执行回收的操作,将该消息内部数据清空并添加到消息链表的表头。

3.Message msg = handler.obtainMessage(); 其内部也是调用的obtain()方法

这里使用了享元设计模式

三 一个线程可以有几个Handler,几个Looper,几个MessageQueue对象

一个线程可以有多个Handler,只有一个Looper对象,只有一个MessageQueue对象。Looper.prepare()函数中知道,在Looper的prepare方法中创建了Looper对象,并放入到ThreadLocal中,并通过ThreadLocal来获取looper的对象, ThreadLocal的内部维护了一个ThreadLocalMap类,ThreadLocalMap是以当前thread做为key的,因此可以得知,一个线程最多只能有一个Looper对象, 在Looper的构造方法中创建了MessageQueue对象,并赋值给mQueue字段。因为Looper对象只有一个,那么Messagequeue对象肯定只有一个

四 Handler导致的内存泄露原因及其解决方案

原因:

1.Java中非静态内部类和匿名内部类都会隐式持有当前类的外部引用

2.我们在Activity中使用非静态内部类初始化了一个Handler,此Handler就会持有当前Activity的引用

3.我们想要一个对象被回收,那么前提它不被任何其它对象持有引用,所以当我们Activity页面关闭之后,存在引用关系:"未被处理 / 正处理的消息 -> Handler实例 -> 外部类",如果在Handler消息队列 还有未处理的消息 / 正在处理消息时 导致Activity不会被回收,从而造成内存泄漏

解决方案:

1.将Handler的子类设置成 静态内部类,使用WeakReference弱引用持有Activity实例

2.当外部类结束生命周期时,清空Handler内消息队列

五 子线程中能不能直接new一个Handler,为什么主线程可以

不能

因为Handler 的构造方法中,会通过Looper.myLooper()获取looper对象,如果为空,则抛出异常,主线程则因为已在入口处ActivityThread的main方法中通过 Looper.prepareMainLooper()获取到这个对象,并通过 Looper.loop()开启循环,在子线程中若要使用handler,可先通过Loop.prepare获取到looper对象,并使用Looper.loop()开启循环

六 handler postDealy后消息队列有什么变化,假设先 postDelay10s, 再postDelay 1s, 怎么处理这2条消息

postDelayed传入的时间,会和当前的时间SystemClock.uptimeMillis()做加和,而不是单纯的只是用延时时间。延时消息会和当前消息队列里的消息头的执行时间做对比,如果比头的时间靠前,则会做为新的消息头,不然则会从消息头开始向后遍历,找到合适的位置插入延时消息

postDelay()一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;

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

七 为什么Android系统不建议子线程访问UI

在android中子线程可以有好多个,但是如果每个线程都可以对ui进行访问,我们的界面可能就会变得混乱不堪,这样多个线程操作同一资源就会造成线程安全问题,当然,需要解决线程安全问题的时候,我们第一想到的可能就是加锁,但是加锁会降低运行效率,还会让逻辑变的更复杂,所以android出于性能的考虑,并没有使用加锁来进行ui操作的控制,使用单线程模型来处理UI

八 Looper.quit/quitSafely的区别

当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空, 无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息

当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息

九 Looper 如何与 Thread 关联的

Looper 与 Thread 之间是通过 ThreadLocal 关联的,这个可以看 Looper#prepare() 方法 Looper 中有一个 ThreadLocal 类型的 sThreadLocal静态字段,Looper通过它的 get 和 set 方法来赋值和取值。 由于 ThreadLocal是与线程绑定的,所以我们只要把 Looper 与 ThreadLocal 绑定了,那 Looper 和 Thread 也就关联上了

十 Handler的消息优先级,有什么应用场景?

在Looper执行消息循环loop()时会执行下面这行代码,msg.targe就是这个Handler对象。msg.target.dispatchMessage(msg);

1.如果Message这个对象有CallBack回调的话,这个CallBack实际上是个Runnable,就只执行这个回调,然后就结束了,然后调用handleCallback,而handleCallback方法中调用的是Runnable的run方法。

2.如果Message对象没有CallBack回调,进入else分支判断Handler的CallBack是否为空,不为空执行CallBack的handleMessage方法,然后return。

3.最后才调用到Handler的handleMessage()函数,也就是我们经常去重写的函数,在该方法中做消息的处理。

使用场景

可以看到Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。我们可以利用CallBack这个拦截来拦截Handler的消息。

场景:Hook ActivityThread.mH , 在 ActivityThread 中有个成员变量 mH ,它是个 Handler,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。

Handler.post(Runnable r)方法的执行逻辑

我们需要分析平时常用的Handler.post(Runnable r)方法是如何执行的,是否新创建了一个线程了呢,实际上并没有,这个Runnable对象只是被调用了它的run方法,根本并没有启动一个线程,

而是该Runnable对象被包装成一个Message对象,也就是这个Runnable对象就是该Message的CallBack对象了,有优先执行的权利了。
 

十一 什么是Handler的同步屏障机制?

先理解什么是同步屏障:发送消息的时候,msg.targer == null

在主线程去消息队列中不断获取消息的时候,调用next方法,取到队头的msg的targer为null,认为此消息为同步屏障,此时就会遍历整个队列,找到一个msg,并且这个msg被标记为异步消息(msg != null && msg.isAsynchronous())时,才会将这个异步消息取出执行,否则,就让next()方法进入阻塞状态,此时主线程就处于空闲状态(什么事也没干).

所以,如果队头是一个同步屏障的消息的话,后面所有的同步消息都将被拦截,而入队的异步消息会优先执行,执行完毕继续阻塞,直到移除这个同步屏障消息。