Android-Framework:Handler全解析,看完这篇还不懂请给我寄刀片

}
}

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can’t create handler inside thread " + Thread.currentThread()

  • " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
    }

不对啊, 你TM骗我,在最开始你继承的Handler可没有这几个参数。哎呀,小伙子别心急,你看这个无参构造方法不也调用的这个方法么。

public Handler() {
this(null, false);
}

在这个有参数的构造方法中呢,可以看到有这么两行:

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can’t create handler inside thread " + Thread.currentThread()

  • " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;

我们在Handler中使用的mQueue就是在这里赋值的。这里的赋值可不简单,它拿的是人家LooperMessageQueue作为自己的MessageQueue,**而且在上面的代码中有一个很关键的点,就是调用Looper.myLooper()方法中获取这个Looper对象,如果是空的话就要抛出异常。**这一点非常关键,我们先做个记号,一会回过头来会看这一行代码。你就会明白它的作用了。

现在先不研究Looper,我们继续看我们的MessageQueue。上面说到,最后发送消息都调用的是MessageQueuequeue.enqueueMessage(msg, uptimeMillis)方法。现在我们已经拿到了queue,进去看看这个方法它做了什么。

// MessageQueue.java
//省略部分代码
boolean enqueueMessage(Message msg, long when) {

synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
msg.recycle();
return false;
}

msg.markInUse();
msg.when = when;

//【1】拿到队列头部
Message p = mMessages;
boolean needWake;

//【2】如果消息不需要延时,或者消息的执行时间比头部消息早,插到队列头部
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 {
//【3】消息插到队列中间
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;
}

if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

主要分为3个步骤(见以上代码标注)。

  1. mMessages 是队列的第一消息,获取到它
  2. 判断消息队列是不是空的,是则将当前的消息放到队列头部;如果当前消息不需要延时,或当前消息的执行时间比头部消息早,也是放到队列头部。
  3. 如果不是以上情况,说明当前队列不为空,并且队列的头部消息执行时间比当前消息早,需要将它插入到队列的中间位置。

如何判断这个位置呢?依然是通过消息被执行的时间。

通过遍历整个队列,当队列中的某个消息的执行时间比当前消息晚时,将消息插到这个消息的前面。

可以看到,消息队列是一个根据消息【执行时间先后】连接起来的单向链表。

想要获取可执行的消息,只需要遍历这个列表,对比当前时间与消息的执行时间,就知道消息是否需要执行了。

好了,MessageQueueJava层的分析到这里就结束了。

等等,这就结束了?

没错,到这一步,消息已经添加到了这个名为队列实为单链表的队列中。

不对啊,我handleMessage方法如何被调用呢?消息添加进去就完了?说好的线程切换呢?

其实到这一步真的就结束了,最起码在Java层是结束了。消息到这一步被添加到了队列中,HandlerMessageQueue在发送的过程中做的工作已经做完了。但是既然有队列,那么不可能说光添加不读取把。不然我添加了有什么用?

是的,接下来就是Looper大展神威的时候到了。

Looper

在上面提到了,Handler中的MessageQueue对象其实就是Handler中的Looper它的MessageQueueHandlerMessageQueue中添加消息,其实就是往HandlerLooper所持有的MessageQueue中添加对象。可能有点绕,但是需要明白的是这个MessageQueueLooper的,不是Handler的。明白了这一点,你就能很好的理解后面发生的事情。

可能有的小伙伴会说了,这个Looper哪来的,我创建Handler的时候从没看见过它出现啊。没错,在使用Handler的时候它确实没出现过,但是大家还记得Handler中两个参数的那个构造方法嘛?就是下面这个:

//Handler.java
//省略部分代码
public Handler(@Nullable Callback callback, boolean async) {
//敲黑板,划重点就是这一句!!!!
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can’t create handler inside thread " + Thread.currentThread()

  • " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
    }

在这一句中Handler通过Looper.myLooper方法获取到了Looper对象,当然,也有可能没获取到。不过,你如果没获取到就惨了,就要抛异常了。

在职责分析中我们提到了, 这个Looper对象作为消息循环的核心,不断从它的MessageQueue中取出消息然后进行分发。

说人话可以不?

刚才说到MessageQueue那个队列中那么多的消息没人拿,MessageQueue的老板Looper看不下去了,说你这也太浪费了,来我拿吧,然后它专门负责一个个拿,然后看这是谁发的,然后让谁去处理。

那我们看看这个Looper.myLooper()方法做了什么事情呢。它是如何返回一个Looper对象的呢?

