android 消息 异步,Android 异步消息处理机制

本文深入探讨了Android的异步消息处理机制,解释了什么是异步消息处理线程及其工作原理。通过分析Looper、Handler、Message和MessageQueue的角色,揭示了Android主线程如何实现异步消息处理。此外,还讨论了开发中与Handler相关的问题,如子线程使用Handler、线程局部存储以及主线程为何不会因Loop.loop()的死循环而卡死。
摘要由CSDN通过智能技术生成

1.前言

在上篇文章Activity启动过程及界面绘制流程解析开篇中提到过要分析activity的启动过程不仅涉及到binder机制还涉及到异步消息处理机制(handler相关知识),这篇文章就来分析Android 异步消息处理机制。文章的大概内容来自《Android内核剖析》一书,主要记录读完相关内容的感受与总结,方便日后复习。

2.异步消息处理线程

要搞懂android 异步消息处理机制,我们有几点需要弄明白:

什么是异步消息处理线程

怎么实现一个异步消息处理线程

异步消息处理线程使用场景

下面先分析什么是异步消息处理线程:

可能到这里大家对“异步”这个词有些疑惑,这里说的“异步”并不是我们常说的Java多线程里面的“同步”和“异步”,本篇文章的异步是指Java里面同步和异步的机制。

所谓异步机制,是指在进行消息处理时,不必等到消息处理完毕才返回。所以异步的同义语是非阻塞(None Blocking)。

通俗的来讲就是异步不用等待代码执行的结果,执行后续的代码,平时开发过程中我们几乎天天用。

在弄清楚异步机制后,我们来看下到底什么是异步消息处理线程。对于Java里面的线程大家肯定不陌生,一般线程在执行完run()方法之后就会结束。而异步消息处理线程是指在线程的内部有一个无线循环,每循环一次从消息队列中取出一个消息进行处理,然后进入下个循环。这样就形成了一个异步机制,当其他线程向当前线程发送消息时,只需要将消息发送到消息队列里,便可以执行其他的操作,而当前线程会在不定的时间从消息队列中取出消息进行处理。

有了异步线程的概念之后我们可以很轻松的得出实现一般异步线程的一般思路。具体来说就是:

每个异步线程内部包含一个消息队列,用先进先出的方式管理消息。外部线程可以向该消息队列中添加消息。

每个异步线程内部有一个while(true)结构,进行无线循环,一旦消息队列中有消息,就能取出相应的消息处理,并回调相应的处理函数。

有了异步消息处理线程的基础之后那我们什么时候会使用这种特殊的线程呢?一般来说:如果一个任务满足以下两点,我们就需要使用异步消息处理线程了。

任务需要常驻,能不断接受外部线程发送的消息。

需要根据不同的操作做出不同的响应。

说到这里大家有没感到有点熟悉,这个特殊的线程和我们熟悉的Android主线程是否很相似。当应用程序运行时,要不断接收用户的响应,所以需要一直常驻内存,满足上面的第一点,运行过程中要判断用户是点击屏幕上的按钮,还是通过物理按键退出程序,根据不同的操作响应不同的事件。所以说我们通常所说的Android主线程其实也是一个异步消息处理线程。

那它的实现满足我们上面的一般异步消息处理线程的条件吗?下面我们具体学习它是怎么实现的。

3.Android主线程异步消息处理的实现

在Java中我们知道一段程序的入口是面main()方法,而我们在开发应用的过程似乎好像没有看到过main()方法,甚至以为activity的onCreate()方法是应用程序的起点。其实不然,Android应用程序的起点依然是main()方法,只是这个方法在Android的底层为我们封装了实现,我们开发的时候只需从onCreate方法开始完成需求。那我们下面来看下应用程序的main()方法,它在ActivityThread类中,因此ActivityThread就是我们通常所说的android应用程序的主线程了。

public static void main(String[] args) {

......

//1.创建消息机制的Loop,同时也会创建消息队列

Looper.prepareMainLooper();

......

if (sMainThreadHandler == null) {//2.获得handler对象

sMainThreadHandler = thread.getHandler();

}

......

//3.进入消息循环

Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");

}

