Android消息机制(基于源码解析)
Posted LXChild
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android消息机制(基于源码解析)相关的知识,希望对你有一定的参考价值。
1. 消息机制概述
android中的消息机制主要指的是Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper、Message的支撑,下文会逐一分析。
2. 为什么需要消息机制
Android中的消息机制主要是为了满足线程间通信而设计的,最重要的应用场景应该在于更新UI
Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,那么程序就会抛出异常
系统为什么不允许在自线程中访问UI呢?这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。
那为什么不对UI控件的访问加上锁机制呢?缺点有两个:
- 首先加上锁机制会让UI访问的逻辑变得复杂;
- 其次锁机制会降低访问UI的效率,因为锁机制会阻塞某些现成的执行
鉴于这两个缺点,最简单且最高效的方法就是采用单线程模型来处理UI操作,对于开发者来说也不是很麻烦,只是需要通过Handler切换下UI的访问执行线程即可
3. Android中线程的分类
- 带有消息队列,用来执行循环型任务(例如主线程
ActivityThread
,Android.os.HandlerThread
)有消息时就处理,没有消息时就睡眠 - 没有消息队列,用来执行一次性任务(例如
java.lang.Thread
)
任务一旦执行完成便退出
4. 带有消息队列的线程具体实现
主要涉及4个方面
- Message(消息)
- MessageQueue(消息队列)
- Looper(消息循环)
- Handler (消息发送和处理)
4.1 消息队列
说到MessageQueue,我们来看下它是干什么的
/**
* Low-level class holding the list of messages to be dispatched by a
* {@link Looper}. Messages are not added directly to a MessageQueue,
* but rather through {@link Handler} objects associated with the Looper.
*
*You can retrieve the MessageQueue for the current thread with
* {@link Looper#myQueue() Looper.myQueue()}.
*/
它是一个低等级的持有Messages集合的类,被Looper分发。Messages并不是直接加到MessageQueue的,而是通过Handler对象和Looper关联到一起。我们可以通过Looper.myQueue()方法来检索当前线程的MessageQueue。
4.2 Message
在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。我们看下这个类的注释
/**
*
* Defines a message containing a description and arbitrary data object that can be
* sent to a {@link Handler}. This object contains two extra int fields and an
* extra object field that allow you to not do allocations in many cases.
*
* While the constructor of Message is public, the best way to get
* one of these is to call {@link #obtain Message.obtain()} or one of the
* {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
* them from a pool of recycled objects.
*/
这个类定义了一个包含描述和一个任意类型对象的对象,它可以被发送给Handler。
从注释里我们还可以了解到以下几点:
- 尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。
- 如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存
- 用message.what来标识信息,以便用不同方式处理message
4.3 Looper
/*
* This class contains the code required to set up and manage an event loop
* based on MessageQueue. APIs that affect the state of the queue should be
* defined on MessageQueue or Handler rather than on Looper itself. For example,
* idle handlers and sync barriers are defined on the queue whereas preparing the
* thread, looping, and quitting are defined on the looper.
*/
这个类是基于消息队列用来设置和管理事件循环的代码,对队列的改变应该在MessageQueue或Handler上定义,而不是在Looper本身定义,例如空间处理和同步障碍应该在队列上定义,然而准备线程、循环和退出在Looper本身定义,
4.4 Handler
/**
* A Handler allows you to send and process {@link Message} and Runnable
* objects associated with a thread’s {@link MessageQueue}. Each Handler
* instance is associated with a single thread and that thread’s message
* queue. When you create a new Handler, it is bound to the thread /
* message queue of the thread that is creating it – from that point on,
* it will deliver messages and runnables to that message queue and execute
* them as they come out of the message queue.
*
*There are two main uses for a Handler: (1) to schedule messages and
* runnables to be executed as some point in the future; and (2) to enqueue
* an action to be performed on a different thread than your own.
*
*Scheduling messages is accomplished with the
* {@link #post}, {@link #postAtTime(Runnable, long)},
* {@link #postDelayed}, {@link #sendEmptyMessage},
* {@link #sendMessage}, {@link #sendMessageAtTime}, and
* {@link #sendMessageDelayed} methods. The post versions allow
* you to enqueue Runnable objects to be called by the message queue when
* they are received; the sendMessage versions allow you to enqueue
* a {@link Message} object containing a bundle of data that will be
* processed by the Handler’s {@link #handleMessage} method (requiring that
* you implement a subclass of Handler).
*
*When posting or sending to a Handler, you can either
* allow the item to be processed as soon as the message queue is ready
* to do so, or specify a delay before it gets processed or absolute time for
* it to be processed. The latter two allow you to implement timeouts,
* ticks, and other timing-based behavior.
*
*When a
* process is created for your application, its main thread is dedicated to
* running a message queue that takes care of managing the top-level
* application objects (activities, broadcast receivers, etc) and any windows
* they create. You can create your own threads, and communicate back with
* the main application thread through a Handler. This is done by calling
* the same post or sendMessage methods as before, but from
* your new thread. The given Runnable or Message will then be scheduled
* in the Handler’s message queue and processed when appropriate.
*/
这个有点长,简单概括下:每一个Handler实例关联了一个单一的ghread和这个thread的messagequeue,当Handler的实例被创建的时候它就被绑定到了创建它的thread。它用来调度message和runnables在未来某个时间点的执行,还可以排列其他线程里执行的操作。
Handler主要用的来管理某个线程(也可能是进程)的消息队列。
比如处理主线程的消息队列,包含消息的发送和接收过程。
消息的发送可以通过Post的一系列方法和Sende的一系列方法来实现。
而post的一系列方法最终是通过send的一系列方法来实现的。这样就可以将一些耗时任务放到其他线程之中,待任务完成之后就往主线程的消息队列中添加一个消息,这样Handler的Callback,即handleMessage就会被调用。但是Handler并不是线程安全的,因此建议将Handler作为一个静态内部类。
所以Handler只是处理消息,耗时任务放在其他线程。
4.5 四元素的交互过程
具体工作过程
- 消息队列的创建
- 消息循环
- 消息的发送最基本的两个API
- 带一个Runnable参数,会被转换为一个Message参数
- 带一个Message参数,用来描述消息的内容
- Handler.sendMessage
- Handler.post
- 消息的处理
基于消息的异步任务接口
android.os.HandlerThread
适合用来处于不需要更新UI的后台任务
android.os.AyncTask
适合用来处于需要更新UI的后台任务
4.5.1 Looper与消息队列的创建
首先确认当前线程是否具有Looper
(主线程默认具有Looper
)如果没有则创建
我们知道Android上一个应用的入口,应该是ActivityThread。和普通的Java类一样,入口是一个main方法。创建主线程源码示例:
public static void main(String[] args) {
//~省略部分无关代码~
//创建Looper和MessageQueue对象,用于处理主线程的消息
Looper.prepareMainLooper();
//创建ActivityThread对象
ActivityThread thread = new ActivityThread();
//建立Binder通道 (创建新线程)
thread.attach(false);
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);
//消息循环运行
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我们可以看到主方法首先通过Looper.prepareMainLooper()
初始化了我们主线程(UI)的Looper并且启动它。然后就可以处理子线程和其他组件发来的消息了
如果不是主线程的两线程进行通信,可以通过以下方式来创建
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
//将当前线程初始化为Looper线程
Looper.prepare();
// ...其他处理,如实例化handler
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
// 开始循环处理消息队列
Looper.loop();
}
}
Looper
源码如下:
public final class Looper {
private static final String TAG = "Looper";
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
//Looper内的消息队列
final MessageQueue mQueue;
// 当前线程
final Thread mThread;
private Printer mLogging;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//试图在有Looper的线程中再次创建Looper将抛出异常
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
/**
* 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() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
//~省略部分无关代码~
}
从中我们可以看到以下几点:
- prepare()其核心就是将looper对象定义为ThreadLocal
- 一个Thread只能有一个Looper对象
- prepare()方法会调用Looper的构造方法,初始化一个消息队列,并且指定当前线程
ThreadLocal并不是一个Thread,而是Thread的局部变量。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本。
所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
在调用Looper.loop()方法之前,确保已经调用了prepare(boolean quitAllowed)方法,并且我们可以调用quite方法结束循环
接下来再看看Looper.loop()
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
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的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.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//开始循环
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
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//将真正的处理工作交给message的target,即handler
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
//回收消息资源
msg.recycleUnchecked();
}
}
通过这段代码可知,调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MessageQueue中取出队头的消息(或者说是任务)执行。
除了prepare()和loop()方法,Looper类还有一些比较有用的方法,比如
- Looper.myLooper()得到当前线程looper对象
- getThread()得到looper对象所属线程
- quit()方法结束looper循环这里需要注意的一点是,quit()方法其实 调用的是MessageWueue的quite(boolean safe)方法。
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
我们看到其实主线程是不能调用这个方法退出消息队列的。至于mQuitAllowed参数是在Looper初始化的时候初始化的,主线程初始化调用的是Looper.prepareMainLooper()方法,这个方法把参数设置为false。
4.5.2 消息的发送和处理(Handler)
从MessageQueue的注释中,我们知道添加消息到消息队列是通过Handler来操作的。我们通过源码来看下具体是怎么实现的
public class Handler {
//~省略部分无关代码~
final MessageQueue mQueue;
final Looper mLooper;
public Handler() {
this(null, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(boolean async) {
this(null, async);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
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 = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
//~省略部分无关代码~
}
先看构造方法,其实里边的重点是初始化了两个变量,把关联looper的MessageQueue作为自己的MessageQueue,因此它的消息将发送到关联looper的MessageQueue上。
有了handler之后,我们就可以使用Handler提供的post和send系列方法向MessageQueue上发送消息了。其实post发出的Runnable对象最后都被封装成message对象
接下来我们看一下handler是如何发送消息的
/**
* Causes the Runnable r to be added to the message queue.
* The runnable will be run on the thread to which this handler is
* attached.
*
* @param r The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
/**
* Enqueue a message into the message queue after all pending messages
* before (current time + delayMillis). You will receive it in
* {@link #handleMessage}, in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
/**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*
* @param uptimeMillis The absolute time at which the message should be
* delivered, using the
* {@link android.os.SystemClock#uptimeMillis} time-base.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
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);
}
这里我们只列出了一种调用关系,其他调用关系大同小异,我们来分析一下
- 调用getPostMessage(r),把runnable对象添加到一个Message对象中。
- sendMessageDelayed(getPostMessage(r), 0),基本没做什么操作,又继续调用sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)方法,在这个方法里拿到创建这个Handler对象的线程持有的MessageQueue。
- 调用enqueueMessage(queue, msg, uptimeMillis)方法,给msg对象的target变量赋值为当前的Handler对象,然后放入到MessageQueue。
那发送消息说完了,那我们的消息是怎样被处理的呢?
我们看到message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码。
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public interface Callback {
public boolean handleMessage(Message msg);
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message 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);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
我们看到这里最终又调用到了我们重写的handleMessage(Message msg)方法来做处理子线程发来的消息或者调用handleCallback(Message message)去执行我们子线程中定义并传过来的操作
为什么主线程不会因为Looper.loop()里的死循环卡死或者不能处理其他事务
这里涉及到的东西比较多,概括的理解是这样的
- handler机制是使用pipe来实现的,主线程没有消息处理时会阻塞在管道的读端。
- binder线程会往主线程消息队列里添加消息,然后往管道写端写一个字节,这样就能唤醒主线程从管道读端返回,也就是说queue.next()会调用返回。
主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
既然是死循环又如何去处理其他事务呢?答案是通过创建新线程的方式。
我们看到main方法里调用了thread.attach(false),这里便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程。
参考资料:
以上是关于Android消息机制(基于源码解析)的主要内容,如果未能解决你的问题,请参考以下文章
ValueAnimator源码解析-基于Android API30