Android开发之异步任务消息机制
前言
文章是一篇学习笔记,主要记录了阅读Handler
、Message
、MessageQueue
、Looper
源码(Android 8.1
)的一些心得体会,但并没有涉及到更深层次源码的分析,比如MessageQueue
的native
层的源码。而AsyncTask
只是简单介绍其API
。
概述
Android
的消息机制主要是指Handler
的运行机制,Handler
的运行需要底层的MessageQueue
和Looper
的支撑,常用于更新UI
。
MessageQueue
用于存储消息(Message
),内部的存储结构采用了链表,而不是队列,它提供插入和删除消息的功能,但不会处理消息。Looper
,MessageQueue
的管家,它的loop
方法会一直查看MessageQueue
是否存在消息(Message
)需要处理,如果有,就交给Handler
来处理。Handler
是消息(Message
)的发送者和处理者。
ThreadLocal
并不是线程,它的作用是可以在每个线程中存储数据,这些数据对于其他线程是不可见的。每个线程中只会有一个Looper
,可以通过ThreadLocal
获取到。
另外,线程默认是没有Loope
r的,如果需要使用Handler
就必须为线程创建,比如在子线程。而主线程已经为我们创建好了,可以查阅ActivityThread
的main
方法,其中包括了MessageQueue
的初始化。
它们的数量级关系是:Handler(N):Looper(1):MessageQueue(1):Thread(1)
。
Handler
的主要作用是将一个任务切换到某个指定的线程中执行,这样设计主要是为了解决Android
只能再主线程中更新UI
。
系统为什么不允许再子线程中访问UI呢?这是因为Android
的UI
控件不是线程安全的,如果再多线程中并发访问可能会导致UI
控件处于不可预期的状态。
加锁的缺点有两个:首先加上锁机制会让UI
访问的逻辑变得复杂;其次,锁机制会降低UI
访问的效率,因为锁机制会阻塞某些线程的执行。
创建方式
在此只列出一种创建Handler
的方式,其他的创建方式可以自行百度,但是,一定要注意:如果在没有Looper的线程里创建Handler会报错的:
// 在主线程创建
public class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
// 处理的代码
}
}
MyHandler handler = new MyHandler();
// 子线程中执行
Message message = handler.obtainMessage();
message.what = 1;
message.obj = 123;
handler.sendMessage(message);
原理图
Handler原理图(一).png handler原理图(二).png源码分析
先从ActivityThread
的main
方法看起,里面有两句需要注意的:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
// 注意
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
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
已经准备好了。通常,我们创建Handler
会选择继承Handler
并重写handleMessage
,因为父类的handleMessage
什么也不做。这里,我们关注其构造方法(无参的):
public Handler() {
this(null, false);
}
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;
}
假如我们再子线程中创建Handler
,但该线程并没有Looper
,它会抛出异常:
Can't create handler inside thread that has not called Looper.prepare()
可见,Handler
的创建需要Looper
。那这个异常如何解决呢?举例如下:
new Thread("new Thread") {
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();
现在,我们回想一下,为什么在主线程可以直接创建Handler
?正如前面所讲述的,当我们的App
启动后,会调用ActivityThread
的main
的方法,而Looper
的prepareMainLooper
方法会被调用,也就是创建了Looper
,也就是满足了Handler
的创建条件。其源码如下:
/** 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) {
if (sThreadLocal.get() != null) {
// 一个线程只能创建一个Looper
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();
}
}
不管是prepareMainLooper()
方法还是prepare()
方法,最后都是通过prepare(boolean quitAllowed)
来创建Looper
。我们先来看sThreadLocal
:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
详细的介绍可以参考这篇博客:Android的消息机制之ThreadLocal的工作原理。它的作用是保存当前线程的Looper
,且线程间互不干扰。
再看Looper
的构造方法,它完成了MessageQueue
的创建:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
总的来说,Android
的主线程就是ActivityThread
,主线程的入口方法为main
,再main
方法中系统会通过Looper.prepareMainLooper()
来创建主线程的Looper
以及MessageQueue
,并通过Looper.loop()
来开启主线程的消息循环。
Message:消息载体
-
public int what
:标识 -
public int arg1
:保存int
数据 -
public int arg2
:保存int
数据 -
public Object obj
:保存任意数据 -
long when
:记录应该被处理的时间值,换句话说就是延时时间 -
Handler target
:保存在主线程创建的Handler
对象引用 -
Runnable callback
:用来处理消息的回调器(一般不用,见原理图二) -
Meaage next
:指向下一个Message
用来形成一个链表 -
private static Message sPool
:用来缓存处理过的Message
,以便复用 -
Message obtain()
:它利用了Message
中消息池(sPool
)
先从Message
的创建入手,官方更推荐使用obtain
来创建,而不是其构造方法。它的构造方法什么也不做,只是完成了Message
的创建,而obtain
可以复用Message
,且有很多多种重载。从内存、效率来看,obtain
更优。
/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
*/
public Message() {
}
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
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();
}
可见,消息池采用了链表结构,确实是复用了Message
。
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
下面是Message
的回收处理:
/**
* Return a Message instance to the global pool.
* <p>
* You MUST NOT touch the Message after calling this function because it has
* effectively been freed. It is an error to recycle a message that is currently
* enqueued or that is in the process of being delivered to a Handler.
* </p>
*/
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
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 = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
前面也提到,Message
可以自行处理,处理的逻辑交给一个Runnable
,也就是callback
,读者可以自行查阅obtain
的其他重载,看看callback
赋值的情况,这里就不帖代码了。
Handler:发送、处理、移除消息
前面已经分析过Message
的相关代码,该小节将从发送Message
开始分析。其实,也可以通过Handler
来创建Message
,其内部也是调用Message.obtain
来创建Message
对象。
public final Message obtainMessage() {
return Message.obtain(this);
}
Handler
发送消息的方式有多种方式,这里选了一种最常见的,它的调用流程:
源码如下:
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0); // 延时发送时间为0
}
// 延时发送
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
// 容错
if (delayMillis < 0) {
delayMillis = 0;
}
// 当前时间加上延时时间就是真正发送Message的时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// Looper创建时,MessageQueue也创建了
// mQueue在Handler创建是就初始化了,代码在前面已经贴过了
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) {
// Message的成员变量,用来存储Handler的对象引用
// 也就是记录了由哪个Handler来处理这个Message
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 将Message插入到MessageQueue中,完成发送
return queue.enqueueMessage(msg, uptimeMillis);
}
另外,Handler
的post
方法也可以发送Message
,且该Message
将交给它自身来处理,当读者看过dispatchMessage
方法后就明白了。
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r; // 可以回忆下讲述Message的那一节
return m;
}
下面是dispatchMessage
的源码,它在Looper.loop
方法中被调用:
public void dispatchMessage(Message msg) {
// 如果Message.callback不为null,就交给它自身处理
if (msg.callback != null) {
handleCallback(msg);
} else {
// 如果mCallback不为null,交给该Handler处理
// mCallback是Handler内部的一个接口
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 最后是我们最常见的,重写handleMessage
handleMessage(msg);
}
}
接着来看看Callback
,这个接口长啥样:
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
它也是在Handler
创建的时候初始化的,但调用的构造方法不一样,导致最后的初始化不一样。无参的构造方法会导致mCallback
为null
。
public Handler(Callback callback) {
this(callback, false);
}
最后来看看Handler
移除Message
的实现,但真正的实现交给了MessageQueue
。
public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null);
}
public final void removeMessages(int what, Object object) {
mQueue.removeMessages(this, what, object);
}
MessageQueue:存储消息的,以message的when排序优先级
Message
经Handler
发送后到达MessageQueue
,它采用链表的数据结构来存储Message
。下面是其构造方法:
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
// 如果需要理解为什么loop方法不会卡住主线程,可以从这个本地方法开始入手
mPtr = nativeInit();
}
MessageQueue
中最主要的两个方法是next
和enqueueMessage
。
next
方法也会阻塞(当消息队列为空时或当前时间还没到达Message
要处理的时间点时)但不会卡住主线程,它的工作就是根据需求从消息队列中取一个Message
。注意,next
方法是一个无限循环的方法。
enqueueMessage
方法的工作就是将一个Message
插入到消息队列且位置是合适的,因为它会根据Message
要处理的时间点进行排序,从它的插入操作也可以了解到MessageQueue
采用了链表。
由于next
方法和enqueueMessage
方法的源码过长,下面只贴出enqueueMessage排序Message
的源码:
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;
}
关于阻塞但不会卡住主线程的问题在下一小节会提到。
Looper:从MessageQueue中获取当前需要处理的消息,并提交给Handler处理
关于Looper
在前面也讲述了一部分,现在只剩下loop
方法了,很重要。在它的内部也开启了一个无限循环。
在for
无限循环中,有一句表明了会阻塞:
Message msg = queue.next(); // might block
导致它阻塞的原因在MessageQueue.next
方法。当消息队列退出或正在退出时,loop
方法会结束,也就是跳出无限循环。
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
在这个循环里还有一句需要注意的:
msg.target.dispatchMessage(msg);
意思时交给Handler
处理这个Message
。至此,整个流程就分析完了。
面试的时候可能会遇到:为什么loop
方法会阻塞主线程但不会卡住呢?以下的文章也许可以解答你心中的疑问:
异步任务
- 逻辑上:以多线程的方式完成的功能需求
- API上:指
AsyncTask
类
AsyncTask
- 在没有
AsyncTask
之前,我们用Handler+Thread
就可以实现异步任务的功能需求 -
AsyncTask
是针对Handler
和Thread
的封装,使用它编码更加简洁,更加高效 -
AsyncTask
封装了ThreadPool
,比直接使用Thread
效率要高
相关API
-
AsyncTack<Params,Progress,Ressult>
-
Params
启动任务执行的输入参数,比如HTTP请求的URL -
Progress
后台任务执行的百分比 -
Result
后台执行任务最终返回的结果,比如String
-
-
execute(Params... params)
:启动任务,开始任务的执行流程 -
onPreExcute()
:在分线程工作开始之前在UIThread中执行,一般用来显示提示视图5 -
doInBackground(Params... params)
:在workerThread
中执行,完成任务的主要工作,通常需要较长的时间 -
onPostExecute(Result result)
:在doInBackground
执行完后再UIThread
中执行,一般用来更新界面 -
publishProgress(Progress... values)
:在分线程中发布当前进度 -
onProgressUpdate(Progress... values)
:在主线程中更新进度
AsyncTask
在具体的使用过程中也是有一些条件限制的,主要有以下几种(摘自《Android开发艺术探索》):
- 1、
AsyncTask
的类必须在主线程中加载,这就意味着第一次访问AsyncTask
必须发生在主线程,当然这个过程在Android 4.1
及以上版本中已经被系统自动完成。在Android 5.0
的源码中,可以查看ActivityThread
的main
方法,它会调用AsyncTask
的init
方法,这就满足了AsyncTask
的类必须在主线程中进行加载这个条件了。 - 2、
AsyncTask
的对象必须在主线程中创建。 - 3、
execute
方法必须在UI线程调用。 - 4、不要再程序中直接调用
onPreExecute
、onPostExecute
、doInBackground
和onProgressUpdate
方法 - 5、一个
AsyncTask
对象只能执行一次,即只能调用一次execute
方法,否则会报运行时异常 - 6、在
Android 1.6
之前,AsyncTask
是串行执行任务的,Android 1.6
的时候AsyncTask
开始采用线程池里处理并行任务,但是从Android 3.0
开始,为了避免AsyncTask
所带来的并发错误,AsyncTask又采用一个线程来串行执行任务。尽管如此,在Android 3.0
以及后续的版本中,我们仍然可以通过AsyncTask
的executeOnExecutor
方法来并行地执行任务。
总结
文章以Handler
的创建过程为参照,简单介绍了Handler的原理和源码。文章末尾对AsyncTask
进行了简单的介绍。读者有空可以读一读《Android开发艺术探索》的第10章和第11章。