类似于过安检,人(Handler)将行李(Message)放在安检机(MessageQueue)中,传送带(Looper)通过不断循环将行李从安检机中取出,最后由同一个人(Handler)取走处理。
- 轮询器。每个线程中只有一个Looper,切换线程和消息分发,唤醒与挂起靠的是 Linux 中的 epoll 机制来实现。
handler构造时,可以选择是否传入looper对象,若不传则默认从当前线程获取,若取不到则会抛出异常。这就是为什么在子线程里不能简单用消息循环的原因,因为handler没有和looper建立起关联。
AMS 调用 ActivityThread.main() 进而调用 Looper.prepareMianLooper() 进而调用 parper(),将主线程和 Looper 绑定,然后调用 loop() 跑起来。
用来将当前线程和Looper绑定。先通过 sThreadLocal.get() 看有没有 value(这里是主线程是否绑定过Looper),value不为 null 就报错不让再次赋值保证了 Looper 的唯一性,value为 null 就调用 sThreadLocal.set() 新建一个 Looper 对象(构造函数中会创建MessageQueue对象)传入赋值实现了 Looper 和主线程绑定。
Looper 中创建了一个 static final 的 ThreadLocal 成员变量。
该类中有一个成员变量 threadLocals 它是一个空的 ThreadLocalMap。即每个线程都有自己的ThreadLocalMap,当不同线程访问代码被 ThreadLocal 操作的是它们各自持有的数据。
在一个线程中可以创建多个 ThreadLocal 对象(作用是当作key存入value),不管是调用 ThreadLocal 对象的 get() 还是 set() 内部都会先获取当前线程Thread(这里是主线程),然后获取 Thread 所持有的 ThreadLocalMap(即Thread的成员变量threadLocals),以该 ThreadLocal 对象(这里是sThreadLocal)为 key 往里面存取 value(这里是Looper)。由此通过 sThreadLocal 将 Looper 和主线程的绑定。
是 ThreadLocal 的静态内部类。Entry 继承于 WeakReference,key 是 ThreadLocal 类型,value是 Object 类型。
返回当前线程绑定的 Looper 对象,如果未绑定过则返回null。
拿到当前线程的 Looper 对象(此处为主线程),通过无限 for 循环保证了不会因为代码执行完而退出 main() 函数导致程序(进程)结束。loopOnce()中调用 MessageQueue.next() 从队列中取出 Message,再调用 Message.target.dispatchMessage() 分发给发送这个 Message 的 Handler 处理(即发送和处理该 Message 的是同一个 Handler)。
负责发送和接收(处理)Message。Handler 在构造时可以选择是否传入 Looper 对象,若不传则默认从当前线程获取,若取不到则会抛出异常。这就是为什么在子线程里不能简单用消息循环的原因,因为需要 Handler 和 Looper 绑定。
sendMessageAtTime( ) | 发送消息的方法非常多 (post/send) 最终调用的都是这个,uptimeMillis即系统开机时间uptimeMillis和传入的延迟时间 delayMillis 相加。 |
handlerMessage( ) | 由外部来重写该方法,以此来处理收到的Message。 |
dispatchMessage( ) | 用于分发消息,判断Message的callback,不为null调用Runable(),为null调用handleMessage()。 |
Handler构造时需要传入一个 Looper 实现了 Handler 和 Looper 绑定,通过唯一的 Looper 拿到唯一的MessageQueue,使得 Handler 和 MessageQueue 绑定(即同一个 Handler 在不同线程中发送 Message 是发到同一个 MessageQueue 中),由于该 MessageQueue 所属的 Looper 所绑定的是主线程,因此子线程中发送的 Message 最终是在主线程中处理。
发送消息的方法非常多(各种post/send)最终调用的都是这个,在确保存在 MessageQueue 后就调用 enqueueMessage() 将 Message 入列。
Handler先将自己赋值给 Messgae 的 target 实现了 Handler 和 Message 绑定(即发送和处理该 Message 的是同一个 Handler),然后调用 MessageQueue.enqueueMessage() 将 Message 存入队列中。
判断 Message 携带的是 callback 还是对象,是 callback 就直接运行,是对象就回调到 handleMessage() 中让我们在创建 Handler 对象的地方重写该方法处理。
消息队列。每个线程中只有一个MessageQueue,存放 Handler 发送来的Message。是单链表结构(非线性非顺序的物理结构。采用见缝插针的存储方式不要求内存连续,也不需要随机访问(只需要取头值),靠 Message中 的 next 指向下一个,随机存储顺序访问)。
Looper构造是私有的,其中会创建 MessageQueue 并赋值给 final 修饰的成员变量mQueue,因此 MessageQueue 也是唯一。
enqueueMessage()里的同步锁让 Handler 在不同线程中发送过来的 Message 同步入列,并通过形参 when 来对队列中的 Message 进行排序。形参 when 给 Message 加了时间戳排序。
next()里的同步锁与 enqueueMessage() 里的同步锁相互协作,即便是在子线程发消息在主线程取消息,实现了消息入列和出列的互斥性(不能同时进行),保证了有序性和线程安全。
可以携带需要的数据在线程间传递。根据优先级分为:异步消息 > 普通消息 > idleMessage。
消息屏障 | 判断依据 msg.target == null。发送屏障消息后会发送异步消息(判断依据msg.isAsynchronous),获取的是当前时间戳,会加入到队列头部,由于优先刷新UI。 |
idleMessage(不重要消息) | 当所有延时为0的消息都处理完后,主线程没有重要的事情做的时候(即不影响主线程的时候),才会去执行idleMessage。例如GC、AMS管理Activity的 stop destroy。 |
Message的字段 | 说明 |
what: Int | 唯一标识。 |
target: Handler | 绑定Handler(该 Message 是由同一个 Handler 发送和处理的)。 |
data: Bundle | 携带 Bundle 数据。 |
arg1: Int arg2: Int |
携带 Int 数据。 |
obj: Object | 携带可序列化数据。 |
callback: Runnable | 携带 Runnable 数据(Handler的post()就是将一个Runnable对象复制过来封装为Message对象)。 |
when:Long | 时间戳。 |
next: Message | 指向下一个节点(Message 通过 next 字段指向下一个Message,从而串联成队列。enqueueMessage()、recycleUnchecked()、obtain() 操作的就是 next 的指向)。 |
回收后用来复用的 Message,它是 static 的。MAX_POOL_SIZE = 50 缓存池最多存50个,超过就会 new 了。sPoolSize = 0 用来记录池中数量,缓存一个就+1取出一个就-1。spoolSync用来做同步锁。
构建 Message 实例,可以通过 new 关键字,推荐通过 obtain() 方法。APP中会有大量管理事件需要通过 Handler 来发送消息(60Hz屏幕光UI刷新每秒就有60个Message更别其它提系统服务了),使用缓存池为了避免频繁创建销毁 Message 对象内存抖动造成卡顿。
使用完的 Message 会被回收,将字段携带的数据都清除掉后,通过 next 将下一个节点指向当前 sPool,再将自己赋值给sPool,这样就像该 Message 插入到了缓存池队列头部。
- 在主线程里构造 handler 并重写 handleMessage( ) 方法;
- 在子线程里构造 message,并使用 handler 将 message 发送出去;
- 在主线程里的 handleMessage( ) 方法中判断是哪个 message 然后做相应处理。
Activity {
private val updateText = 123 //定义Message唯一值
//【第一步】创建一个全局的Handler并重写handleMessage()
private val handler = object : Handler(Looper.getMainLooper()) {
//【第三步】处理消息
override fun handleMessage(msg: Message) {
when (msg.what) { //判断是哪个Message
updateText -> {
msg.obj //获取携带的数据
}
}
}
}
//【第二步】创建Messgae并发送
fun show() {
val str = "一条消息"
//方式一:直接新建
val msg1 = Message()
msg1.what = updateText
msg1.obj = str
handler.sendMessage(msg1)
//方式二:避免频繁创建销毁Message推荐使用obtain()
val msg2 = Message.obtain()
val msg3 = handler.obtainMessage() //相对于上一个,会自动将target字段设置为这个handler
val msg4 = handler.obtainMessage(updateText, str) //相对于上一个,构造里就能携带what和obj字段
handler.sendMessage(msg4)
//方式三:利用post在子线程中更新UI,利用postDelayed延时
handler.postDelayed({
//此处执行在Runnable中
}, 2000) //延迟2秒
}
}
Kotlin无影响的原因详见闭包讲解
- 非static的内部类或匿名内部类会持有外部类的引用,我们经常在一些内部类中显示跳转activity的时候,给Intent赋值的时候,第一个参数会写外部类名.this ,这就是持有外部类的引用的很好表现。 同样,其他地方需要用到这个内部类的时候,也不能是直接new出来,因为为非static的,必须先通过new出外部类才能。
- 那么现在的情况就是,这个非static的handler内部类,无论是否是匿名的,便会持有外部的activity的引用。在Activity finish之后,若此时消息队列中有未处理完的Message(特别是带有延时的),那么Handler也仍然存在(在enqueueMessage()的时候Handler被赋值给了Message.target,所以Message持有Handler),由于Handler中持有Context的引用,那么Activity就无法正常回收(GC可达性分析),Activity中所有占内存的东西就成了内存泄露。
- 方式一:被static修饰后调用便不需要外部类的实例,但是总不能把Activity中的控件全static才能操作,我们通过使Handler持有Activity的一个弱引用来解决这个问题,直接持有Activity的话,我们便与之前的匿名内部类直接持有外部类的引用没区别了,而持有了弱引用,在Activity有用的情况下,其会被AMS持有强引用,GC不会回收,而当其finish了,便没有强引用对象持有了,此时GC时便会回收该Activity。
- 方式二:在 onDestroy() 中调用 handler.removeCallbackAndMessage() 也可以避免内存泄漏。
public class MainActivity extends Activity {
private MyHandler mMyHandler;
//自定义一个Handler
private static class MyHandler extends Handler {
private WeakReference mActivityWeakReference;
//构造中传入当前Activity并使用弱引用包装
public void MyHandler(MainActivity activity) {
mActivityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
MainActivity mainActivity = mActivityWeakReference.get();
if (mainActivity != null) {
switch (msg.what) {
case 0:
//TODO...
break;
}
}
}
}
要在子线程中创建 Handler 就在构造的时候传入子线程中的Looper,进而需要拿到子线程的 ThreadLocal,进而需要拿到子线程对象,因此子线程不能写成匿名内部类,需要定义成一个类。在类中重写 run() 方法调用 Looper.prepare() 创建 Looper 再调用 looper.loop() 将循环跑起来,然后提供一个 getLooper() 方法供外部获取。外部先创建线程对象,调用 start() 跑起来就初始化了Looper,然后通过获取 getLooper() 的在创建 Handler 的时候传入。注意:子线程在 run() 中创建Looper,外部创建 Handler 时是在主线程中 getLooper() 传入的,可能为null,主线程拿子线程结果存在多线程并发问题。使用 sleep() 存在性能问题,要使用同步锁机制。
实际开发中不会手写,而是使用HandlerThread。Looper在 run( ) 中创建在 getLooper( ) 中获取,两个方法中的同步代码块使得创建和获取互斥,保证了获取前 Looper 已经被初始化。
获取 Looper。同步代码块中的判断用来确保获取前 Looper 已经被初始化,否则释放锁让线程进到 run( ) 方法的同步代码块中创建Looper。
创建 Looper 并循环起来。同步代码块保证了在 getLooper( ) 中获取的安全性。可能有很多地方等着获取该 Looper 来创建 Handler,因此要 notifyAll() 全部唤醒。
val handlerThread = HandlerThread("demo") //创建HandlerThread子线程
handlerThread.start() //启动线程
val handler = Handler(handlerThread.looper) { msg -> //创建Handler
//此处为handleMessage
when (msg.what) {
123 -> {}
else -> {}
}
false
}
handler.sendEmptyMessage(123) //发送消息
.1 Handler、Loop、MessageQuee关系?
一个线程对应一个 Looper 对应一个 MessageQueue 对应无数个 Handler。
.2 非UI线程真的不能操作View吗?
可以,报错的意思是“只有创建视图层次结构的原始线程才能访问其视图”,界面一般是主线程进行绘制的,所以界面的更新也就一般都限制在主线程中。Android系统只是限制了必须在同个线程内进行 ViewRootImpl 的创建和更新这两个操作。ViewRootImpl 在初始化的时候会将当前线程保存到 mThread,在后续进行 UI 更新的时候就会调用 checkThread() 方法进行线程检查,如果发现存在多线程调用则直接抛出以上的异常信息(哪个线程创建的view哪个线程才能更改)。
public void click(View view) {
new Thread(new Runnable() {
public void run() {
Looper.prepare();
boolean b1 = Looper.myLooper() == Looper.getMainLooper(); //fale,当前线程不是主线程
Toast.makeText(Drawer99_Test_Activity.this,b1+","+b2,Toast.LENGTH_LONG).show();
Looper.loop();
}
}).start();
}
.3 为什么不用wait/notify而用epoll?
早期使用的是 wait/notify,Android2.3后来使用 epoll 机制为了可以同时处理 native 消息。
.4 View.post() 和 Handler.post()的区别?
View.post() 最终调用的是 Handler.post()。
.5 如何进行线程切换的?
子线程持有的 Handler 如果绑定到的是主线程的 Looper (Handler创建的时候需要传入Looper) 的话,那么子线程发送的 Message 就可以由主线程来消费,以此来实现线程切换,执行 UI 更新操作等目的。
.6 主线程 Looper.loop() 为什么不会导致 ANR?
loop() 循环本身不会导致 ANR,会出现 ANR 是因为在 loop 循环之内 Message 处理时间过长,无法处理其他事件导致。让程序不退出的话,写一个死循环,那么main方法中的代码永远不会执行完,这样程序就不会自己退出了。正是因为Looper.loop方法这个死循环,它阻塞了主线程,所以我们的app才不会退出。本质上Android就是事件驱动的程序,界面刷新也好,交互也好,本质上都是事件,这些事件最后通通被作为了Message发送到了MessageQueue中。由Looper来进行分发,然后在进行处理。也就是我们的Android程序就是运行在这个死循环中的,一旦这个死循环结束,app也就结束了。
.7 为什么 UI 体系要采用单线程模型?
UI控件不是线程安全的,并发访问会造成不可预期的状态,上锁会变得复杂低效,还会阻塞某些进程的执行。