在main()方法中我们似乎没找到while(true)的结构,难道android主线程不是异步消息处理线程?别慌,main()方法里面里面还有一些方法没有分析,我们来逐一分析(和异步消息处理无关的代码,在这里省略了)。其实在上面已经出现了成为一个异步消息处理线程的因素--Looper、Handler。在android程序里面就是通过Looper、Handler、Message、MessageQueue来实现一个异步机制的,在这个机制里面他们分工合作,共同使主线程成为一个异步消息处理线程,其实他们的实现和一般Java里面的异步机制别无两样,只是在android系统里面封装了起来。下面先分析他们各自的作用,然后结合main()方法的实现来看为什么通过这四个对象就能完成一个异步线程。

Handler:消息的处理者,在异步机制里面负责向MessageQueue发送消息和处理消息

MessageQueue:消息队列,在异步机制里面负责存放消息和取出消息

Message:消息的封装者,在异步机制里是Handler发送和处理消息的对象

Looper:循环者,在异步机制里能让当前线程变成循环线程,然后从消息队列中循环读取消息

有了这个概念之后,我们再来分析main()方法里面的先关代码:

在main方法的注释1处,通过调用Looper.prepareMainLooper()方法会创建一个Loop对象,然后在Loop对象的构造方法里面创建一个线程的队列。

/**

* Initialize the current thread as a looper, marking it as an

* application's main looper. The main looper for your application

* is created by the Android environment, so you should never need

* to call this function yourself. See also: {@link #prepare()}

*/

public static void prepareMainLooper() {

//真正的创建Loop对象

prepare(false);

synchronized (Looper.class) {

if (sMainLooper != null) {

throw new IllegalStateException("The main Looper has already been prepared.");

}

//通过myLooper()拿到prepare(false)创建的loop对象

sMainLooper = myLooper();

}

}

接下来看prepare(boolean quitAllowed):

private static void prepare(boolean quitAllowed) {

if (sThreadLocal.get() != null) {

throw new RuntimeException("Only one Looper may be created per thread");

}

//通过Loop的构造方法创建一个Loop对象

sThreadLocal.set(new Looper(quitAllowed));

}

再来看Looper的构造函数:

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);

mThread = Thread.currentThread();

}

通过这个地方看到了确实通过在main方法通过Looper.prepareMainLooper();会创建一个Loop对象和MessageQueue对象,这样距离当前线程成为异步消息处理线程近了一步。下面我们接着看main()方法里面的注释2:

if (sMainThreadHandler == null) {

sMainThreadHandler = thread.getHandler();

}

通过ActivityThread的getHandler()方法会得到ActivityThread的一个类型为Handler内部类H的实例,这样就得到handler对象了。

在准备好了消息的发送与处理的执行者Handler,消息队列和能让线程进行无线循环的Loop之后,接着会执行main()方法里面的注释3Loop.loop()开始接收和处理外部线程的消息。

/**

* Run the message queue in this thread. Be sure to call

* {@link #quit()} to end the loop.

*/

public static void loop() {

//获取在main()里面注释1处创建的loop对象

final Looper me = myLooper();

if (me == null) {

throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

}

//获取在main()里面注释1创建的loop对象时在构造方法里面创建的队列

final MessageQueue queue = me.mQueue;

......

//进入一个无限循环类似于一般异步消息处理线程中的while(true)

for (;;) {

从消息队列中取出消息

Message msg = queue.next(); // might block

if (msg == null) {

// No message indicates that the message queue is quitting.

return;

}

......

// msg.target是一个Handler类型的变量,调用到Handler的dispatchMessage(msg)方法

msg.target.dispatchMessage(msg);

......

//

msg.recycleUnchecked();

}

}

loop方法里面先拿到在main()里面注释1处创建的loop对象及MessageQueue 对象,然后通过MessageQueue 取出消息交给 msg.target处理,也就是调用到Handler的dispatchMessage(msg)方法中,下面我们来看看这个方法。

/**

* Handle system messages here.

*/

public void dispatchMessage(Message msg) {

if (msg.callback != null) {

handleCallback(msg);

} else {

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

}

handleMessage(msg);

}

}

