本篇文章主要目的是 搞清楚 Handler原理,以及面试中问道的一些问题。
1:Handler、 message、Looper和MessageQueue的原理以及他们之间的关系?
2:可以在非UI线程可以使用Handler吗?
3:Loop 既然是一个死循环,那么在主线程为什么不会造成ANR?
1、Handler 是Android 进行线程间通讯的主要方法
通常我们会在代码中这样使用Handler
// Handler 基本使用 子线程 和 主线程见通讯
final Handler hanler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 主线程 进行UI操作
switch (msg.what) {
case 1: {
}
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
Message message = new Message();
Message message1 = Message.obtain(); // 推荐使用obtain 减少内存申请
message.obj = "";
message.what = 1;
hanler.sendMessage(message);
}
});
new Thread(new Runnable() {
@Override
public void run() {
hanler.post(new Runnable() {
@Override
public void run() {
// 可以直接进行UI 的操作
}
});
}
});
sendMessage(msg) 方法是把消息放入队列,可以直接点击源码查看
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
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(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
最终通过 queue enqueueMessage() 方法吧消息放入队列
那这里也就知道 Message 和 MessageQueue 之间关系 , Message 是消息,MessageQueue 是消息队列,从名字上就观察出,那么MessageQueue 真的是队列吗? 肯定不是,犹记得背诵面试题的时候,MessageQueue 采用的数据结构是单向链表数据结构而非队列。它的next()方法指向下一个Message元素
发散下思维,这里解释下单向链表链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是在每一个节点(数据存储单元)里存放下一个节点的位置信息(即地址)。
回到MessageQeue,我知道它是一个单向链表结构就可以,关于消息的存入到此为止,主要需要我们关心的是怎么出队列的。从我们背诵的概念,知道Looper 帮助 message 出队列,而Looper是死循环,会不停的从消息队列中取消息。
如果想搞清楚原理,流程 请仔细查看我所贴的源码,因为我一般都把解释接在源码中间。源码有删减
// 这里是Message 出队列的过程,为了方便写注释,我直接CV出来
boolean enqueueMessage(Message msg, long when) {
// ... 省略一些检查代码
synchronized (this) {
// ... 省略一些检查代码
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
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.
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(mPtr); // 唤醒 nativePollOnce() 下方有解释
}
}
return true;
}
Message next() {
// ...
int nextPollTimeoutMillis = 0;
/*
* 关注下nextPollTimeoutMillis 这个值,被nativePollOnce() 方法使用,
* 而nativePollOnce() 是一个native方法,实际作用是通过native层的MeaageQeue阻塞当前调用栈线程,
* 那么
* nextPollTimeoutMillis : 小于零 一直阻塞,直到被唤醒
* 等于零 不会阻塞
* 大于零 最长阻塞时间为 nextPollTimeoutMillis ,期间如被唤醒会立即返回
* */
for (;;) {
// 无限循环
// ...
nativePollOnce(ptr, nextPollTimeoutMillis); // 阻塞 被MessageQeue #enqueueMessage()方法中 nativeWake() 方法唤醒
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;
}
//...
}
到这里,我们总结下以上我要说明的问题和关系。 Handler 发送 message ,message 到 MessageQeue 进行存储,
所使用的的两个主要方法 MessageQeue# enqueueMessage() 进入队列,MessageQeue # next() 轮寻消息出队列 一切都清楚了,那么好像没有Looper什么事情了? 哈哈,我看到这里也迷糊了,不是说轮寻是Looper的事情吗?怎么MessageQeue就把事情给干了呢,不过仔细想一想,方法是next 那么是谁调用的呢?这里我没有寻找源码,而是交给了百度。得知Looper调用MessageQeue 的next() 方法,
还记得背诵的概念吗? 子线程使用Handler 首先需要Looper.prepare() 创建一个looper。那么我就从这里进入源码,我们也知道Looper的最主要方法就是loop() , 所以我们直接查看loop方法就可以
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
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();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
for (;;) {
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//进入这个方法,我第一眼就看到这个方法,死循环调用 queue.next()
// 后面还有 为了篇幅 不贴了,你们可以自行查看下
}
我整理下这个方法: 进入首先 获取Looper 对象, 为空就报错, 然后进入死循环 走MessageQueue # next()方法,获取消息,消息为空 就退出消息,不为空,分发消息 dispatchMessage()
那么现在可以回答第一个问题了,
回答 1:
Handler 、 message、 Looper、 Messagequeue 组成Android 的消息机制。
首先Handler 发送 message 到 Messagequeue ,MessageQueue 存储消息,等待Looper 轮寻器调用 next()方法,获取消息,调用 dispatchMessage()方法, 这个方法中其实就一个 message.callback.run 然后我们就开始根据what 获取自己需要的消息,执行我们的逻辑。
回答 2:
从我们背诵的概念就可以知道,非UI线程可以执行Handler操作,但是需要获取Looper对象,而前文也说明了,我们查看Looper . loop()方法,就是在子线程中执行 Looper.prepare() 获取对象
衍生问题,为什么在主线程中不需要获取Looper对象,就可以使用Handler了呢?
从问题1,我知道Looper是为了调用MessageQueue的next方法,获取message ,如果没有没有Looper对象是肯定不能调用。毫无疑问会发生空指针,如果在子线程不使用 Looper.prepare() 方法,直接使用Handler 抛出:
"No Looper; Looper.prepare() wasn't called on this thread."可以证明
那么假设,在主线程肯定是系统已经创建了Looper 对象,所以不用我们再去创建。那是怎么创建的呢?还记得之前查看Loop() 方法中的 final Looper me = myLooper();
这里贴的都是源码: 主要确认 Looper 对象怎么获取的。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
发现是 sThreadLoacl 对象中获取的Looper对象 继续进入查看
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
看着像是获取当前线程,从map中获取。到这里我已经看不下去,这要是从头开始查,就太多了 ~~~陷入沉思
ThreadLocal 这个类好像似曾相识。百度下、小猿笔记
一个线程的局部变量,每条线程只能看到自己的值,并不知道其他线程中业存在该变量(我理解就是 每一个线程都有一个ThreadLoacl ,别的线程无法访问其他线程的ThreadLoal )
实现原理:每个Thread的对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。
OK ,大概理解了ThreadLocal 的作用,知道Looper 是set() 方法进入 ThreadLocal 中 ThreadLocalMap() 中的。那么按照我的猜测,主线程替我们创建了 Looper 对象,然后set()到主线程的ThreadlocalMap中,我们这里只是获取。
那么,我们要搞懂衍生问题,可能还需要查看主线程启动的整个过程
因为APP启动不是我们主要研究的重点,这里涉及一些知识,大家可以看下这个图。我们知道程序的运行都有一个主入口,学习java的时候,程序的主入口是 main函数,而Android 开发使用语言是java 所以理所当然 入口也Main函数啊。
所以我直接找到 ActivityThread # main 方法中
public static void main(String[] args) {
// ...
Looper.prepareMainLooper();
// ...
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//...
Looper.loop();
// ...
}
很显然,Looper. prepareMainLooper() 方法使我们需要关心的
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
prepare(false) 是不是很熟 , 子线程中 Looper. prepare() 方法 ,还记prepare()的实现吗?
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
sThreadLocal.set(new Looper(quitAllowed)); 这里不就是new一个放入了Threadlocal 的
ThreadloaclMap中了吗?
回答 问题2 衍生问题:
为什么在主线程中不需要获取Looper对象,就可以使用Handler了呢?
因为在App启动的时候 ActivityTread # Main 函数 帮我们创建了Looper 对象,所以我们使用Handler不需要再创建。(整个APP启动运行在UI线程,不需要我解释为什么Main 函数中创建的Looper 对象在 主线程的 ThreadLcal
Map 中吧。)
回答问题3:
Looper 运行既然是一个死循环,在UI线程运行为什么不会造成anr 异常。
我们再次回想一下 ANR 造成的原因:ANR,是“Application Not Responding”的缩写,即“应用程序无响应”
主线程处理屏幕时间,也就是在Activity中, 5秒无响应
主线程在执行BroadcastRecetive 的 onRreceive 中 10秒无响应
主线程执行服务各个生命周期20秒无响应
显然:提问这个问题的面试官是在给你挖一个陷阱, ANR出现的原因 是无响应, 避免anr 我就要避免在主线程进行耗时操作。那么接下来直接问为什么Looper 都死循环了,程序没有造成anr ,我的脑袋直接宕机 .。 对App 运行 、 Handler原理 不熟悉,这个问题就死定了。
我先确定一个概念,什么是Android ANR 什么是无响应。 你怎么知道一个事件无响应,那就是主线程在处理事件,5秒内来不及处理你触发的事件。如果在在主线程睡眠 10秒, 但是此期间,不触发任何事件,会报 ANR吗?肯定不会报的。
为什么呢? 那么我想问下各位,在学习 java的时候,例如main 方法执行一个计算,控制台输出,运行程序,执行完毕,你想再次计算怎么办?再次运行程序。而 APP呢? 为啥执行完Main 函数,程序没有退出呢?
Looper.loop() 死循环保证App 不退出。
再次回想 我们看Message#next()方法中的nativePollOnce() 如果没有消息,程序一直处于堵塞状态!一直到 被另一个消息唤醒或者系统唤醒~
说到这里是不是就清楚了,
主线程一直处于堵塞状态,主线程运行的looper 一直处于运行状态,ANR 是事件无响应, 你压根不给我事件唤醒我,我为什么要报anr, 你给我事件了,那我就唤醒了啊,然后给你麻溜的处理了。为啥要报anr。
面试第四问:handler的消息排序~
回答:这里只是记录下吧,群里的小伙伴提供一个问题,就是handler 的排序问题,刚开始听到这个问题,满脑子不解,消息存在排序吗?不是单向链表结构,next指向下一个消息地址。为什么存在呢!但是别说,潜意识里 貌似记得面试的过程遇到过这样的问题。
那么我们继续看源码这次简单点
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
发现sendMessage 中单调用 sendMessageDelayed() 这个方法是我们发送延迟消息的。那么首先可以确定如果存在排序 排序的 根据是 时间
其中SystemClock.uptimeMillis() App 运行到目前的时间间隔
boolean enqueueMessage(Message msg, long when) {
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
boolean needWake;
msg.next = p;
// New head, wake up the event queue if blocked.
mMessages = msg;
// Inserted within the middle of the queue. Usually we don't have to wake
needWake = mBlocked;
} else {
// and the message is the earliest asynchronous message in the queue.
// up the event queue unless there is a barrier at the head of the queue
p = p.next;
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
msg.next = p; // invariant: p == prev.next
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
一路跟着时间元素, when 这里果然出现了排序
细心的朋友会发现mMessages这个对象,我们可以把它理解为是待执行的message队列,该队列是按照when的时间排序的且第一个消息是最先执行。
首先先看真对when的判断 三个条件,如果mMessages对象为空,或者when为0也就是立刻执行,或者新消息的when时间比mMessages队列的when时间还要早,符合以上一个条件就把新的msg插到mMessages的前面 并把next指向它,也就是msg会插进上图中队列的最前面,等待loop的轮询。
如果上面的条件都不符合就进入else代码中,when < p.when when是新消息的执行时间,p.when的是队列中message消息的执行时间,如果找到比新的message还要晚执行的消息,就执行
简单的比对时间 ,插入MessageQueue 中, 这也可以解释为什么 MessageQueue 采用的数据结构命名是链表,为啥命名成队列。按照时间顺序,先进先出~
OK完结~ Hndler原理, 外加三问, 都是之前面试中遇到的。历时一天,终于整理完毕,脑子中回想一下面试官问我handler原理:我会怎么回答~貌似还没有背诵原理说出来的多~哎