Handler源码解析:
-
Android源码:
- Android11--->30的源码
-
第二期Handler源码学习:
-
Handler作用
-
依托消息管理机制,进行线程切换;
-
共享内存池,MessageQueue:避免内存抖动
- next函数:将消息传出去
-
Looper.loop():轮询,取出消息
-
采用for循环不断进行轮询(死循环),取出消息
-
此时,ActivityThread中的main就会在死循环中不断执行
- 因为APP启动后,一直可见(loop中的死循环就保证了代码不会停止)
- 程序不会停下的好处:类似于后台服务器一直保证活(loop是心跳机制)
- 但是当没有消息了,loop就会被阻塞(block)
-
由ActivityThread调用
-
-
调用dispatch函数进行分发:
-
-
APP中的所有事务处理都是Handler
- 每一个APP事务都是Message;
- 服务/Fragment中的每一个生命周期都是一个Message
- 相当于所有代码都是运行在Message之上了
- 证明了所有的异常的log信息的起点都是Looper.loop
-
在处理APP卡顿:定位卡顿位置,判断每个生命周期的执行时间
-
基础:
- Handler对外开放了接口
- 所有主线程中的代码都是Message
-
思路:BlockCanary
- 搞一个日志管理系统:通过日志打印时间不同得出每段生命周期的执行时间,就行了
- 找每个生命周期的执行时间--->找对应的消息的时间
-
执行:BlockCanary的实现思路
- 在Looper中有一个msg.target.dispatchMessage(msg)代码
- 在前后分别整一个开始时间和结束时间--->对logging赋值
- Looper中有一个叫setMessageLogging的接口,设置一个类,就是接口的参数,就可以打印各个生命周期执行的时间
- 最后再在msg.target.dispatchMessage(msg)这行代码后面的还有一个logging对应就行了
-
-
从handler.sendMessage到Handler处理发生了什么?
-
概述:
- sendMessage:向消息队列中添加消息,生产者
- next():从消息队列中取出消息,消费者
- 消息队列本身可以看作仓库,整体为生产者消费者模型
-
生产者--消费者是一个带阻塞的系统
-
当仓库满了--->入队列被阻塞
-
当仓库空了--->出队列被阻塞
-
仓库问题:本身没有设置上限,但是当系统没有内存,进程没有内存时就是满了
-
为什么不设置上限:达到上限APP就挂掉了
- 因为Message本身就是一个心跳机制并且对于60HZ的手机,一秒刷新60次,平均16.7ms刷新一次UI,一次刷新带来3条Message;直接干没了。直接就挂掉了
-
-
仓库什么时候醒来?入队列就醒了
- 会调用Native方法
-
-
什么是阻塞,为什么仓库空了要阻塞,为什么这个阻塞不会导致ANR?
-
什么是阻塞:非得等,并且不能干其他的,阻塞就是睡觉,将CPU的资源放出去;这个时候线程都挂起来了
-
这个时候可以睡觉,当可以了,就会通知你
-
因为安卓中的每一个都可以看做消息,当这个仓库都会空了,那么就让它睡觉,把CPU的资源交出去;
-
这个时候为什么不会ANR,明明线程都挂起来了,而且挂的时间还比较久
-
Handler的阻塞与Message的阻塞两码事并且ANR也算一个消息
-
ANR可以看作一个定时任务没有在定时范围内完成,就像埋了一个定时炸弹;
没有完成就爆炸,并且弹个ANR
-
-
-
Android阻塞是如何实现的:
-
消息队列入队列是如何排序的:for循环轮询,按照执行时间排序,时间早在前面
-
但是当我拿消息(加了锁的Synchronized)的时候,还没有等到它的执行时间,那么就阻塞一秒钟;但这个是不可能的;因为至少系统会不断刷新UI
-
具体实现
-
取出对头消息,拿到时间,计算出需要等的时间
-
退出,将这个时间通过nativePollOnce传到native代码中:
-
java去调用底层代码--->Linux 层;java虚拟机是由C++实现的,
java调用底层代码(JNI 层),此时将这个java代码打上native标记
-
-
什么是静态调用:
- java层中调用的跟Native层中调用的是一样的
-
-
-
Handler中的阻塞:最终是epoll_wait()
-
调用流程:
-
当消息需要等待的时候调用nativePollOnce(this,等待时间)
-
调用与之同名的JNI层的函数:android_os_MessageQueue_nativePollOnce
-
然后转交到NativeMessageQueue::pollOnce
- 调用C++层中对应的mLooer->pollOnce
-
再到pollInner
- 最后到这个epoll_wait里面来(在这里线程才被阻塞,上层调用者也因此被阻塞)
-
-
-
非阻塞忙轮询
- 一分钟干一个电话,问到没有
-
场景:设计到I/O:(当有多个网络请求,从数据库中拿数据)
-
阻塞:读数据的时候用的
-
因为在读取数据的时候,由于读取的速度不同(缓存,阻塞)
-
一个线程中如何处理多个I/O?最节约时间的
- 一个线程里面用while循环,遍历所有的流;有就拿,拿完所有的流
- 当所有的流都没有数据了,这个时候还是在for循环里面但是没有读取数据的动作,CPU就空转--->CPU资源浪费
- 所以当流都读完了,就阻塞,避免CPU一直在for循环中挣扎
-
-
引入select:处理的是一系列的流,是一把
- 还有一种解决办法:在for循环之前使用select选择流(Linux 进行实现的),当都没有需要处理的流了,CPU在select里面阻塞;
- select只知道这个一系列流中有I/O需要处理但是它不知道要处理谁,要处理多少个;这个是硬件层面的了
- 一般情况下都是,先使用select判断一下;这一系列中有需要处理的,那么就搞个循环去处理所有的流;select就是先对一大把过滤一下,最后细化
-
细节
- 即使采用多个线程处理多个I/O :由于CPU底层决定了,每一次I/O,都会涉及到硬盘的读取,每一次读取都会降低效率;
-
引入epoll:epoll是Linux内核中的一种可扩展IO事件处理机制。大量应用程序请求时能够获得较好的性能。
-
-
引入epoll
-
工作原理:
- 使用epoll_wait()处理所有的流,当这个流有I/O需要处理的话,就把这个流加到activie_stream[]去--->后面只需要使用一个for循环去遍历activie_stream[]就行了
-
好处:
- CPU底层是需要对所有的流都进行一次遍历O(n),但是引入epoll机制后,将时间复杂度降到O(1)了
-
重要函数:
-
int epoll_create(int size); 创建一个epoll的句柄, size用来告诉内核需要监听的数目一共有多大
在创建MessageQueue的时候,会调用nativeInit,在这个里面完成对epoll进行初始化
-
int epoll_ctl(int epfd, int op, int fd, structepoll_event *event); epoll的事件注册函数,因为MessageQueue本身就可以看做是一个事件
-
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout); 参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)
-
线程与epoll之间是没有关系的,是在MessageQueue搞了一个出来就会带上一个epoll
-
-