Handler
Handler作用
既然process内的thread share their memories. why we need handler.我们直接可以 subThread get data,赋值给Ui thread的变量呀。
正常来说是没问题的。但是像下面这种情况。
1 | public class MainActivity extends AppCompatActivity { |
如上:主线程定义了anInt,子线程对其修改。我们再在主线程打印,能得到修改后的anInt = 2 吗?
答案:不确定
原因:线程争夺cup使用权顺序不确定,可能,子线程先获得使用权,则两个Log都打印2。当然主线程先获得使用权时主线程的log打印数值为1。
解决方案
1、让子线程先执行。
比如主线程设置低优先级、主线程休息等等(不靠谱,提供一种想法吧)
2、安卓中的Handler机制
子线程发送消息,主线程接受消息,只有主线程收到消息再打印处理数值。
handler使用略。。。。。。
3、接口回调机制
原理图

各组件作用
- Handler:事件的发送及处理者,在构造方法中可以设置其 async,默认为 false。若 async 为 true 则该 Handler 发送的 Message 均为异步消息,有同步屏障的情况下会被优先处理。
- Looper:一个用于遍历 MessageQueue 的类,每个线程有一个独有的 Looper,它会在所处的线程开启一个死循环,不断从 MessageQueue 中拿出消息,并将其发送给 target 进行处理
- MessageQueue:在主线程,用于存储 Message,内部维护了 Message 的链表,每次拿取 Message 时,若该 Message 离真正执行还需要一段时间,会通过 nativePollOnce 进入阻塞状态,避免资源的浪费。若存在消息屏障,则会忽略同步消息优先拿取异步消息,从而实现异步消息的优先消费。
消息入队算法
https://www.jianshu.com/p/9efe3b48b730
https://juejin.cn/post/6844904113470177293
https://www.bilibili.com/video/BV1VE411Z7Ay?p=4
ThreadLocal存取算法
ThreadLocal是一个线程内部的数据存储类,它可以指定线程的存储数据,数据存储后只有在指定的线程种裁可以获取到存储的数据, 其他线程无法获取。
验证:
private ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();
mStringThreadLocal.set("我是主线程");
Log.d(TAG, "mStringThreadLocal 主线程 : " + mStringThreadLocal.get());
exec.submit(new Runnable() {
@Override
public void run() {
mStringThreadLocal.set("我是线程1");
Log.d(TAG, "mStringThreadLocal 线程1: " + mStringThreadLocal.get());
}
});
exec.submit(new Runnable() {
@Override
public void run() {
Log.d(TAG, "mStringThreadLocal 线程2: " + mStringThreadLocal.get());
}
});
运行结果:
D/MainActivity: mStringThreadLocal 主线程 : 我是主线程 D/MainActivity: mStringThreadLocal 线程1: 我是线程1 D/MainActivity: mStringThreadLocal 线程2: null
从日志可以看到,虽然在不同线程访问的同一个ThreadLocal对象,但是他们通过ThreadLocal获取到的值确不一样
结论: 对ThreadLocal所做的 读写操作仅限于各自线程的内部,ThreadLocal可以在多个线程互不干扰的存储和修改数据。
!!!任务: 分析ThreadLocal存取算法
[^参考:Android开发艺术探索 p377]
消息
同步消息
来个简单的例子
1
2
3
4
5
6
7
8new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler = new Handler();
Looper.loop();
}
}).start();
把 Looper.prepare();注释,报错日志 : java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
从报错信息入手看原因,初始化Handler (API = 25)
1 | public Handler(Callback callback, boolean async) { |
mLooper为空,所以报错,看下Looper.myLooper();
1 | /** |
注释可以看到,当前线程没有Looper对象, 那么Looper对象是怎么创建的呢,很显然就是上面例子里的 Looper.prepare();
1 | /** Initialize the current thread as a looper. |
如果没有Looper 则new 一个,由此也看到每个线程最多一个Looper对象,接着看初始化的Looper
1 | private Looper(boolean quitAllowed) { |
MesageQueue也是通过Looper创建的,看到private 构造方法,一个Looper管理一个 MesageQueue,
但是我们通常在UI线程初始化Handler不需要调用 Looper.prepare();
,这是因为应用启动后,主线程
ActivityThread main方法为我们做了工作。
1 | public static void main(String[] args) { |
这样初始化Handler操作分析好了,接着就开始发消息了 handler.sendMessage(message);
,经过层层调用来到这个方法
1 | public boolean sendMessageAtTime(Message msg, long uptimeMillis) { |
第二个参数时间是可以添加延时的参数
1 | boolean enqueueMessage(Message msg, long when) { |
这要Mesage消息就添加到 MesageQueue中了,按事件顺序添加,然后就是取消息了,通过调用Looper.loop();
1 | /** |
通过 Message msg = queue.next(); 获取消息,然后用 msg.target.dispatchMessage(msg);
把消息往哪发呢 ?
1 | /** |
点开 handleMesage(msg),msg.target 就是Handler
1 | /** |
这里就清楚了,消息就传到了 Hanlder里面handleMessage的实现方法
从上面分析handler消息传递基本了解了,借张图看下整片森林
异步消息
Android系统16ms会刷新一次屏幕,如果主线程的消息过多,在16ms之内没有执行完,必然会造成卡顿或者掉帧。那怎么才能不排队,没有延时的处理呢?这个时候就需要异步消息,在处理异步消息的时候,我们就需要同步屏障,让异步消息不用排队等候处理。可以理解为同步屏障是一堵墙,把同步消息队列拦住,先处理异步消息,等异步消息处理完了,这堵墙就会取消,然后继续处理同步消息。
https://blog.csdn.net/ly502541243/article/details/109091386
查找异步消息方法
MessageQueue.java
1 | Message next() { |
https://blog.csdn.net/ly502541243/article/details/109091386
发送延时消息
看下面问题:handler如何实现延时发消息postdelay()
https://zhuanlan.zhihu.com/p/260661053
https://blog.csdn.net/thh159/article/details/103644489
IdleHandler使用
当消息队列空闲时会执行IdleHandler的queueIdle()方法,该方法返回一个boolean值,如果为false则执行完毕之后移除这条消息,如果为true则保留,等到下次空闲时会再次执行,下面看下MessageQueue的next()方法可以发现确实是这样
1 | Message next() { |
Activity启动优化:onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间
想要在一个View绘制完成之后添加其他依赖于这个View的View,当然这个用View#post()也能实现,区别就是前者会在消息队列空闲时执行
一些第三方库中有使用,比如LeakCanary,Glide中有使用到,具体可以自行去查看
以前我们在Activity中获取某个控件的宽高的时候总是得到的是0,那是因为view的测量还未完成。通常的做法是监听ViewTreeObserver,它是在ViewRootImpl测量完成之后调用ViewTreeObserver.dispatchOnGlobalLayout()方法,这时候在onGlobalLayout回调中获取的控件宽高都是正确的数据。
1 | Looper.myQueue().addIdleHandler { |
https://blog.csdn.net/ZYJWR/article/details/103086664
https://www.wanandroid.com/wenda/show/8723
问题
handler的是怎样实现的?
新线程中执行了一个耗时操作,然后把该结果塞给message,handler将发送这个messageQueue,Loop不停的从消息队列中取出消息。Handler 分发给Ui.
Handler是怎么切换线程的
我们在不同的线程发送消息,线程之间的资源是共享的
Handler机制了解吗?一个线程有几个Looper?为什么?
只能有一个,不然调用Looper.prepare()会抛出运行时异常,提示“Only one Looper may be created per thread”
handler如何实现延时发消息postdelay(),
可以看到这里也是一个for循环遍历队列,核心变量就是nextPollTimeoutMillis。可以看到,计算出nextPollTimeoutMillis后就调用nativiePollOnce这个native方法。这里的话大概可以猜到他的运行机制,因为他是根据执行时间进行排序的,那传入的这个nextPollTimeoutMillis应该就是休眠时间,类似于java的sleep(time)。休眠到下一次message的时候就执行。那如果我在这段时间又插入了一个新的message怎么办,所以handler每次插入message都会唤醒线程,重新计算插入后,再走一次这个休眠流程。
值得注意的是这个方法没有开启子线程,只是调用了run(), 在 msg.target.dispatchMessage(msg)
可以看到,接着调用了handleCallback().
https://www.jianshu.com/p/68083d432b3f
如果移除一个延时消息会解除休眠吗
主线程死循环不会卡死吗
从前面的主线程、子线程的分析可以看出,Looper会在线程中不断的检索消息,如果是子线程的Looper死循环,一旦任务完成,用户应该手动退出,而不是让其一直休眠等待。(引用自Gityuan)线程其实就是一段可执行的代码,当可执行的代码执行完成后,线程的生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder 线程也是采用死循环的方法,通过循环方式不同与 Binder 驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。Android是基于消息处理机制的,用户的行为都在这个Looper循环中,我们在休眠时点击屏幕,便唤醒主线程继续进行工作。
主线程的死循环一直运行是不是特别消耗 CPU 资源呢? 其实不然,这里就涉及到 Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
https://www.jianshu.com/p/a7969d73c120
handler内存泄露问题
说说你对Handler机制的了解,同步消息,异步消息等
IdleHandler用过吗,IdleHandler应用场景?
见上面IdleHandler使用
handler如何实现延时发消息postdelay()。callback,runnable,msg的执行优先级。阻塞是怎么实现的?为什么不会阻塞主线程?
Handler休眠是怎样的?epoll的原理是什么?
如何实现延时消息,如果移除一个延时消息会解除休眠吗?
Handler内存泄漏的GCRoot是什么?
epoll的时候算是卡顿吗
怎么样算是卡顿了
怎么利用消息机制检测卡顿
除了这种方式还有别的监测卡顿的方式吗
Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume
等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。
主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll
机制,简单说就是在主线程的MessageQueue
没有消息时,便阻塞在loop的queue.next()
中的nativePollOnce()
方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。 Gityuan–Handler(Native层)
Handler通信,Binder通信
Handler同步屏障
Handler发送的消息分为普通消息、屏障消息、异步消息,一旦Looper在处理消息时遇到屏障消息,那么就不再处理普通的消息,而仅仅处理异步的消息。不再使用屏障后,需要撤销屏障,不然就再也执行不到普通消息了。
为什么需要这样?它是设计来为了让某些特殊的消息得以更快被执行的机制。比如绘制界面,这种消息可能会明显的被用户感知到,稍有不慎就会引起卡顿、掉帧之类的,所以需要及时处理(可能消息队列中有大量的消息,如果像平时一样挨个进行处理,那绘制界面这个消息就得等很久,这是不想看到的)。
屏障消息仅仅是起一个屏障的作用,本身一般不附带其他东西,它需要配合其他Handler组件才能发挥作用。
1、Handler问题三连:是什么?有什么用?为什么要用,不用行不行?
2、Android UI更新机制(GUI) 为何设计成了单线程的?
3、真的只能在主(UI)线程中更新UI吗?
4、真的不能在主(UI)线程中执行网络操作吗?
6、为什么建议使用Message.obtain()来创建Message实例?
7、为什么子线程中不可以直接new Handler()而主线程中可以?
8、主线程给子线程的Handler发送消息怎么写?
9、HandlerThread实现的核心原理?
10、当你用Handler发送一个Message,发生了什么?
11、Looper是怎么拣队列里的消息的?
12、分发给Handler的消息是怎么处理的?
14、Looper在主线程中死循环,为啥不会ANR?
15、Handler泄露的原因及正确写法
16、Handler中的同步屏障机制
17、Android 11 Handler相关变更
https://juejin.cn/post/6844904150140977165
handler post和handleMesage区别
post的runnable会直接在callback中调用run方法执行,而sendMessage方法要用户主动重写mCallback或者handleMessage方法来处理
https://www.bilibili.com/video/BV1A7411M7zQ?from=search&seid=14693797999095810218
https://juejin.im/post/6844904150140977165
http://blog.csdn.net/guolin_blog/article/details/9991569
https://xiaozhuanlan.com/topic/0843791256
glide handler