这里一般msg.callback及mCallback为空,所以进入handleMessage(msg)中,点进去发现handleMessage(msg)是一个空方法,是不是有点奇怪。其实是正常的,平时开发中我们会重写handler的handleMessage(msg)方法来处理我们自己特殊的需求,所以这个方法就被设置为了一个空实现。

handler不仅需要处理消息,还需要将消息添加到消息队列。我们再来看下,handler是如何将消息添加到消息队列的。平时我们开发中都是通过handler的sendMessage(msg)方法来添加消息到队列,那我们就来看一下具体是怎么实现的。

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);

}

Handler的sendMessage方法有几个重载的函数,最终会调到上面的函数中。上面的函数又会调到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);

}

最终调用了MessageQueue的enqueueMessage(msg, uptimeMillis)方法:

boolean enqueueMessage(Message msg, long when) {

......

synchronized (this) {

.....

msg.when = when;

Message p = mMessages;

boolean needWake;

if (p == null || when == 0 || when < p.when) {//1.将消息添加到消息队列头

// New head, wake up the event queue if blocked.

msg.next = p;

mMessages = msg;

//是否需要唤醒主线程

needWake = mBlocked;

} else {//2.根据消息的处理时间合适安排消息在队列的位置

// 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);

}

}

return true;

}

这个方法我们注意两点:在上面的注释1处,进入if判断的条件是此时消息队列为空的时候,所以当添加一个消息到队列时,会将消息添加到队列头。注释2处,如果队列中有消息,则会进入else判断,然后根据消息执行的时间来判断消息应该安排在队列的哪个位置。这个时间是怎么回事呢?平时开发中会用到handler发送延时消息,有一个时间参数,在这里就会用来计算应该排在队列的哪个位置。方法如下:

28366bfabf01

handler发送消息的方法列表.png

handler除了直接发送消息外,还可以通过post方法处理runnable,我们来看下它的实现:

public final boolean post(Runnable r)

{

return sendMessageDelayed(getPostMessage(r), 0);

}

可以看到传入post方法的runnable直接作为参数传递给了getPostMessage(r)方法:

private static Message getPostMessage(Runnable r) {

Message m = Message.obtain();

m.callback = r;

return m;

}

最终还是会生成一个Message 对象,将runnable存在Message 的callback 变量中,再调用sendMessageDelayed(Message msg, long delayMillis)方法,依然会调用handler的sendMessageAtTime(Message msg, long uptimeMillis)方法。

小结

通过上面的分析,我们就已经知道android主线程作为一个特殊的异步处理消息的线程,是怎样异步处理消息的。它是通过Handler、Message、Loop以及MessageQueue协同合作来完成异步消息处理的。

4.开发中需要注意的地方

在搞明白了android的异步消息机制后,我们要再来看下平开发中有那些与handler相关的问题。我大概总结了4个问题:

子线程能否使用handler来完成异步操作

一个线程能否有几个handler,loop,messageQueue

通过ThreadLocal这个变量思考android里面变量的生命周期

为什么主线程不会因为Looper.loop()里的死循环卡死?

子线程可以使用handler

子线程是可以使用handler的,但是有一个前提条件,需要一个MessageQueue,这点我们从MessageQueue在handler、loop、messageQueue、message职能中就可以看出来,一个线程中如果没有消息队列,handler如何发送消息呢?从这点来看很好理解,下面再从代码的角度来分析,handler的构造方法:

public Handler(Callback callback, boolean async) {

......

mLooper = Looper.myLooper();

if (mLooper == null) {

throw new RuntimeException(

"Can't create handler inside thread that has not called Looper.prepare()");

}

......

}

通过 Looper.myLooper()方法得到一个Loop类型的mLooper ,如果mLooper 为空,就会报一个 "Can't create handler inside thread that has not called Looper.prepare()"的错误,还记得我们上面的分析吗?在Looper.prepare()方法中会创建一个Loop对象,在Loop对象的构造方法里面会生成一个MessageQueue。所以如果线程里面没有MessageQueue对象,子线程是不能用handler的。

一个线程只能有一个loop,一个messageQueue,但是可以有多个handler