public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

sThreadLocal又是什么鬼?咱们看一下它的定义

//sThreadLocal.get() will return null unless you’ve called prepare().
@UnsupportedAppUsage
static final ThreadLocal sThreadLocal = new ThreadLocal();

可以看到这个sThreadLocal是一个ThreadLocal类,并且它的泛型是Looper对象。ThreadLocal提供了线程的局部变量,每个线程都可以通过set()get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。简要言之:往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。

呀呵,源代码中还有行注释,这行注释的意思是除非您已调用prepare(),否则sThreadLocal.get()将返回null。这行注释就有趣了,刚才我还寻思这个ThreadLocal的get方法得有数据才能返回,可这个数据是啥时候塞进去的呢?你这注释就告诉我了,只有我调用了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));
}
复制代码

可以看得出,最后调用了是prepare(boolean quitAllowed)方法,而这个方法首先判断,如果sThreadLocal有值,就抛异常,没有值才会塞进去一个值。其实很好理解,就是说prepare方法必须调用但也只能调用一次,不调用没有值,抛异常,调用多次也还抛异常。我好难哦~不过大家还记得上面我们重点关注的一个内容吗,在Handler中的有参构造函数中有这么一行代码会报异常:

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can’t create handler inside thread " + Thread.currentThread()

  • " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;

如果Looper为空就抛异常,现在我们知道了,什么时候Looper为空呢?没有调用prepare方法的时候会为null,也就是说在构造Handler之前,必须得有Looper对象,换言之,**在构造Handler之前,必须调用Looperprepare方法创建Looper。**这句话非常重要,所以我又是下划线又是加粗的,一定要记住这句话。在后面自定义一个Looper的时候会用到。

接下来再看看这行sThreadLocal.set(new Looper(quitAllowed));做了什么吧,它是如何塞进去的呢

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

set方法首先获取到了当前的线程,然后获取一个map。这个map是以键值对形式存储内容的。如果获取的map为空,就创建一个map。如果不为空就塞进去值。要注意的是,这里面的key是当前的线程,这里面的value就是Looper。也就是说,线程和Looper是一一对应的。也就是很多人说的Looper线程绑定了,其实就是以键值对形式存进了一个map中。没什么高大上的。你来你也行。

而这个Looper的构造方法我们也得去看一下:

private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}

Looper的构造方法中,可以看到它创建了一个MessageQueue,没错就是那个被Handler无耻使用的MessageQueue。需要注意的一点,上面的分析中提到了prepare方法必须调用但也只能调用一次,调用以后就会创建Looper对象,也就是说一个线程中只会创建一个Looper对象,而一个Looper对象也只会创建一个MessageQueue对象。

现在我们来梳理一下这个流程哈~

首先创建一个无参数的Handler,在这个Handler的构造方法中又去获取Looper对象,当然获取Looper对象其实是为了它的MessageQueueHandler巴结上了人家Looper对象的MessageQueue以后,发送消息的时候,把要发送的消息给了MessageQueue,添加到了队列中。是不是感觉缺少了什么?没错,好像在这个里面Looper的作用没体现出来,说好的分发消息呢?而且你刚刚说了得调用prepare()方法才会创建Looper,可我没调用过这个方法啊。那这个Looper谁创建的?

Android-Framework:Handler全解析,看完这篇还不懂请给我寄刀片_第1张图片

刚才提到了,Looper在创建的时候会被当成value塞入到一个map中去,这个mapThreadLocal。而key就是创建Looper时所在的线程。也就是所谓的Looper线程绑定。我们一般在用的时候从没创建过Looper,但是我们知道handle中的回调handleMessage方法是运行在主线程中的。Looper的职责不就是分发消息么,也就是说Looper对象在主线程中把消息分发给了Handler。那么这下就明白了,在我们创建Looper的时候,Looper所在的线程是主线程,换言之,与这个Looper绑定的线程就是主线程。

明白了,我这就去和面试官对线。

Android-Framework:Handler全解析,看完这篇还不懂请给我寄刀片_第2张图片

既然是主线程,那么大家应该知道,主线程是谁创建的?ActivityThread类。ActivityThread类也正是整个app的入口。以前我也很好奇,既然Android是用Java写的,按理说Java不应该是有个什么main方法么?怎么我写Android没用过这个main方法呢?其实呢,在ActivityThread中就有这个main方法,它是程序的入口,也就说当你点开app以后,首先会进入到这个main方法中,然后做了一大堆事情,这里就不分析了。你只需要知道,这个main方法才是真正的入口。

