应用启动流程:launchAPP zygote fork一个进程,启动虚拟机,启动ActviityThread,执行main方法,
调用Looper.prepareMainLooper(); Looper.loop,开启循环
开启循环是开启了一个死循环,死循环中,会不停的拿消息出来,Looper.mQueue.next(),
然后得到msg,而后通过msg.target获取到这个msg对应的handler,调用handler的dispatchMessage。对应代码 msg.target.dispatchMessage(msg);
有一个判断 当这个msg为空时,退出循环。
我们平时使用handler,开始在handler.sendMessage(),结束在handler的handlerMessage,这中间发生了什么事呢,追源码看一下
handler发送消息有很多方法,但是最终都是调用 了enqueueMessage(queue, msg, uptimeMillis);该方法第一行 msg.target = this;将message与发送的handler绑定到一起。
而后调用了queue.enqueueMessage(msg, uptimeMillis),这个queue其实就是Messagequeue,往下看Messagequeue的enqueueMessage干了什么,源码如下
从这里可以看出,enqueueMessage为msg入队的操作,并且msg的消息队列实际上是一个链表,每个msg的next都指向了下一个msg,这里实际为一个优先级队列。
这个next的调用方为looper。调用Looper.loop(),其中又调用了MessageQueue的next()。返回一个msg,而后调用msg的handler.dispatchMessage(msg),最终调用了我们常用的handler的handlerMessage;
这其中有一个比较核心的点,我们一般使用handler都是从主线程或者子线程发送,最后接收的地方都为主线程。从内存上来说,message本身为一块内存,而内存的使用对象可以是任意线程,无所谓在哪里发送消息,想一想,我们平时使用时实现handleMessage方法是不是都在主线程?自然而然就在主线程接收了,同理,如果在子线程接收了,那就会在子线程接收到消息。
MessageQueue
再来说一下MessageQueue,MessageQueue为单链表实现的优先级队列。
单链表:因为msg只有一个next,所以是单向的。
为什么是优先级队列:是有先后顺序的。我们调用sendMessageAtTIme的时候需要传入一个时间。代码如下:
可以看到,for循环里面对比了传入的msg跟当前轮询的msg的执行时间,如果当前msg的时间小于轮询的msg,那就直接插入队列,将传入的msg的next指向当前轮询的msg。
为什么是队列?因为MessageQueue的next()每次取都是从队头取,也就是所谓的先进先出原则。
Looper
Looper的核心在他的构造函数上,以及loop(),ThreadLocal
Looper的构造函数为private,调用prepare()初始化。在此方法中new对象,这是为了方便管理,防止外部随便创建对象。
prepareMainLooper方法中,首先调用了prepare方法,调用了sThreadLocal的set方法。而sThreadLocal只有一个static final的对象,所以一个线程中只有一个Looper。
怎样保证唯一的?sThreadLocal是一个static final的对象,set方法之前会先get,如果不为空则抛出异常,set中,先根据当前所在的线程拿到线程持有的map,然后将ThreadLocal对象为key,Looper为value放入。从而保障唯一。源码如下:
MessageQueue是怎么创建的:实在Looper对象创建的时候创建的。既然Looper既然是唯一的,那MessageQueue也是唯一的。源码如下:
MessageQueue是属于哪个线程的?MessageQueue是一个容器,是一块内存。内存不能说是属于哪个线程的,所以这个问题本身是错误的。只有方法函数可以说是哪个线程的。
一个线程有几个handler?
无数个,你可以随便new
一个线程能有几个Looper?1个,原因如上。
handler内存泄漏原因?
因为匿名内部类的创建方式,会持有外部类的引用,但是在Activity销毁的时候,Handler如果没有手动销毁,那就会内存泄漏。举个例子:如果msg是一个20分钟后执行的任务,msg的target指向了handler,handler所处的Activity销毁之后,但是由于GC不能回收msg,所以不能回收handler。也不能回收handler所持有的Activity。总结一下为:内部类对象的生命周期大于了持有的外部类对象的生命周期,导致泄漏。
为什么主线程可以直接new Handler,子线程为什么不行。
其实核心不在于handler,核心在于Looper,因为主线程启动的时候默认创建了,而子线程是没有的,所以需要手动创建,也就是调用Looper.prepare(), Looper.loop;
那当子线程没有消息的时候怎么办?没有消息的时候,就调用了Looper的quit(),looper的loop死循环只有一种情况会退出,那就是msg为空的时候,什么时候为空呢,当Looper调用quit的时候,会反回一个空的消息。
handler一整套运作机制是一个生产者消费者的设计模式,但是有区别的点在于,生产着消费者的仓库是有限制大小的,而messagqueue是没有大小限制的,所以当消息过多的时候执行速度跟不上会卡顿,并且消息过多,会导致内存溢出。
有两种情况会堵塞、等待:
第一种,当取出的消息还没到执行的时刻,就会等待,比如:handler post延迟10s任务。
源码里面又一层判断,如果msg的执行时间大于当前时间,那会计算出需要延迟多少秒,而后进行下次循环,首先会判断 延迟的时间是否为0,不为0,则会调用nativePollOnce方法进行睡眠等待。
第二种:消息队列为空的时候,会一直等待直到下次事件,nativeWake唤醒
既然可以有多个handler能往队列里放消息,那是如何保证线程安全的?
用锁。synchronized 内置锁。在enqueueMessage(),next(),quite()方法中都加了 对象锁,也就是MessageQueue的对象,并且每个线程只有一个对象。所以当取消息或者放消息,在一个线程中同时只能有一个在操作。
主线程是否能quite
不行。主线程调用会抛异常。
msg的复用,为什么要复用?
复用是为了防止,msg频繁的创建回收出现内存抖动。如果不复用,容易产生内存碎片,而如果此时有对象需要申请内存,内存碎片的大小小于申请的内存大小,就会出现OOM,比如 内存长度为10,在4跟9的位置内存被使用,此时有一个对象需要申请一个长度为5的内存,那此时内存就不够用,会oom。
使用Message.obtain() 使用了享元设计模式,实现内存复用。
Looper死循环为什么不会导致应用卡死?
Looper的死循环跟应用卡不卡是没有关系的。平时应用中的点击事件或者广播之类的东西都是最后包装程msg然后触发的,当一个msg的执行时间大于5s就会被系统判定为anr,然后应用结束。而looper的死循环是一直在处理消息,当消息为空时,会挂起 释放cpu资源,但是如果有事件来到,则会被唤醒继续处理任务。
同步屏障
普通的msg是属于异步任务,需要入队列等待轮训。而有一些任务是需要立马执行,叫做同步任务。比如我们的点击事件,屏幕刷新事件。