首先从上面的分析我们知道一个线程如果使用handler必须要有一个MessageQueue,这也就意味着必须要有一个Loop,那一个线程能有多个Loop吗?答案是否定的。我们来看Loop的构造方法:

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));

}

在上面的if判断里面调用sThreadLocal.get() 会返回一个loop对象,如果Loop对象不为空,就会报一个运行时异常"Only one Looper may be created per thread",这里已经说得很清楚了,一个线程只能有一个Looper 对象,这也就意味着一个线程只能有一个messageQueue对象。下面我们再来看一下,一个线程能否有多个handler,答案是肯定的,因为在平时开发中,我们经常在activity的oncreate()方法中创建新的handler,而在分析ActivityThread是一个异步消息处理线程的时候我们说过,main()方法中用到了一个handler对象,这个对象在activity的oncreat方法之前以前存在。所以一个线程能有多个handler对象,只是他们对应一个loop、messageQueue对象。

不同类型变量的作用域

在是上面的Loop的 prepare(boolean quitAllowed) 方法中我们看到一个ThreadLocal类型的sThreadLocal的变量,从字面来看好像是一个线程局部储存的对象,那么什么叫线程局部储存呢?

首先我们来看android里面有哪几种变量:

1.方法内部的变量

2.类的成员变量

3.类的静态成员变量

他们的生命周期有很大的不同:方法内部的变量在方法结束之后就会销毁,类的成员变量在类的实例被销毁时会销毁,而类的成员变量在第一次加载进内存之后,只有当进程结束时才会销毁。所以方法内部的变量只能在方法中使用,类的非静态成员变量只能在类实例中使用,类的静态成员变量可以在整个进程中使用。

但是我们这里这里有个需求通过上面的分析,一个线程只能有一个messageQueue对象,一个loop对象,这就相当于loop是属于线程的一个变量,在一个线程中是一样的,而在不同线程中不一样。很显然,上面的提到的android原有变量都不符合这个要求,我们需要自己创建一种用来存储线程变量的东西,这个就是Threadlocal,它存储对象时根据键值对来储存,将线程的id作为键,loop对象作为值进行保存。这样就可以实现相同的线程得到同一份对象的引用,不同的线程得到对象是不同的。从而将原有的android变量类型好像增加了一种线程变量。

为什么在ActivityThread的main方法中有死循环(Loop.loop()),不会卡死

我们看到在ActivityThread的main中调用了 Looper.loop()

public static void main(String[] args) {

......

Looper.prepareMainLooper();

//建立一个Binder通道(会创建新线程,向主线程的messageQueue中发送消息)

ActivityThread thread = new ActivityThread();

thread.attach(false);

if (sMainThreadHandler == null) {

sMainThreadHandler = thread.getHandler();

}

......

Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");

}

要说清楚问题,我们要知道android是基于消息驱动的。具体体现在上述代码中,在代码注释的地方我们可以看到 ActivityThread thread = new ActivityThread(); thread.attach(false);这两行代码,执行这两句代码会建立一个与ActivityManagerService连接的binder通道。ActivityManagerService负责管理所有activity的生命周期方法,例如oncreat,onresume等,当ActivityManagerService开始需要activity执行生命周期方法时,会首先通过建立好的binder通道调用应用程序进程的ApplicationThread的相关方法中。ApplicationThread会通过一个类型为Handler的H类将相关信息发送到主线程的消息队列中,然后通过handler来处理这个消息。这样就不会导致程序的主线程卡死。

上面只是说明了一种情况(activity的生命周期调用),其实所有的情况都是如此。又比如界面的更新:当界面需要更新的时候,也是讲这个消息封装在message对象中,然后添加到主线程的消息队列中,由消息队列统一管理。因此有消息时会进行处理,没有消息时,主线程处于休眠状态。

所以,由于android主线程是基于消息驱动的,因此虽然有Loop.loop()这个死循环,但是主线程不会卡。

5.总结

这篇文章从什么是异步消息处理机制开始,如何实现异步消息处理线程,详细分析了android中主线程作为一个异步消息线程是怎么实现的,以及涉及到的一些源码。最后理清了关于handler的几个问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值