声明:本文是参考了以下几位大神的文章,自己按照自己的思维习惯整理的笔记,并添加一些相关的内容。如有不正确的地方,欢迎留言指出,谢谢!
郭霖博客
鸿洋博客
任玉刚《Android开发艺术探索》
一. Andoid消息机制概述
Android规定访问UI只能在主线程进行,如果在子线程中访问UI,那么程序就会抛出异常。ViewRootImpl对UI操作做了验证,这个验证工作是由ViewRootImpl的checkThread方法来完成的,如下:
void checkThread(){
if(mThread != Thread.currentThread()){//不是主线程,抛出异常
throw new CalledFromWrongThreadException("Only the
original thread that created a view hierarchy can touch its views);
}
}
Android为什么不允许在子线程中访问UI呢?
因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。
为什么系统不对UI控件的访问加上锁机制?
- 首先加上锁机制会让UI访问的逻辑变得复杂
- 锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行
回想一下,实际开发中,在子线程进行UI操作,通常有以下几种方法:
- 调用 runOnUiThread() 方法,通过匿名内部类实现run()方法进行UI操作
- 使用AsyncTask
- 在主线程中创建Handler对象,子线程创建Message对象,然后通过 sendMessage() 发送创建的Message,主线程通过handleMessage() 处理接收到的Message
- Handler的 post() 方法
- View的 post() 方法
其实,这么多的方法,其内部的实现原理都是一样的,都是通过接下来我们要说的Android异步消息处理机制来实现的。
二. Android异步消息处理机制分析
Android的异步消息处理机制实际上就是Handle的运行机制,因此本节主要围绕Handler的工作过程来分析,主要包括 Handler 、Message 、MessageQueue 和 Looper 这四个类。另外,为了更好地理解Looper这个类,还会介绍 ThreadLocal 这个类。
1. ThreadLocal的工作原理
ThreadLocal是一个线程内部的 数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
ThreadLocal的使用场景:
- 当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候
- 复杂逻辑下的对象传递。比如监听器的传递,有时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,就可以采用ThreadLocal,让监听器作为线程内部的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。
下边通过例子看一下ThreadLocal的真正含义:
//定义一个ThreadLocal对象,存储一个Boolean类型的数据
ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<Boolean>();
//在主线程中设置值为true
mBooleanThreadLocal.set(true);
System.out.println("[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
//新建子线程1,设置值为false
new Thread("Thread#1"){
@Override
public void run() {
mBooleanThreadLocal.set(false);
System.out.println("[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
};
}.start();
//新建子线程2,不设置
new Thread("Thread#2"){
@Override
public void run() {
System.out.println("[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
};
}.start();
上面代码中,在主线程中设置mBooleanThreadLocal的值为true,在子线程1中设置其值为false,在子线程2不设置其值。然后分别在3个线程中通过get方法获取mBooleanThreadLocal的值,根据前边对ThreadLocal的描述,这个时候,主线程应该是true,子线程1中应该是false,而子线程2中由于没有设置值,所以应该是null。运行结果如下:
[Thread#main]mBooleanThreadLocal=true
[Thread#1]mBooleanThreadLocal=false
[Thread#1]mBooleanThreadLocal=null
从运行结果可以看出,虽然在不同线程中访问的是同一个ThreadLocal对象,但是它们通过ThreadLocal获取的值却不一样,这就是ThreadLocal的奇妙之处。
ThreadLocal之所以有这么奇妙的效果,是因为不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值。很显然,不同线程中的数组是不同的。
2. MessageQueue的工作原理
MessageQueue主要包含两个操作:插入和读取
。对应的方法为 enqueueMessage() 和 next(),其中enqueueMessage的作用就是往消息队列插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列删除。故读取操作本身会伴随着删除操作。
尽管MessageQueue叫消息队列,当时它的内部实现并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。
enqueueMessage的源码如下:
final boolean enqueueMessage(Message msg, long when) {
if (msg.when != 0) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
this.notify();
}
}
return true;
}
观察上面的代码,可以发现,其实所谓的入队其实是将所有的消息按时间进行排序(通过比较when参数)。具体的操作方法就是根据时间顺序调用msg.next(),从而为每一个消息指定它的下个消息是什么。
next()方法有点长,这里不贴出来。next()方法是一个无限循环方法,如果消息队列中没有消息,那么next方法就会一直阻塞。当有新消息到来时,next方法会返回这条消息并将其从单链表移除。
3. Looper的工作原理
Looper在Android消息机制中扮演着消息循环的角色,负责创建一个MessageQueue,然后进入一个死循环不断从该MessageQueue中读取消息。Looper主要有两个方法,prepare() 和 looper()。
首先看prepare()的源码:
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(true));
}
可以看到,首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。如果sThreadLocal已经有了looper,则抛出异常,这也说明Looper.prepare()不能被调用两次,同时保证了一个线程中只有一个Looper实例
。
再来看看Looper的构造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
在Looper的构造方法中,创建了一个MessageQueue。由于Looper只能被调用一次,这也就保证了一个线程只能有一个Looper实例,一个Looper只能对应一个MessageQueue
。
Looper最重要的方法是looper方法,只有调用了looper后,消息系统才会真正地起作用。下面看看loop()源码:
public static final 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;
for(;;) {//死循环
Message msg = queue.next(); // might block(可能阻塞)
if (msg != null) {
if (msg.target == null) {
return;
}
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
//把消息交给msg的target的dispatchMessage方法去处理。
msg.target.dispatchMessage(msg);
if (me.mLogging!= null) me.mLogging.println(
"<<<<< Finished to " + msg.target + " "
+ msg.callback);
//释放消息占用资源
msg.recycle();
}
}
}
looper()方法会陷入一个死循环,不断地调用MessageQueue的 next() 方法去获取消息队列中的消息。每当有一个消息出队,就将它传递到 msg.target
的 dispatchMessage() 方法,这里的msg.target就是Handler(下边会分析为什么msg.target是一个Handler对象)。如果消息队列为空,则会阻塞
。
另外,看看myLooper方法
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
在looper()方法中,会先调用myLooper()方法判断looper是否为空。故,looper()必须在调用prepare()后才能调用。
Looper提供了quit 和 quitSafely来退出一个Looper,二者区别是:quit会直接退出,而quitSafely只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全退出。
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
从源码可以看到,退出Looper的两个方法都调用了MessageQueue的quit()方法。
void quit(boolean safe) {
...
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
...
}
}
通过源码,发现,最终,分别调用的是MessageQueue的 removeAllFutureMessagesLocked()
和 removeAllMessagesLocked()
方法。removeAllMessagesLocked()方法主要是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息还是非延迟消息;而removeAllFutureMessagesLocked()方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有非延迟消息派发出去让Handler去处理完后才停止Looper循环。quitSafely相比于quit方法安全的原因在于清空消息之前会派发所有的非延迟消息。
在子线程中,如果手动为其创建了Looper,那么在所有的事情处理完成后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待状态。
4. Handler的工作原理
Handle 的工作主要包含消息的发送和接收过程。消息的发送可以通过 post() 的一系列方法以及 send() 的一系列方法来实现。使用Handler之前,我们都是初始化一个实例,所以想来看看Handle的构造方法,看其如何与MessageQueue联系上的,它在子线程中发送的消息(一般发送消息都在非UI线程)怎么发送到MessageQueue中的。
Handler的无参构造方法:
public Handler() {
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 = null;
}
可以看到,构造方法中会调用Looper.myLooper()来获取当前的Looper对象,如果Looper为空,则会抛出异常。然后又获取了这个Looper实例中保存的MessageQueue(消息队列),这样就保证了handler的实例与我们Looper实例中MessageQueue关联上了。
然后看看我们最常用的 sendMessage() 方法:
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
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);
}
辗转发侧,最后调用了 sendMessageAtTime() 。其实,Handler有很多发送消息的方法,其中除了sendMessageAtFrontOfQueue()方法之外,其它的发送消息方法最终都会辗转调用到sendMessageAtTime()方法中。
sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间。
sendMessageAtTime()方法又调用了enqueueMessage()方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这个方法中首先为 msg.target 赋值为this,也就是把当前的handler作为msg的target属性,最终会调用queue的enqueueMessage()方法入队列。如果大家还记得Looper的looper()方法,该方法中会取出每一个msg,然后交给msg.target.dispatchMessage(msg)
通过处理消息,上文说这里的msg.target就是一个Handler对象就是在这里注入的。
接下来看看dispatchMessage()方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
该方法中,先进行判断,如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法。而Handler的handleMessage是一个空方法,所以一般我们会继承Handler并重写handlerMessage()方法来实现我们处理消息的逻辑。
5. 总结
一个最标准的异步消息处理线程应该是这样写的:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。整个异步消息处理流程的示意图如下图所示:
总结一下:
- 首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,一个线程只会有一个Looper和一个MessageQueue
- Looper.loop()会让当前线程进入一个无限循环,不断从MessageQueue队列中通过next()方法获取消息,然后回调msg.target.dispatchMessage(msg)方法进行处理
- Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue进行关联
- Handler的sendMessage()方法,会给msg的target赋值为handler自身,然后加入MessageQueue中
- 在构造Handler实例时,我们会重写handleMessage()方法,也就是msg.target.dispatchMessage(msg)最终调用的方法
三. 子线程进行UI操作几种方式的分析
本文开始的时候就归纳了5种在子线程中进行UI操作的方法,前边分析了整个Android异步消息处理机制的流程,也就是我们比较常用的通过重写Handler的handleMessage()方法这一流程。下边,我们对其它几种方式进行分析。
1. Handler的post()方法
Handler的post()方法如下:
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
调用了sendMessageDelayed()方法去发送一条消息,并且还使用了getPostMessage()方法将Runnable对象转换成Message。
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
这个方法中,将消息的callback字段值指定为传入的Runnable对象。在Handler的dispatchMessage()方法中原来有做一个检查,如果Message的callback等于null才会去调用handleMessage()方法,否则就调用handleCallback()方法。那我们快来看下handleCallback()方法中的代码吧:
private final void handleCallback(Message message) {
message.callback.run();
}
非常粗暴,直接调用一开始传入的Runnable对象的run()方法。因此,在子线程中通过Handler的post()方法进行UI操作可以这么写:
public class MainActivity extends Activity {
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@Override
public void run() {
// 在这里进行UI操作
}
});
}
}).start();
}
}
2. View的post()方法
看一下View中的post()源码:
public boolean post(Runnable action) {
Handler handler;
if (mAttachInfo != null) {
handler = mAttachInfo.mHandler;
} else {
ViewRoot.getRunQueue().post(action);
return true;
}
return handler.post(action);
}
直接调用了上边分析过的Handler的post()方法
3. Activity的runOnUiThread()方法
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
也很简单,先判断当前线程是不是主线程,如果不是,调用handler的post()方法。否则直接调用Runnable的run()方法。
4. AsyncTask机制
由于AsyncTask后边会专门写一篇文章介绍,这里就不展开了。