- 一.前言
- 二.Handler两问
- 三.Handler的使用
- 四.整体架构
- 五.Message,Handler,MessageQueue,Looper各自的作用
- 六.源码相关
- 5.1.Looper
- 5.2.Handler
- 5.4.消息的发送过程
- 5.5.Looper线程循环阶段
- 六.Handler涉及的问题总结
- 1.Handler的主要作用或能做什么事情
- 2.系统为什么不允许在子线程中更新UI
- 3.为什么UI线程不设计成线程安全的
- 4.非UI线程一定不能更新UI吗
- 5.Handler是如何完成子线程和主线程间的通信
- 6.Handler工作原理
- 7.Looper的工作原理
- 8.Looper会调用MessageQueue的next方法来获取新消息
- 9.Handler如何实现阻塞的
- 10.Message的obtain涉及到了享元设计模式
- 11.生产者消费者模式
- 12.消息入列
- 13.出列
- 14.Handler、MessageQuese、Looper、Message对应关系
- 15.ThreadLocal
- 16.prepareMainLooper
- 17.Looper 中死循环为什么不会导致应用卡死?
- 18.Handler引发的内存泄漏问题
- 19.Looper的生命周期有多长
- 20.如何保证一个线程只有一个Looper
- 七.Handler问题(二)
- 八.Handler问题(三)
- 九.参考
- 由于本人水平有限,若有错误的地方,请指正,谢谢。
========================================================
Handler相关的问题层出不穷,跟项目也息息相关,而且跟内存优化也有很大关系- Handler面试题
Handler源码FrameWork源码的查看技巧不能陷入细节,会越陷越深,从宏观的角度去看,而不是微观的角度去看;
学习目标:减少内存泄漏、应付面试、防止内存抖动的思想;Handler的原理Handler是做什么的(等价上面的问题)做线程通讯的解决问题最核心的地方:是解决线程切换问题;
经典面试题:Looper.loop方法还会不会往下执行Handler架构-传送带图Handler源码从发消息作为切入点主要函数架构是一种妥协的产物
Looper的生命周期是多长当前线程的生命周期长度
如何保证一个线程只有一个Looper通过ThreadLocal将Looper对象存到Thread中的map中当第二次调用的时候,就从Thread的map中去找,如果之前有存的情况下,就抛出异常;
使用Handler为什么会造成内存泄漏从两个方面进行回答内存泄漏是什么以及持有链(message持有handler引用,是为了做回调处理)说出内存泄漏的本质,然后说出持有链,单单讲解内部类持有外部类引用是不够,不正确的;内存泄漏的本质:长生命周期对象持有短生命周期对象,造成了内存泄漏;
线程—->Looper—->MessageQueue—->Message—>Handler—>Activity;
为什么调用Looper.loop不会产生阻塞(或为什么主线程调用死循环不会出现阻塞);
========================================================
阻塞的位置,为什么主线程阻塞了不会产生anr先回答,android是一个事件驱动的系统,looper循环是不可以退出的。android不同于java程序java程序是单一的,执行完,进程就退出了但是android不可以android是以事件为驱动的操作系统
内存抖动————-再看一下视频短时间内大量创建、并且销毁解决该问题的根本原因是复用
复用池事件分发相关的TouchTarget
- 描述一下什么是epoll机制
- 使用ArrayBlockQueue(单纯的用该队列在纯应用层是没问题的)不能实现发送延迟消息
- 无法做到以时间进行排序
- 使用java对象中锁的唤醒与阻塞也不符合app开发的需求
- 驱动事件的考虑
消息按照时间的方式进行插入和取出的逻辑分析(视频中通过画图的方式讲解,很详细,易懂)一个Looper对应一个回收池
========================================================
- 非阻塞io
- socket的实现是使用了c的socket,c的socket用了select模型(最浪费性能的地方-遍历所有的app)
- nativeInit只做一件事情,往红黑树添加数据(每个app开启的时候都会增加一个文件描述)
- 如何阻塞的
- 通过nativePollOnce—-> 最终调用了epoll_wait(linux函数-等待文件的的IO)
- select和epoll对比,想知道哪个app执行相应的时间,前者需要的时间相对于后者会比较久
========================================================
一.前言
- 本篇文章大致分为三个部分
- 整体架构
- 源码分析
- Handler问题整理
- 学习知识需要有三个阶段,理解—->记忆—->讲解,并且对知识的掌握需要有一定的深度(任玉刚大神给出的建议是,学到阿里p6的程度),同时要避免把知识点的边边角角全部掌握(时间成本过高,并且太过于深入的问题一般人是理解不了的,我们学习知识的目的是为了获取更高的薪水,有更好的工作和生活);
- 笔记终究是笔记,记录的时候要避免以书的方式去记录,根据不同的专题选择不同的模板,适当的增删标题(付诸于模板的标题),而整体架构—->源码分析—->问题收集(应付面试),这种模板对于Handler很合适,很多的技术知识都比较适合该模板;
- 我们在学习的时候,单单只看笔记或者博客的知识,其实是不够的,更好的学习方式是看书(前提是书要选好),看不同作者对同一块知识的讲解,视频也需要配合起来看(讲师的经验以及能力都是非常强的,同样地需要选好机构);
- Handler学习推荐书籍
- 《Android开发艺术探索》
- 《Android的设计与实现》
- 有了书籍,以及该遍笔记(需要不断的丰满),从理解到背诵再到讲解,要逐步走向往新的一层;
学习Handler的建议,去熟悉一下链表数据结构,尝试写出单链表的添加和删除功能。可以帮助更好地理解消息入队和出队的源码部分;
引入问题
- 在Android中如果在子线程中更新ui,就会抛出这样的异常,根本原因是Android中的View不是线程安全的,在Android的应用启动时,会创建一个线程,程序的主线程(作用:负责UI的展示,事件消息的分发。有时候也会把主线程称作UI线程。),在Android平台中会引进一个Handler的机制,可以通过在子线程发送消息来给主线程更新UI,在子线程中可以做耗时的操作,通过Handler发送消息的机制来让主线程更新UI。
Handler主要用于异步消息的处理;
引入问题
学习Handler的原因:Handler跟项目开发息息相关,Handler涉及的问题(包括面试方面)非常多,跟内存优化有很大的关系,而且在整个Android中扮演很重要的地位,总结起来有三个学习目标:
Handler大大的降低了我们开发者开发项目的难度,为什么这么说,原因在于Android中涉及到的线程间通讯的问题,notify,wait等基本上很少使用,我们借助Handler就很容易的完成线程间的通讯。我们不要对Handler有很深入的了解,便可以实现线程间的通讯,对开发者来说,知道的最少,使用简单;
- Handler实现线程间通讯的方案实际上是内存共享的方式;而且内存管理设计的非常优秀,线程间不会彼此干扰;至于wait、notify使用很少是由于将需要这部分的功能在linux层进行了封装;
另外在Android异步消息处理机制的一些框架,如AsyncTask,HandlerThread,IntentService都能Handler有关联;
三.Handler的使用
3.1.post(runnable)
使用该方式,主要有三个步骤: a.在成员变量中创建一个handler(会自动绑定到当前线程); b.在创建的线程中执行耗时操作; c.通过post(runnable)方法,把runnable传到handler当中,handler会在合适的时候让主线程执行runnable当中的代码;
注:post(runnable),最终调用的是sendMessage,只是封装了一下;
3.2.send系列,如,sendMessage(message)
a.创建handler,复写handleMessage方法,根据msg的what值进行不同的操作; b.创建message对象,通过message的what和message的arg1,arg2来进行赋值,主要设置message所携带的数据; c.通过sendMessage方法将message传入handler中,然后通过handlemessage方法来进行ui线程的处理;
注:handler创建有一个特点,在创建一个handler的时候,会绑定到当前的线程;
3.3.handler的主要函数
- Handler通过send系列 或post系列来发送消息,最终都会走MessageQueue.enqueueMessage(Message,long) 方法。
按照模板,就能做到消息从子线程出发,最后又回到了主线程
a. 迪米特原则。在整个Android消息机制里面,整个实现是非常复杂的,但是会发现,当我们使用这个消息机制来进行线程间通信的时候,会发现使用非常简单。只需要创建一个Handler对象就可以使用如此庞大的消息传递机制。
b. 反过来思考,如果不是在发送消息的时候,message上存有handler本身的引用(目标就是目的地),从handler出发到handler结束,那么还需要一个对象专门接收消息;c. 使用message的target,做到了目的地就是出发地,就非常完美的做到了LKP;
四.整体架构
- 关于FrameWork层知识的学习方法注意点
- 源码的查看技巧:不能陷入细节,因为会越陷越深,需要从宏观的角度去看,而不是微观的角度去看;
- FramWork层最应该先学的就是Handler;
- 关于架构(补充)
- 架构是一种妥协的产物
- 包含3点:发消息,取消息,处理消息;
- 我们是在线程(Thread或MainThread)中去写Java代码的,而跟Handler相关的类有,Message、MessageQueue、Looper、Handler;
- 4.1.Handler机制原理[发送消息和处理消息的流程图]
- Looper开启了一个循环,不断地从MessageQueue中获取消息,然后从MessageQueue的头部获取消息,以msg.target的形式调用handler的handleMessage方法进行处理,处理完成之后还是会回到Looper当中不断地开始Looper循环从MessageQueue中获取消息;
- 4.2.四个类Message,Handler,MessageQueue,Looper之间的结构关系;
- UML类图几种关系的总结
- 先理解什么是消息驱动机制比较好,见《Android设计与实现》
五.Message,Handler,MessageQueue,Looper各自的作用
- Message:消息对象,里面有很多参数,用于存放传递的数据,是主线程和子线程传递数据的载体;
- Handler:发送(只发送到Handler自身相关的消息)和处理(Looper发送过来的)消息;
- MessageQueue:消息队列,通过先进先去的方式来管理Message;
- Looper:读取MessageQueue当中的消息,读到消息后,把消息传递给Handler来进行处理;
注:目前只需要知道标记颜色的字体内容即可,后面会对每一个类进行分析;
六.源码相关
源码基于Api29
工具Source Insight 4.0/Android studio(更新该文章的在用)
6.1.分析方式
先了解大体流程,后深入细节;
- 1.按照—->发消息[源码分析的切入点]—->取消息—->处理消息的方式进行;
- 2.中间涉及的一些细节和补充的内容同时会进行讲解;
3.最后,忽略细节,再次按照发消息—->取消息—->处理消息的方式进行整体分析;
6.2.先简单了解消息
举例:线程A与线程B通讯,在A线程发送消息(Message),B线程处理消息,B是根据消息来处理具体的逻辑,相当于是一种命令信息;
- 另外:Handler实现线程间通讯的方案实际上是内存共享的方式,其实也是靠Message决定的;
-
6.3.发消息
先找到跟发消息有关的类和方法,以及需要具备样的条件(环境);
发消息由Handler负责,发消息的方法有很多,我们只分析sendMessage,顺着方法步步查看,如下:
sendMessage
---> sendMessageDelayed
---> sendMessageAtTime
---> enqueueMessage
---> queue.enqueueMessage //分析1:涉及到了queue对象(即MessageQueue类)
对于分析1,即MessageQueue的enqueueMessage方法,内部代码比较核心,源码分析的重点之一。在此之前,我们先要了解Handler构架相关的几个核心类,即4.2中的4个重要类,需要清楚每个类的作用,即五中所写。发送消息是通过调用Handler的方法,实际上是由MessageQueue来进行管理,现在我们来看看分析1所涉及的的代码,如下:
//分析这里的代码是为了引出跟发消息有关的一些需要去了解的内容,先摸清楚轮廓
boolean enqueueMessage(Message msg, long when) {
//先不关注,但是注释还是得加上,回头得看(后面均略写为先不关注)。
//判断Message的target(是1个Handler,发送改msg的那个Handler)是否为空,为空则抛出异常
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//(先不关注)判断Message是否在使用,在使用则抛出异常。目标:为了线程安全,(发消息和取消息)必须保证同一时刻只有一个线程在执行
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//(先不关注)synchronized修饰:同上一步中的注释
synchronized (this) {
//(先不关注)表示消息处理的目标端Handler所在的线程已经异常退出
//主线程
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
//消息正在被使用
msg.markInUse();
//记录消息何时开始处理,when好处及作用需要了解(分析2:跟实现消息的延迟有关联)
//消息是有时间,故有when,以及阻塞和唤醒机制(分析3)的概念。
msg.when = when;
//Message其实是一个链表的结构(分析4),得学习链表的插入,即消息如何实现插入的
Message p = mMessages;
boolean needWake;
//消息的插入代码略
//调用native层的nativeWake方法,略
}
return true;
}
综上:我们目前需要了解哪些知识?
- message的when的好处和作用
- 阻塞唤醒机制
- 链表的插入
- Handler是如何保证线程安全的
- 而阻塞唤醒机制又涉及到阻塞时间和唤醒时间的计算(待了解了发消息和取消息的流程后再分析)
- 按照上面的注释,我们先来解决分析4(下次更新会补充链表的基本操作之插入),分析4的准备知识可以通过《漫画算法》的2.2.2章节来学习。接着,分析when;
- when:消息队列根据when进行排序(根据时间进行排序的链表),谁先执行谁就放在队头。场景假设:在最开始的时候,整个消息队列可能是没有消息或者只有一条消息的,假设队列中有3条消息,此时需要添加第4条消息。添加的方式:沿着消息队列去轮询,新插入的消息跟比较的消息的时间时刻(when)比较,根据时间when的参数执行链表的插入操作。接着分析阻塞和唤醒机制;
阻塞和唤醒机制
涉及到Looper类,先粗略分析一下Looper的next方法,最终是调用MessageQueue的next方法。其它的相关问题先列出来,单暂不分析
- 什么情况下需要使用Looper
- Looper的prepare方法
- Looper的sThreadLocal成员变量
- Looper的quit方法
- 其它…
直接上Looper的next方法的源码
public static void loop() {
//(先不关注)为了获取Looper
final Looper me = myLooper();
if (me == null) {
//(先不关注)使用loop方法时,必须要先创建Looper
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//获取Looper中的MessageQueue对象(MessageQueue跟Looper的关系可以看4.2)
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
//(不关注)
final long ident = Binder.clearCallingIdentity();
//(先不关注)调用loop方法,表示开启无限循环。提出疑问,为什么不卡死所在线程(先不关注)
for (;;) {
Message msg = queue.next(); // might block 消息队列取消息的方法,下面分析
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//...
//(先不关注)
msg.target.dispatchMessage(msg);
//...
//(先不关注) 一个Looper对应一个回收池;
msg.recycleUnchecked();
}
}
分析queue.next()方法,上源码
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
//(先不关注)上方注释写的比较清楚
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//(不关注)
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//(先不关注)
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
//(不关注)
Binder.flushPendingCommands();
}
//(先不关注) 需要知道该方法的作用
//这个函数会阻塞,返回有三个条件
//出错、超时、有消息(事件发送)
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//(不关注)
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
//(先不关注)链表不为空,开始取消息
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
//(先不关注)
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
//(先不关注) 队列中没消息了
nextPollTimeoutMillis = -1;
}
//(先不关注)
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
5.1.Looper
5.1.1.Looper的prepare和myLooper方法
```java public final class Looper {
//存储一个线程局部对象存储Looper对象 static final ThreadLocal
sThreadLocal = new ThreadLocal (); private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue; final Thread mThread;
private Printer mLogging;
//将当前线程初始化为一个Looper线程,进入线程准备阶段 public static void prepare() {
prepare(true);
}
//方法重载 private static void prepare(boolean quitAllowed) {
//每个线程只允许有一个Looper实例
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//创建Looper对象并存入到sThreadLocal,这样每个调用prepare方法的线程将创建本线程惟一的Looper对象
//
sThreadLocal.set(new Looper(quitAllowed));
}
//用于返回当前线程创建的Looper对象 public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
}
- prepare()方法会调用其重载方法创建一个Looper对象,并将其存入线程局部变量sThreadLocal中。sThreadLocal是ThreadLocal类型的变量,保证每个线程中都有该Looper对象的独立副本;
- 创建Looper对象时,会首先判断sThreadLocal线程局部变量中是否已存在一个Looper对象,如果存在便会抛出运行时异常,因此在当前线程中多次调用prepare方法会导致线程异常退出,所以Looper对象是线程唯一的;
- 综上所述,prepare方法创建了一个线程独立且唯一的Looper对象,如果要访问这个Looper对象,只需要使用myLooper方法。
<a name="ae846ef596fadf5a874a0b14355c1b15_h3_1"></a>
### 5.1.2.sThreadLocal
- sThreadLocal是ThreadLocal类型的变量,保证每个线程中都有该Looper对象的独立副本;
- [有精力再深入-并发编程 | ThreadLocal 源码深入分析](https://www.sczyh30.com/posts/Java/java-concurrent-threadlocal/)
<a name="759be2c509976000a3ed8785c5f743c2_h3_2"></a>
### 5.1.3.Looper对象的创建
- 创建Java层的Looper对象(Native层后续知识点跟上再补)
```java
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
- Looper在构造函数中创建了MessageQueue对象,并将其存入成员变量mQueue中,最后在成员变量mThread中存储当前线程;
- 创建Java层Looper对象的同时创建了Java层的MessageQueue对象;
5.1.4.创建Java层的MessageQueue对象
public class MessageQueue{
private final boolean mQuitAllowed;
//...
private native static long nativeInit();
//...
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
//...
//向消息队列添加消息,消息入队;
boolean enqueueMessage(Message msg, long when) {
//...
}
//...
//从消息队列中获取消息,使用for循环,消息出队;
Message next() {
//...
for (;;) {
//...
}
//...
}
//...
}
- 在MessageQueue的构造方法中,首先给传入的quitAllowed参数的成员变量mQuitAllowed赋值为true,表示当前消息循环允许退出;
- 然后调用Native层方法nativeInit进入native层的初始化(Native层后续知识点跟上再补);
enqueueMessage:向消息队列添加消息,消息入队;next:从消息队列中获取消息,使用for循环,消息出队;
5.2.Handler
Handler是Looper线程的消息处理器,创建并初始化Handler是Looper线程运行过程中的关键步骤之一;
5.2.1.Handler构造方法
```java /**
- 分析默认的构造函数
- public Handler() {
- this(null, false);
- } */
public Handler(@Nullable Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { //调式接口,默认为false final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, “The following Handler class should be static or leaks might occur: “ + klass.getCanonicalName()); } }
//mLooper:用于返回当前线程中Java层的Looper对象
mLooper = Looper.myLooper();
//必须调用looper.perpare()后才能使用Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//Looper对象中存储的MessageQueue
mQueue = mLooper.mQueue;
//默认构造中callback为null,即不指定回调函数
mCallback = callback;
//默认构造中mAsynchronous为false
mAsynchronous = async;
}
//成员变量
final Looper mLooper; //存储Java层Looper对象内部的MessageQueue
final MessageQueue mQueue; //存储Java层的Looper对象
final Callback mCallback; //回调函数,处理消息
IMessenger mMessenger; //用于处理跨进程的消息发送
- 创建Handler的工作主要是:在本线程中创建的Looper和MessageQueue关联到其成员变量中;
<a name="d02a2be55fce6a1ec6c3450a8a9bac05_h2_4"></a>
## 5.3.Message
```java
public final class Message implements Parcelable {
public int what; //消息码,消息接收端以此区分消息类型
//...
public long when; //指定执行的时间
//...
/*package*/ Message next; //内部存储下一条消息的引用
public static final Object sPoolSync = new Object(); //消息池的锁
private static Message sPool; //消息池
private static int sPoolSize = 0; //当前消息池的大小
private static final int MAX_POOL_SIZE = 50; //消息池最大值
//...
Handler target; //是一个Handler
Runnable callback;
Message next; //是一个Message。MessageQueue的数据结构是一个单向链表,
next用来保存MessageQueue链表中的下一个Message
//...
/**
* 获取消息的通用方法 相当于从sPool中取出消息池头部的消息
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool; //指向消息池头部
sPool = m.next; //消息池头部指向下一条消息
m.next = null; //当前要处理的消息池
m.flags = 0; // clear in-use flag
sPoolSize--; //消息池大小-1
return m; //返回消息池的消息
}
}
return new Message();
}
//...
/*
* 推荐使用obtain创建消息对象
*/
public Message() {
}
//...
//可序列化 保证Message可以通过Intent或者Binder远程传递
public static final @android.annotation.NonNull Parcelable.Creator<Message> CREATOR
= new Parcelable.Creator<Message>() {
public Message createFromParcel(Parcel source) {
Message msg = Message.obtain();
msg.readFromParcel(source);
return msg;
}
public Message[] newArray(int size) {
return new Message[size];
}
};
public int describeContents() {
return 0;
}
//...
//消息池的初始化 使用消息的一方,只需要调用该方法便可以把废弃的消息放入消息池中重新利用
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this; //将当前消息加入消息池头部
sPoolSize++; //消息池当前消息树加1
}
}
}
}
5.4.消息的发送过程
5.4.1.Java层发送消息
- post系列方法:接收的是Runnable的参数,Runnable是个接口,需要传入具体的实现类;
- send系列方法:接收的是Message参数;
- 两组方法的本质相同,post方法内部还是调用了send方法,只是把传入的Runnable参数存入Message的callback成员变量中; ```java //发送需要立即处理的消息 public final boolean sendMessage(@NonNull Message msg) { return sendMessageDelayed(msg, 0); }
//delayMillis:相对当前的时间多久后处理该消息。 //用于发送相对于当前时间多久后需要处理的消息 public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
//uptimeMillis:表示在何时处理该消息 //用于发送在指定的绝对值时间需要处理的消息 public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { MessageQueue queue = mQueue;//获取looper存储的MessageQueue if (queue == null) { RuntimeException e = new RuntimeException( this + “ sendMessageAtTime() called with no mQueue”); Log.w(“Looper”, e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { //指定处理消息的目标端,this表示由当前Handler处理 msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis); //消息入列
}
- handler的send方法最终调用MessageQueue的enqueueMessage方法
- Runnable是一个接口,不是一个线程
> - The runnable will be run on the thread to which this handler is attached;
> - 通过demo验证,跟上方注释吻合;
> - 既是说,这个开启的runnable会在这个handler所依附线程中运行;
> - postDelayed(new Runnable()) 而没有重新生成新的 New Thread()
<a name="d24f6e76cd32eb22bcab6c4bc72b3830_h3_6"></a>
### 5.4.2.MessageQueue的enqueueMessage方法
```java
//MessageQueue类中的成员变量
// True if the message queue can be quit.
//表示MessageQueue是否允许退出
//mQuitAllowed表示MessageQueue是否允许退出,系统创建的UI线程的MessageQueue是不允许的,
其他客户端代码创建的都是允许的;
private final boolean mQuitAllowed;
//mPtr是native代码相关的,指向C/C++代码中的某些对象(指针)
private long mPtr; // used by native code
Message mMessages; //表示消息队列的头Head
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
//mQuitting表示当前队列是否处于正在退出状态
private boolean mQuitting;
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
//翻译:表明next()调用是否被block在timeout不为0的pollOnce上;
private boolean mBlocked;
// The next barrier token.
//翻译:下一个barrier token
private int mNextBarrierToken;
boolean enqueueMessage(Message msg, long when) {
//由MessageQueue的enqueueMessage方法入列的消息必须指定target
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
//mQuitting若为true:表示消息处理的目标端Handler所在的线程已经异常退出
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
//记录消息何时开始处理
msg.when = when;
//mMessages消息队列头部,即要当前处理的消息,p相当于工作指针,指向消息头部
//涉及的链表数据结构的知识单独作为一小结总结出来
Message p = mMessages;
boolean needWake;
//对应三种情况:
1.消息队列为空,
2.新消息需要立刻处理
3.新消息处理时间早于消息队列头部消息的处理时间
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
//消息队列的头部消息后移
msg.next = p;
//将新消息插入消息队列头部,这样可以立即处理
mMessages = msg;
//mBlocked默认为false,表示消息处理线程是否block,消息头部加入新消息,如果
消息处理线程阻塞住,需要唤醒它处理消息
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
//对应第4种情况,新消息处理时间晚于消息队列头部消息的处理时间
//如果当前线程是休眠的,即mBlocked为true,且队头是屏障,并且当前消息是最早的一条异步消息,就要唤醒线程(这一点没搞明白,如何保证会执行下方for循环的break? 暂时不深入细节了)
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { //根据消息处理时间,检索消息应该插入消息队列哪个位置
prev = p;
p = p.next;
//循环退出条件是到达消息队列末尾或在消息队列中 找到一个处理时间大于新消息处理时间的消息节点
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;//异步消息不需要唤醒消息处理线程
}
}
//找到新消息的插入位置
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
//假设需要唤醒消息处理线程,这里还需要执行nativeWake
nativeWake(mPtr);
}
}
return true;
}
- mMessage:MessageQueue内部的成员变量,是Message类型的对象,其内部的next成员变量引用了下一条Message。Message在消息队列中是按照消息执行时间(when)排列的;
- 消息入队前对应以下四种处理情况:
- 当p == null,即当前消息队列为null,新加入的消息必然存入消息队列头部;
- 当when = 0,即需要立即处理,此时应该加入消息队列头部;
- 当when < p.when,即新加入消息的处理时间要早于消息队列头部消息的处理时间,此时新加入消息也需要立即处理,仍然需要把新消息加入消息队列头部;
- 不满足以上三种情况时,新加入的消息处理时间要晚于消息队列头部消息的处理时间,因此需要遍历消息队列,找到新消息的插入位置。即类似链表的添加元素功能。
- 将新消息加入消息队列只是enqueueMessage方法的第一步,如果新消息加入到消息队列头部,且此时处理消息的线程处于block状态,则需要调用nativeWake方法唤醒消息处理线程;
- nativeWake是一个Native方法(代码略),具体代码目前自己看不懂(欠缺c/c++/jni相关的知识),该方法最终调用了JNI层Looper对象的wake方法(代码略)。通过write系统调用管道写入“W”字符串,这样处理消息的线程因为I/O事件被唤醒。
- 综上所述,消息插入就是其加入链表的时候按时间顺序从小到大排序,然后判断是否需要唤醒。如果需要唤醒则调用
nativeWake(mPtr)
来唤醒之前等待的线程; messageQueue中的元素是按序按时间先后插入的(先执行的在前)。
5.4.3.Native层发送消息
与Java层消息发送机制的区别是:在Native层由Looper(C++)发送消息,略(这里不做深入,知识盲区,嘿嘿)。
- Native层的唤醒机制与Java层是一样的,都是向管道中写入“W”;
消息发送以后,Looper线程在loop()循环中便可以读取消息,接下来分析loop方法。
5.5.Looper线程循环阶段
即分析Looper.loop()方法,源码如下:
public static void loop() {
//获取线程局部变量中存储的Looper对象
final Looper me = myLooper();
//调用Loop方法前必须首先调用prepare方法
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//获取Looper中存储的MessageQueue
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
//在IPCThreadState中记录当前线程所属的PID和UID
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//...
//在无限循环中轮询MessageQueue
for (;;) {
//可能阻塞
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
//无消息时,消息队列退出
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
//...
//将读取的消息发送到消息处理器
msg.target.dispatchMessage(msg);
//...
//将处理过的消息重新初始化并放在消息池中
msg.recycleUnchecked();
}
}
Looper的主要工作
-
5.5.2.循环监听消息(MessageQueue的next)
即分析MessageQueue的next方法 ```java Message next() {
//mPtr是native代码相关的,指向C/C++代码中的某些对象(指针),分析略(知识欠缺)
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//IdleHandler的数量
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//空闲等待时间
int nextPollTimeoutMillis = 0;for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//调用native方法,传入NativeMessageQueue的地址
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
//屏障掉后面的普通消息 (不会屏障异步消息)
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
//未到消息处理时间,设置等待时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
//不阻塞,即消息需要立即处理
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
//消息设置已使用标记
msg.markInUse();
return msg;
}
} else {
// No more messages.
//无消息
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
//消息队列中无消息或者当前消息未到处理时间时获取IdleHandler数量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//未注册IdleHandler时,设置当前线程blocked状态为true
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
//空闲消息处理,待分析
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
- next方法所做的工作分为三部分
- 调用nativePollOnce
- 从消息队列中获取一个消息
- 当消息队列中无消息或者当前消息未到处理时间时,获取控件处理器IdleHandler数量,此时将进入空闲消息处理阶段;
- 从消息队列中获取消息返回的过程很简单,消息返回后进入消息处理阶段,这部分内容连同空闲消息处理都放在消息处理一节分析;
<a name="031754226e8c8733d28ff628c27f6434_h3_10"></a>
### 5.5.3.nativePollOnce
- 消息监控的核心实现;
- native层方法;
- 表明所有消息的处理已完成, 线程正在等待下一个消息;
> `nativePollOnce` 方法用于“等待”, 直到下一条消息可用为止. 如果在此调用期间花费的时间很长, 则您的主线程没有实际工作要做, 而是等待下一个事件处理.无需担心.
<a name="b3125980e0a11c31b16b4a34e951de2b_h3_11"></a>
### 5.5.4.分发消息到处理器
- 消息的处理由Hnadler及其子类完成,首先分析Handler
```java
public void dispatchMessage(@NonNull Message msg) {
//msg.callback:Message类中的成员变量,是一个Runnable
//首先匹配消息中指定的回调方法
if (msg.callback != null) {
//分析一
handleCallback(msg);
} else {
//然后匹配创建Handler时指定的回调方法 分析二
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//最后匹配Handler的handleMessage方法
handleMessage(msg);
}
}
//分析一
private static void handleCallback(Message message) {
//即调用Runnable的run方法
message.callback.run();
}
//分析二
public Handler(@Nullable Callback callback, boolean async) {
//...
mLooper = Looper.myLooper();
//...
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
//Callback
public interface Callback {
boolean handleMessage(@NonNull Message msg);
}
java层的消息处理由dispatchMessage方法完成,该方法最多会做三次匹配工作,匹配成功后,将消息转发给相应的方法处理。
在线程消息处理机制中,有一类特殊情况:当线程处于空闲状态时如何处理消息;
- 线程空闲状态指的是线程满足以下两种状态:
- 线程的消息队列为空
- 消息队列头部的处理时间未到
- 当线程进入空闲状态时,需要执行空闲处理函数。空闲处理函数由IdleHandler指定;
简单点说就是:next方法取出下一个Message(从头部取),如果没有Message可以处理,就可以处理下IdleHandler。
1.IdleHandler
MessageQueue内部的接口
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
//简单概括一下:消息队列中没有消息或者消息的处理时间还没有到,这两种情况下会调用该方法
//返回true和返回false的区别
//true:只要线程是idle状态,会一直触发该回调
boolean queueIdle();
}
- 空闲函数queueIdle的处理由IdleHandler的实现类实现
- 空闲消息处理位于MessageQueue的next方法中 ```java
private final ArrayList
Message next() {
//...
//仅当第一次循环时为-1
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//...
for (;;) {
//...
synchronized (this) {
//...
if (msg != null) {
if (now < msg.when) {
//...
//消息处理时间未到,将进入空闲状态
} else {
//...
//消息需要处理,直接返回,不进入空闲状态
}
} else {
//消息为空,进入空闲状态
//...
}
//...
//如果走到这里说明没有消息可以分发的,下一个for循环就要进入休眠了
//空闲处理,对应消息为空或者未到消息处理时间
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//如果IdleHandler的数量为0,线程要阻塞
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;//继续for循环,不进入空闲状态
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
//将mIdleHandlers转化为数组形式
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
// 减少引用数量,防止内存泄漏
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
//执行空闲处理函数
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
//keep表示是否保留已执行的线程处理函数,默认不保留
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler); //执行后移除
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
//重置线程处理函数数量
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
//空闲处理状态处理完毕,此时可能已有新消息
nextPollTimeoutMillis = 0;
}
}
<a name="1d69c41961e39fc843d57b9c73044aa4_h4_1"></a>
#### 2.Idle的具体场景(用法)
- 以ActivityThread为例分析IdleHandler【FremeWork层使用到了IdleHandler的地方】
```java
public final class ActivityThread extends ClientTransactionHandler {
...
final GcIdler mGcIdler = new GcIdler();
...
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
//在该方法中做一些事情
doGcIfNeeded(); //空闲时判断是否GC 触发了一次gc
purgePendingResources();
return false; //表示queueIdle回调是一次性的
}
}
//将GcIdler添加到当前线程的消息队列中
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
Looper.myQueue().addIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
//从当前线程的消息队列中移除GcIdler
void unscheduleGcIdler() {
if (mGcIdlerScheduled) {
mGcIdlerScheduled = false;
Looper.myQueue().removeIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
}
空闲消息处理函数用于在线程暂时无消息处理时做一些辅助工作,其重要应用之一是在GcIdler中完成空闲时内存垃圾回收;另外一个重要作用是,在Home启动后进入空闲状态时发送BOOT_COMPLETED广播。
3.IdleHandler的适用场景
延迟执行(在app启动的时候会执行一些延迟任务-使用Handler实现):把一些不怎么紧急的事情进行延迟加载,通过IdleHandler实现-即启动相关的性能优化;
批量任务(两个特点:任务密集、只关注最终结果)
线程间通讯
- 处理线程切换(最核心的作用)
- ===========================
- a. 将一个任务切换到某个指定的线程中去;
- b. 如果没有Handler,我们的确没有方法将访问的UI切换到主线程中去执行;
- ===========================
-
2.系统为什么不允许在子线程中更新UI
Android的UI控件不是线程安全的,如果在多线程并发的情况下可能导致UI控件处于不可预期的状态;
- 即便加锁,也有缺点。逻辑变复杂,效率变低;
- 单线程模型处理UI操作,简单、高效;
3.为什么UI线程不设计成线程安全的
- 如果设计成线程安全的,那么性能就会大打折扣,UI的更新有如下的特性:
- UI是具有可变性的,甚至是高频可变。
- UI对响应时间很敏感,这就要求UI操作必须要高效。
- UI组件必须批量绘制来保证效率。
所以为了保证渲染性能,UI线程不能设计成线程安全的。Android设计了Handler机制来更新UI是避免多个子线程更新UI导致的UI错乱的问题,也避免了通过加锁机制设计成线程安全的,因为那样会导致性能下降的很厉害。
4.非UI线程一定不能更新UI吗
不一定。
- 说明:我们知道在Android提供的SurfaceView、GLSurfaceView里面是都能在非UI线程更新UI的。
并且在一些特定的场景下,子线程更新View也是能更新成功的。
- 例如,下面的代码在子线程中更新界面是可以成功的: ```java import android.app.Activity; import android.os.Bundle; import android.widget.Button;
public class TestActivity extends Activity {
Button btn = null;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn = (Button) findViewById(R.id.Button01);
new TestThread(btn).start();
}
class TestThread extends Thread {
Button btn = null;
public TestThread(Button btn) {
this.btn = btn;
}
@Override
public void run() {
btn.setText("TestThread.run");
}
}
}
- 当我们深入分析其原理的时候,就可以知道,能否更新成功的关键点在于是否会触发checkThead()导致更新失败,抛出异常:
```java
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");
}
}
- 而在ViewRootImpl中,会有这些方法调用到checkThread()方法:
- 经过分析,最终可以得到,在子线程中给TextView setText 不会抛出异常的两个场景:
- 1:TextView 还没来得及加入到ViewTree中
- 2:TextView已经被加入了ViewTree,但是被设置了固定宽高,且开启了硬件加速
子线程操作View 确实不一定导致Crash,那是因为刚好满足一定的条件并没有触发checkThread机制,但这并不代表我们在开发过程中可以这么写,其实我们还是应该遵循google的建议,更新UI始终在UI线程里去做。
5.Handler是如何完成子线程和主线程间的通信
Android使用Handler实现子线程与子线程、主线程之间通信
6.Handler工作原理
关于Handler的原理需要先了解4个角色:Handler,Message,MessageQueue和Looper;
通过Looper.prepare()创建Looper,通过Looper.loop()开启消息循环;
不停地从MessageQueue中查看是否有新消息,如果有就立即处理,否则就阻塞在那里;
8.Looper会调用MessageQueue的next方法来获取新消息
a. 当没有新消息的时候,next方法会一直阻塞在那里,这也导致Looper阻塞在那里;
- b. 如果next返回了新消息,Looper就会处理这条消息;
-
9.Handler如何实现阻塞的
Java层的阻塞是通过native层的epoll监听文件描述符的写入事件来实现的;
- 消息出列检查是否需要阻塞:从MessageQueue的next方法入手,从源代码中可以分析到,将需要等待的时长传递到Linux层,实现阻塞;
- 消息入队检查是否需要唤醒:每当往队列中插入一条消息,就提醒可以去获取消息,就将needWake 标记为mBlocked,mBlocked在next中 可能会赋值为true,一旦needWake 标记为true。就会调用nativeWake(mPtr)方法,即通知底层唤醒;
10.Message的obtain涉及到了享元设计模式
- 属于性能优化方面使用较多的
内存抖动
涉及到推送的会使用;
- 根据生产消费模式,生产者有产品的时候一般情况下会唤醒消费者。那么MessageQueue入队列的时候应该会去唤醒;
- UI的工作机制-主线程的工作机制可以概况为 生产者 - 消费者 - 队列 模型。
-
12.消息入列
MessageQuese.enqueueMessage(),继续往下,nativeWake(mPtr),底层实际上用到了NDK,整个实际上在c++层;
入队,根据时间排序,当队列满的时候,阻塞,直到用户通过next方法取出消息;
当next方法被调用,通知MessageQuese可以进行消息的入列;
13.出列
当Looper.loop()启动轮寻器,对queue进行轮询。当消息达到执行时间就取出来。当MessageQuese为空的时候,队列阻塞,等消息队列调用enqueueMessage()时候,通知队列,可以取消息,停止阻塞;
14.Handler、MessageQuese、Looper、Message对应关系
Handler的构造方法中,会创建Handler所在线程的Looper;
- 线程唯一,决定Looper唯一:通过ThreadLocal保证;
- Looper唯一,决定MessageQuese唯一:查看Looper构造方法可以知道;
- Message中打包了Handler对象;
-
15.ThreadLocal
线程隔离:保证了Looper对象在线程里是唯一的;
- ThreadLocal 提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同,而同一个线程在任何时候访问这个本地变量的结果都是一致的;
推荐阅读-并发编程 | ThreadLocal 源码深入分析,对ThreadLocalMap的内部实现讲解的很好(里面的内容对本人来说有一定难度,有精力再深入);
16.prepareMainLooper
首先调用了 prepare(false) 创建了一个不可以退出的 Looper;
然后检查 MainLooper 是否已经创建,最后保存了一下 MainLooper 的引用;
17.Looper 中死循环为什么不会导致应用卡死?
这是一道经典面试题,有很多种问法,如:
- 为什么调用Looper.loop不会产生阻塞
- 为什么主线程调用死循环不会出现阻塞
- Looper.loop方法还会不会往下执行
- 阻塞的位置,为什么主线程阻塞了不会产生anr
- 先回答,Android是一个事件驱动的系统,Looper循环是不可以退出的。
- 真正卡死主线程的操作,是在生命周期回调方法 onCreate()、onStart()、onResume() 等中操作时间过长,会导致 UI 渲染掉帧,甚至 ANR。
- Android是依靠事件驱动的,不同于Java程序,Java程序是单一的,执行完,进程就退出了。Android通过Loop.loop()不断进行消息循环,一旦退出消息循环,应用也就退出了;
- 如果仅仅使用死循环会一直占用 CPU,导致 CPU 一直处于工作状态。即使不会造成应用卡死,也会十分耗电。而事实上 loop() 中的死循环在没有消息的情况下是处于休眠状态的,并没有一直在运行;
- a. MessageQueue.next() 方法调用了 native 方法 nativePollOnce(),此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生时唤醒主线程;
- b. 原来这里采用的是 epoll 机制,消息到达时通过往 pipe 管道写端写入数据来唤醒主线程工作;
18.Handler引发的内存泄漏问题
即使用Handler为什么会造成内存泄漏
-
20.如何保证一个线程只有一个Looper
通过ThreadLocal将Looper对象存到Thread中的map中;
当第二次调用的时候,就从Thread的map中去找,如果之前有存的情况下,就抛出异常;
七.Handler问题(二)
部分问题需要再次温习视频学习 Looper的副业 ThreadLocal原理
1.线程的消息队列是怎么创建的
1.1.可以在子线程创建handler吗
- 1.2.主线程的Looper和子线程的Looper有什么区别
- 1.3.Looper和MessageQueue有什么关系
- 1.4.MessageQueue是怎么创建的(这个涉及到底层原理)
- 子线程若需要创建一个Handler,需要先调用Looper的perpare方法,否则会抛出异常;
- 主线程的Looper不可以退出,子线程的Looper可以退出;
- Java层Looper里面包含一个MessageQueue,它们是一一对应关系。但Native层里面MessageQueue是包含一个Looper,也是一一对应关系;
- Java层MessageQueue对象创建的时候会调用nativeInit函数,即创建Native层的MessageQueue。而Native层的MessageQueue在创建的时候会创建一个Looper,Looper会创建一个EventFd,并且添加一个可读事件到epoll里面;
2.说说Android线程间消息传递机制
- 考察点
- 2.1.消息循环过程是怎样的?
- 2.2.消息是怎么发送的?
2.3.消息是怎么处理的?
- Loop.loop的原理:
- 拿到当前线程的Looper,然后拿到Looper中的MessageQueue,然后就是for循环中不断地在消息队列中取消息,如果队列中没有消息,则取消息的函数会进行阻塞。如果返回的结果是null,说明Looper结束了,如果取到了消息,则会分发消息,用msg的target对象进行分发,该target是一个handler,分发完之后就会回收消息(消息标记为可用,属性重置,若消息池的数量未超过50,则塞入到单链表中)。
- 重点:如何从messageQueue中获取下一条消息/如何分发message
- Handler的hook技术
- 在for循环中,会先去执行nativePollOnce方法
- 转发给了NativeMessageQueue对象的pollOnce,pollOnce:转发给了Looper的pollOnce。Looper的pollOnce函数,又是一个for循环,分析pollnner。
- pollnner:整个消息循环的核心,分析epoll_wait,有几种情况该方法会返回,出错了/超时了(该时间段一直没有事件发生)/有事件发生(会在for循环中依次处理epollEvents,每一个事件对应一个fd,如果fd就是我们关注的内容-mWakeEventFd,而且事件是读事件,就调用awoken,这样的话,pollInner就可以返回了);
- 返回之后会执行next函数
- 消息循环的整个过程就是在for循环中不断地调next,从消息队列中取下一条消息,然后把消息分发给对应的handler。不断地从for循环中nativePollOnce,看有没有事件。如果别的线程往当前线程丢消息时,nativePollOnce方法就返回了,当前线程就被唤醒了,然后检查消息队列中有没有消息可以处理。
- 从单链表的表头取了一条消息
- 另一个线程怎么往该线程消息队列发送消息?
- 通过send/post方法,最终会调用MessageQueue的enqueueMessage方法,给消息插入到消息队列里,然后调用nativeWake方法。
- nativeWake,首先,拿到NativeMessageQueue对象,然后调用了对象的wake函数,wake调用到了NativeMessageQueue的Looper的wake函数(往mWakeEventFd[计速器]里面写了一个东西,eventfd就可以收到可读事件了)。
- 另外一个线程往当前线程消息队列发送消息之后,当前线程该怎么处理消息呢?如何唤醒,分发消息?
- 分发:三种情况
- 一,消息自带的callback是否非空,非空则执行callback的run方法,callback设置的实际,通过post方法发送消息时设置的。
- 二,创建handler对象时,是否在handler构造函数中设置了回调方法,如果设置了则回调对应的方法;该回调的返回值类型是boolean,一些hook技术通过改动该方法的返回值,
- 三,既没有通过post发送消息,也没有在handler的构造函数中指定回调方法,则消息的处理会调用handleMessge方法;
- 分发:三种情况
- Loop.loop的原理:
回答方式
注意三点:
- 消息怎么发送的,讲清楚handler.sendMessage的原理即可。
- 消息是怎么进行消息循环的,对应Looper.loop函数。
- 怎么处理消息分发的,handler.dispatchMessage。
3.Handler消息延时是如何实现的?
- 消息延时是做了什么特殊处理吗?
- 是发送消息延时了,还是消息处理延时了?
- 延时精度怎么样?
- 当前时间 + 目标延迟时间
- 消息触发时间的排序,按时间顺序来插的
- 延时处理消息
消息延时实现原理总结:
- 消息队列按消息触发时间排序
- 设置epoll_wait的超时时间,使其在特定时间唤醒
- 关于延时精度(不高,只能实现大概的延时,epoll_wait的超时时间明显是不精确的,还有的消息队列里面有的消息就是处理的时候可能会非常耗时导致后面的消息延时处理了)
4.说说idleHandler的原理
- 问该问题的出发点:一些app在做启动相关的性能优化时会涉及到idleHandler机制。
三个考察点
了解idleHandler的作用以及调用方式 了解idleHandler有哪些使用场景 熟悉idleHandler的使用原理
处理时机:nativePollOnce方法返回(三种情况-出错、超时、有消息)了之后,并且没有消息可以分发的时候;
如何回答
- 1.用途
- 2.说一下其实现原理(如何触发的),回调返回的结果的意义;
- 3.如果可以的话,就结合自己的项目来说一下;
用法 ```java Looper.myQueue().addIdleHandler(New MessageQueue.IdleHandler(){ //重写queueIdle方法 return true(一直能触发回调,反之只触发一次) })
Public void addIdleHandler(IdleHandler handler){ synchronized(this){ mIdleHandlers.add(handler) } } ```
5.应用主线程进入loop循环,为什么没有ANR?
- 考察点
- 了解ANR触发的原理
- 了解应用大致启动流程(主线程是怎么启动looper循环的)
- 了解线程的消息循环机制(重点)
- 了解应用和系统服务的通信过程
应用发生ANR,系统会弹出一个dialog,该dialog是从AMS中弹的,AMS在SystemService进程,会调用appNotResponding方法。
- appNotResponding调用的时机
ANR的场景
Service TimeOut BroadCastQueue TimeOut ContentProvider TimeOut InputDispatching TimeOut
回答题目(总结,答出以下三点就差不多)
- ANR是应用没有在规定的时间内完成AMS指定的任务导致的;
- AMS请求调到应用端Binder线程,再丢消息去唤醒主线程来处理;
- ANR不是因为主线程loop循环,而是因为主线程中有耗时任务;
Service ANR源码分析
补充:消息分为3种,消息屏障,普通消息,异步消息(同普通消息没有区别,只是设置了一些标志位)
用来给别的消息添堵的
如何往消息队列中插一个屏障
通过postSyncBarrier(用来插入一个消息屏障)方法
- 关于消息屏障的6个注意点
- 消息没有handler
- 消息屏障也带有消息戳,插入消息队列的时候也是按照时间来排序的,而且只会影响其后面的消息
- 消息队列可以插多个消息屏障
- 消息插到消息队列之后是没有消息唤醒线程的
- 插入消息屏障后,会返回一个token(消息队列中撤出消息屏障需要使用该值,删除屏障的时候可能需要去去唤醒线程),即消息屏障的序列号(凭token在消息队列中移除对应的消息屏障)
- postSyncBarrier的调用只能通过反射
- 关于消息屏障的6个注意点
删除屏障:在移除屏障的时候,如果线程是因为屏障导致被阻塞住的情况下,此时需要去唤醒线程;
- 屏障(阻塞住屏障后的普通消息)是如何处理的(是在MessageQueue的next函数中)
- 如果第一条消息就是屏障,就往后遍历,看看有没有异步消息;
- 如果没有就无线休眠,等待被唤醒;
- 如果有,就看离这个消息触发事件还有多久,设置一个超时,继续休眠;
- 插入消息的时候,有屏障的情况
- 新消息插入到队头,如果当前线程是休眠的,就需要唤醒;
- 如果没有插入到队头,如果当前线程是休眠的,并且队头是屏障,且当前消息是最早的一条异步消息,就要唤醒线程;
注意(对应下面问题2):场景,处理完了一条消息,然后再去取下一条消息,结果一看,没有消息可以处理,这个时候处理IdleHandler,处理之后,检查一下消息队列(原因:因为在处理IdleHandler的过程中,可能有新消息过来了),如果检查之后还是没有消息,那就回去休眠。如果此时被唤醒了,检查消息队列还是没有消息,此时不会重复调用IdleHandler的。
问题: 1.消息队列是空的时候,插入一个消息屏障,会不会触发IdleHandler吗
不会(因为线程还在休眠) 2.如果删除了屏障,消息队列空了,会触发IdleHandler吗 不会,IdleHandler已经调用过了 3.如果消息队列只有一个消息屏障,插入一个普通消息会触发IdleHandler吗 有可能,(IdleHandler的处理时机是)消息队列是空或者第一条消息的处理时间还没有到关键点在于屏障的时间是否到了
4.如果消息队列只有一个消息屏障,插入一个异步消息会触发IdleHandler吗 有可能,消息队列是空或者第一条消息的处理时间还没有到 关键点在于屏障的时间是否到了
总结
插到消息队列之后,会block住这个屏障后的所有普通消息,让这些普通消息先别处理。目的:为异步消息开绿色通道,让异步消息优先执行,FrameWork层使用到屏障的地方不多,比如说界面绘制(scheduleTraversals方法中使用了屏障),输入事件,事件分发等等,这些消息都是比较紧急的,不能被普通消息耽误了。
7.怎么跨进程传递大图片
- 跨进程传递大图片
考察点
- 了解各种跨进程传递数据的方式及各自优缺点
- 了解TransactionTooLargeException的触发原因和底层机制
- 了解bitmap底层的传输原理
跨进程传大图,有哪些方案
- 给图片保存到固定的地方,传key给对象
- 比较典型的:图片缓存,但图片缓存是针对同一个进程。那么,换一种处理方式,将图片的路径作为key保存在文件里面。当然也会有问题,写入到文件和从文件中读取都是耗时的,所以,性能就非常差。
- 通过IPC的方式转发图片数据,另外一个进程拿到图片之后再去加载图片。
- 这种方案没有经过文件系统,完全是在内存中处理。但是呢,在内存里面也可能涉及多次拷贝。
- 给图片保存到固定的地方,传key给对象
IPC传图方式
- Binder(性能不错,但有大小限制)
- Socket、管道(至少有2次拷贝,性能不太好,同时也有大小限制-数据量如果超了,则需要进行分包)
- 共享内存(性能不错)
注意事项,IPC传大图需要注意的两个点:1,性能,减少拷贝次数。2,内存泄漏,资源及时关闭。
TransactionTooLargeException(事物过于巨大异常)
事务:client端向service端发起binder调用,一直到调用结束的过程。 关于该异常需要了解的三点。 发出去的或者返回的数据量过大;
- 跨进程通信是需要buffer的,发数据和回数据都需要buffer,buffer只有到事务结束才释放的。如果发数据占用很多buffer的空间,那么留给对方回数据的空间就很小了。如果申请buffer失败了,那么这个事务就失败了,上层就收到异常。
Binder缓存用于该进程所有正在进程中的Binder事务
- 进程在启动Binder机制的时候会映射一块内存(大小是1M),接下来跨进程通信,申请缓冲区不能超过1M(所有Binder共享1M),如果一个事务申请过多的话,其它的事务申请的就很少了,尽量别跑起多个数据量大的事务。
大数据量打碎分批发,或(先发部分)按需发
Bitmap如何写入到parcel,略(native层的源码比较多,待对C++有更深入的学习再做复习)。
- 如何回答这道题目
- 1.图片写到文件,路径传递到另一个进程,再读出来;
- 2.intent传图,但是容易抛异常,原因是什么;
- 肯定是因为intent带bitmap的时候,bitmap直接拷到parcel缓冲区了,没有利用这个ashmem;
- 3.binder调用传图【比较可行的方案】,底层ashmem机制(能说则说);
- 为什么在组件间通信会把AllowFd的机制给禁用了(高端问题了,视频中的老师说不清楚,面试时若面试官装逼就用这个问题反问)
如果传递的不是图片,而是数据列表,可以使用ContentProvider和MemoryFile,这两者底层都使用到了共享内存。
8.说一说ThreadLocal的原理
考察
ThreadLocal适用于什么场景 ThreadLocal使用方式是怎样的 ThreadLocal实现原理是怎样的
FrameWork层使用到了ThreadLocal的地方
- Looper
- Looper中的静态类
- 屏幕绘制
- 获取Choreographer
- Looper
回答
- 每个线程里面都有一个Thread对象,Thread对象里面都有一张表(其实是一个数组),该表跟HashMap不同,key和value都存在数组中,key对应的是WeakReference
,value对应的如Looper对象,一个应用里面可以定义多个ThreadLocal,每一个都有自己的哈希值(哈希值计算方式略); 问题:如何解决hash冲突的 计算出index(hash值跟数组的size取余得到)之后,从当前index开始往下遍历,哪个是空的就存到哪儿。
- 每个线程里面都有一个Thread对象,Thread对象里面都有一张表(其实是一个数组),该表跟HashMap不同,key和value都存在数组中,key对应的是WeakReference
回答方式(4个点最好都答到)
值得再次重学
- 【需要先梳理源码(在native层LooperLLpollInner方法中)】通过Looper调用addFd函数—->对应Java层MessageQueue的api是addOnFileDescriptorEventListener;
- FrameWork应用到Looper副业的地方(貌似没有看到,Native层用的比较多-vsync机制【SurfaceFlinger如何通知应用进程有vsync信号】)
- demo示例(内容有点多,需要时自行百度或重新翻看视频)
总结(知识点)
检测工具
FrameWork自带的机制:WatchDog,主要检查SystemService,这些系统服务它们是不是正常(是不是死锁,工作线程还能否处理新的消息等等)的; BlockCanary:检查线程是否耗时任务;
WatchDog
- 检查是否发生了死锁
- 检查线程是否被任务Blocked
- 关于WatchDog有两点
- 支持多目标检测(可以同时检测多个线程,多个锁)
- 本身就是一个线程
WatchDog核心变量:mHandlerCheckers(每一个都对应一个Monitor,对应一个Thread)
BlockCanary
一个线程发送一个消息到另一个线程,且没有返回
- 使用场景:
- 关联到前面的知识点,后续再重新学习一遍
八.Handler问题(三)
1.描述一下什么是epoll机制