那我们来看看这个main方法到底干了什么事情:

//ActivityThread.java
//省略部分代码
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “ActivityThreadMain”);
Process.setArgV0("");
//1 敲黑板,划重点,就是这一句!
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);

if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, “ActivityThread”));
}

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//2 敲黑板,划重点,这一句!
Looper.loop();

throw new RuntimeException(“Main thread loop unexpectedly exited”);
}

这段代码是不是很符合我们平常写的java程序呢?熟悉的main方法又回来了。main方法中可以看到,它调用了Looper的prepareMainLooper方法:

public static void prepareMainLooper() {
//设置不允许退出的Looper
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException(“The main Looper has already been prepared.”);
}
sMainLooper = myLooper();
}
}

可以看到注释1,这个方法最终还是调用了Looperprepare方法,这个方法干嘛的?创建Looper并且把它和当前线程一起塞进map中的啊。当前线程是哪个线程?主线程啊!

一切到这里就真相大白了,在APP启动的时候,入口方法中已经自动帮我们创建好了Looper,并且也自动的帮我们和主线程绑定了。也就是说我们平常用的Handler中的Looper就是主线程中创建的这个Looper。细心的小伙伴应该会发现,这个prepareMainLooper方法你是不能调用的。为啥?因为这个方法在入口的时候执行了一次,所以里面的sMainLooper不为Null了,如果你在调用一次,不就要抛异常了么~

现在Looper也有了,LooperMessageQueue也有了。接下来该分发消息了吧?我Handler发送消息可是已经很久过去了,你这里分析一大通,我还干不干活了?

好,我们现在先假设一个场景。

你买了一个快递,你知道迟早会给你送到,但是不确定到底什么时候才会送到。你想早点拿到快递应该怎么做?

你会不停的问快递公司,我的快递到哪了,到哪了。当然,现实中一般都是等快递员打电话才去拿快递~问题在于,这是程序。

Looper虽说要分发消息,但是它又不知道你什么时候会发送消息,只能开启一个死循环,不断的尝试从队列中拿数据。这个死循环在哪里开始的?没错就是注释2处,Looper.loop()开启了一个死循环,然后不断的尝试去队列中拿消息。

// Looper.java
public static void loop() {

//拿到当前线程的Looper
final Looper me = myLooper();

if (me == null) {
throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);
}

//拿到Looper的消息队列
final MessageQueue queue = me.mQueue;

// 省略一些代码…
//1 这里开启了死循环
for (; {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

try {
msg.target.dispatchMessage(msg);
//省略一些代码…
} catch (Exception exception) {
//省略一些代码…
throw exception;
} finally {
//省略一些代码…
}
//省略一些代码…
msg.recycleUnchecked();
}
}

在循环中Looper不停的取出消息,拿到Message对象以后,会去调用Messagetarget字段的dispatchMessage方法,这个target字段还有印象吗?没错,就是发送它的Handlermessage在被发送出去的时候就已经暗暗记下了是谁发送出去的。现在轮到它报仇了~

我们可以跟进看一下这个dispatchMessage方法:

//Handler.java
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

可以看到,消息会先分发给Meesgaecallback,我们没有定义这个callback,那我们接下来看,还有一个mCallback。这个mCallback是创建Handler的时候可以选择传一个CallBack回调,里面依然是handleMessage方法。也就是说你可以自定义一个类继承Handler,重写handleMessage方法,也可以直接new一个Handler传一个回调。当然,这个都很简单,我就不再赘述了,大家可以自行尝试体验。

我们关注重点,当Looper拿到Message以后,并且根据Messagetarget字段找到了发送消息的Handler,紧接着调用了HandlerhandleMessage方法。重点来了,这个Looper是在哪个线程运行的?主线程,它调用方法是在哪个线程运行的?依然是主线程!handleMessage方法此时在哪个线程运行的?依然是主线程!不知不觉中,线程已经切换过来了,神奇不?其实并不神奇,其实就是主线程中的Looper不断的尝试调用handleMessage方法,如果有消息就调用成功了,此时handleMessage方法就是在主线程中调用的。而handler在哪个线程,Looper并不关心,我反正只在主线程调用你的handleMessage方法。这就是线程切换的本质。就是没有线程切换,主线程的Looper不断的尝试调用而已。

可能有的小伙伴已经懵逼了,我们再次从头到尾梳理一下哈~

  1. mainThreadActivityThread首先创建了一个运行在主线程的Looper,并且把它和主线程进行了绑定。
  2. Looper又创建了一个MessageQueue,然后调用Looper.loop方法不断地在主线程中尝试取出Message
  3. Looper如果取到了Message,那么就在主线程中调用发送这个MessageHandlerhandleMessage方法。
  4. 我们在主线程或者子线程中通过Looper.getMainLooper为参数创建了一个Handler
  5. 在子线程中发送了Message,主线程中的Looper不断循环,终于收到了Message,在主线程中调用了这个HandlerhandleMessage方法。

这里需要注意的是,Looper.loop方法中取到的Looper对象并不一定就是主线程的,因为它是取出当前线程的Looper对象。只不过在ActivityThread这里是主线程,所以拿到的是主线程的Looper对象。所以如果我们要在子线程中创建一个Looper也是可以的,一会我们就实现一下。

到这里可能有的小伙伴还是懵逼,我还是不太明白怎么切换的线程。我们通过一个比喻很好的解释一下

首先有一个小学生小明,小明的任务是写作业。然后有一个老师,老师的任务是批改作业。这个班里还有一个学习委员,学习委员的任务就是负责收作业然后交给老师去批改。

一般情况下,老师是学校已经聘请好的,我们不需要自己去聘请老师。老师一般也就只在办公室批改作业,办公室我们可以理解为主线程。学校就是我们的app。老师就是Looper对象。而小明同学就是Handler,学习委员就是MessageQueue,作业就是Message。老师(Looper)在办公室(主线程)不断的从学习委员(MessageQueue)那里拿到下一本要批改的作业(Message),老师突然发现作业里有错误,老师很生气,于是就从作业本上的姓名知道了是谁写的这个作业(对应Messagetarget字段),于是老师把小明(Handler)叫到办公室(主线程),让小明在办公室(主线程)把作业改好(handleMessage)。在这个例子中,小明作为Handler,他可以在任何地方写作业(sendMessage),也就是说他可以在家里写作业,可以在教室写作业,也可以在小公园写作业,这里的各个地方就是不同的线程。但是写完作业以后一定要交给学习委员,学习委员手里有一摞作业,这一摞作业就是消息的队列,而学习委员就是MessageQueue,他负责收集作业,也就是收集Message。老师在办公室批改作业,发现出错了,就把小明叫到了办公室,让小明在办公室改错。在这里,办公室就是主线程,老师不会管小明是在哪里写的作业,老师只是关心作业出错了,需要小明在办公室里改错。小明在办公室里改错这就是handleMessage方法运行在了主线程。但是也有个问题,不能说你小明在办公室改错改个没完没了,那岂不是影响了后面同学作业的批改?如果小明真的改错改的没完没了,也就是在主线程上作耗时操作很久,那么老师也无法进行下一个同学的作业批改,时间一长,教学就没法进行了。这就是著名的ANR问题。不知道这样比喻,小伙伴们能不能理解线程切换的意思和ANR的意思。如果还不能理解,那么你来砍我吧~

Looper和ANR

很多面试官喜欢问,Looperloop方法是个死循环,而loop方法又是运行在主线程的,主线程上有死循环为什么不会导致ANR存在呢?

其实这里面很有趣的一个点就是,很多小伙伴把Looperloop方法当做一个普通方法来看待,所以才会有这样的疑问。但是这个loop方法并不是一个普普通通的方法。

我们先思考一点,如果我们写一个app,里面一行代码也不写的话,app会不会崩溃?

答案显而易见,是不会的。

可是在上面提到了,本质上App就是一个Java程序,Java程序就有main方法,在ActivityThread类中也确实有这个main方法。我们一般写java程序的时候,是不是main方法中的代码执行完,程序也就结束了。但是app并没有,只要你不退出,它一直运行。那这是为什么呢?

很多小伙伴应该想到了,没错,让程序不退出的话,写一个死循环,那么main方法中的代码永远不会执行完,这样程序就不会自己退出了。Android当然也是这么干的,而且不止Android,基本上所有的GUI程序都是这么干的。正是因为Looper.loop方法这个死循环,它阻塞了主线程,所以我们的app才不会退出。那你可能有疑问了,那既然这里有死循环了,那我其他的代码怎么运行?界面交互怎么办?你问到点子上了。

本质上Android就是事件驱动的程序,界面刷新也好,交互也好,本质上都是事件,这些事件最后通通被作为了Message发送到了MessageQueue中。由Looper来进行分发,然后在进行处理。用人话来说就是,我们的Android程序就是运行在这个死循环中的。一旦这个死循环结束,app也就结束了。

那么ANR是什么呢?ANRApplication Not Responding也就是Android程序无响应。为什么没响应呢?因为主线程做了耗时操作啊。可我还是不明白,明明Looperloop方法就是阻塞了主线程,为什么不ANR呢。那我们就来说道说道,什么是响应?响应就是界面的刷新,交互的处理等等对吧。那么这个响应是谁来响应的?没错,就是loop方法中进行响应的。没响应什么意思?就是loop方法中被阻塞了,导致无法处理其他的Message了。

所以结论就来了,主线程做耗时操作本质上不是阻塞了主线程,而是阻塞了Looperloop方法。导致loop方法无法处理其他事件,导致出现了ANR事件。对比小明这个比喻的话,就是因为小明在办公室里没完没了的改作业,占用了老师的时间,让老师没法批改下一个同学的作业,才导致了教学活动无法正常进行。而老师不断的批改作业,这本身就是正常的教学活动,也正是因为老师不断批改作业,同学们才有提高,教学才能继续。

Handler在Java层几个需要注意的点

  1. 子线程Looper

如果要创建Handler,必须通过Looper.prepare()方法创建Looper,在主线程中ActivityThread已经帮我们创建好了,我们不需要自己去创建,但如果在子线程中创建Handler,要么使用Looper的mainLooper,要么自己调用Looper.prepare()方法创建属于这个线程的looper对象。如下是创建了一个子线程的Looper对象:

class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
//TODO 定义消息处理逻辑.
}
};
Looper.loop();
}
}

  1. 消息池

在生成消息的时候,最好是用 Message.obtain() 来获取一个消息,这是为什么呢?

// Message.java

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–;
return m;
}
}
return new Message();
}

可以看到,obtain方法是将一个Message对象的所有数据清空,然后添加到链表头中。sPool就是个消息池,默认的缓存是50个。而且在Looper的loop方法中最后一行是这样的

msg.recycleUnchecked();

Looper在分发结束以后,会将用完的消息回收掉,并添加到回收池里。

  1. Handler导致的内存泄露问题

什么是内存泄露?简而言之就是该回收的东西没有回收。在Handler中一般是这样使用:

class HandlerActivity: AppCompatActivity() {

private val mHandler = MyHandler()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在子线程中通过自定义的 Handler 发消息
thread {
mHandler.sendEmptyMessageDelayed(1, 1000)
}
}

// 自定义一个 Handler
class MyHandler: Handler() {
override fun handleMessage(msg: Message) {
Log.i(“HandlerActivity”, “主线程:handleMessage: ${msg.what}”)
}
}
}

乍一看没有问题,但是有没有想过一个问题,就是说再发送延时消息之前,app推出了,那么handleMessage方法还会执行吗?答案是会的。为什么?我明明退出了,为什么还会执行呢?其实这和java有关系

MyHandlerHandlerActivity 的内部类,会持有 HandlerActivity 的引用。

在进入页面以后,发送了一个延时 1s 的消息,如果 HandlerActivity 在 1s 内退出了,由于 Handler 会被 Message 持有,保存在其 target 变量中,而 Message 又会被保存在消息队列中,这一系列关联,导致 HandlerActivity 在退出的时候,依然会被持有,因此不能被 GC 回收,这就是内存泄漏!当这个 1s 延时的消息被执行完以后,HandlerActivity 会被回收。

虽然最终结果还是会被Android-Framework:Handler全解析,看完这篇还不懂请给我寄刀片_第3张图片
回收,但是内存泄露问题我们也必须去解决,如何解决?

1.将 MyHandler 改为静态类,这样它将不再持有外部类的引用。可以将 HandlerActivity 作为弱引用放到 MyHandler 中使用,页面退出的时候可以被及时回收。

2.页面退出的时候,在 onDestroy 中,调用 HandlerremoveMessages 方法,将所有的消息 remove 掉,这样也能消除持有链。

  1. 同步消息屏障

什么是同步消息屏障?

在Looper的loop方法中通过Message msg = queue.next();这么一行代码拿到Message进行分发,这个MessageQueue的next方法中有这么一行:

//MessageQueue.java
//省略部分代码
Message next() {

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;
//1 这一行很关键,同步消息屏障的关键点所在
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}

}

}
}

注释1下面的这一行代码,首先会判断msg不为null,然后紧接着判断msgtarget为null。我们知道messagetarget就是发送它的handler,所有的message都有一个handler,这里怎么可能没有handller呢?针对同步消息还真的是所有的message都有handler,而这里是异步消息。满足target == null的消息就是异步消息同步屏障是用来阻挡同步消息执行的。说得好,那么同步屏障有什么用呢?

似乎在日常的应用开发中,很少会用到同步屏障。那么,同步屏障在系统源码中有哪些使用场景呢?Android 系统中的 UI 更新相关的消息即为异步消息,需要优先处理。简而言之,如果在启动绘制之前,用户(开发者)插入了一个非常耗时的消息到队列中,那就会导致 UI 不能按时绘制,导致卡顿掉帧。,同步消息屏障就可以用来保证 UI 绘制的优先性。

Handler在C++层

如果你的目标是理解Handler在Java层是如何实现的,下面就不需要看了。下面主要讲解Handler在C++层是如何工作并实现的。

首先,细心的小伙伴们可能会有疑问。Looper一直处于死循环中,就像老师一直不断的问学习委员要作业批改,老师也是人,不会累么?你问对了,老师当然不会一直不断的问学习委员要作业,正常情况下,是有人交了作业以后,学习委员送过来,老师才会去批改。没有作业的时候,老师可能在休息,可能在玩游戏**。Looper也是一样,在消息队列为空的时候,Looper实际上处于休眠状态,只要当有Handler发送消息的时候,Looper才会被唤醒,去进行分发消息**。那么是怎么实现的呢?

在整个消息机制中,MessageQueue是连接Java层和Native层的纽带,换言之,Java层可以向MessageQueue消息队列中添加消息,Native层也可以向MessageQueue消息队列中添加消息。

这是MessageQueue中的Native方法:

// MessageQueue.java

private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis);
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);

在MessageQueue的构造方法中是这样的:

//MessageQueue.java
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}

调用了nativeInit方法,在native层创建了native层的MessageQueue,mPtr是保存了NativeMessageQueue的指针,后续的线程挂起和线程的唤醒都要通过这个指针来完成,其实就是通过Native层的MessageQueue来完成。

//android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
//初始化native消息队列
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
nativeMessageQueue->incStrong(env); //增加引用计数
return reinterpret_cast(nativeMessageQueue);
}

这个是NativeMessageQueue的构造方法:

//android_os_MessageQueue.cpp
NativeMessageQueue::NativeMessageQueue()
: mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {

mLooper = Looper::getForThread(); //功能类比于Java层的Looper.myLooper();
if (mLooper == NULL) {
mLooper = new Looper(false); //创建native层的Looper
Looper::setForThread(mLooper); //保存native层的Looper到TLS,功能类比于Java层的ThreadLocal.set();
}
}

Looper的构造方法是这样的:

//Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
mWakeEventFd = eventfd(0, EFD_NONBLOCK); //构造唤醒事件的fd
AutoMutex _l(mLock);
rebuildEpollLocked(); //重建Epoll事件
}

void Looper::rebuildEpollLocked() {
if (mEpollFd >= 0) {
close(mEpollFd); //关闭旧的epoll实例
}
mEpollFd = epoll_create(EPOLL_SIZE_HINT); //创建新的epoll实例,并注册wake管道
struct epoll_event eventItem;//新建唤醒监听事件
memset(& eventItem, 0, sizeof(epoll_event)); //把未使用的数据区域进行置0操作
eventItem.events = EPOLLIN; // 设置监听内容可读事件
eventItem.data.fd = mWakeEventFd;
//将唤醒事件(mWakeEventFd)添加到epoll实例(mEpollFd)
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);

for (size_t i = 0; i < mRequests.size(); i++) {
const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem;
request.initEventItem(&eventItem);
//将request队列的事件,分别添加到epoll实例
lFd >= 0) {
close(mEpollFd); //关闭旧的epoll实例
}
mEpollFd = epoll_create(EPOLL_SIZE_HINT); //创建新的epoll实例,并注册wake管道
struct epoll_event eventItem;//新建唤醒监听事件
memset(& eventItem, 0, sizeof(epoll_event)); //把未使用的数据区域进行置0操作
eventItem.events = EPOLLIN; // 设置监听内容可读事件
eventItem.data.fd = mWakeEventFd;
//将唤醒事件(mWakeEventFd)添加到epoll实例(mEpollFd)
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);

for (size_t i = 0; i < mRequests.size(); i++) {
const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem;
request.initEventItem(&eventItem);
//将request队列的事件,分别添加到epoll实例

你可能感兴趣的:(程序员,架构,移动开发,android)