在Android开发当中,Thread、Handler、Looper这几个类是特别常见,在刚开始学习Android的时候对这些类可能并不是很清晰。下面我们就一起从源码的角度剖析一下这几个类的工作原理。
Thread
首先是Thread, 我们都知道一个Thread就是一个线程对象,只要在run方法中填写自己的代码然后启动该线程就可以实现多线程操作。例如 :
new Thread(){
public void run() {
// 耗时的操作
};
}.start();
我们知道,针对上面的代码中,当执行完run中的操作时,整个线程就会结束,并不会一直执行下去。而我们的应用程序会一直执行,除非你退出或者应用程序抛出异常。这又引入了另外一个概念,即消息队列。在Android应用启动时,会默认有一个主线程(UI线程),在这个线程中会关联一个消息队列,所有的操作都会被封装成消息然后交给主线程来处理。为了保证主线程不会主动退出,会将取消息的操作放在一个死循环中,这样程序就相当于一直在执行死循环,因此不会退出。
示例图如下 :
Android应用程序的入口为ActivityThread.main方法,详情请参考Android应用程序进程启动过程的源代码分析,UI线程的消息循环就是在这个方法中创建的,源码如下:
public static void main(String[] args) {
SamplingProfilerIntegration.start();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();// 1、创建消息循环Looper
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler(); // UI线程的Handler
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop(); // 2、执行消息循环
throw new RuntimeException("Main thread loop unexpectedly exited");
}
执行ActivityThread.main方法后,应用程序就启动了,并且会一直从消息队列中取消息,然后处理消息。那么系统是如何将消息投递到消息队列中的?又是如何从消息队列中获取消息并且处理消息的呢? 答案就是Handler。
Handler
在我们在子线程中执行完耗时操作后很多情况下我们需要更新UI,但我们都知道,不能在子线程中更新UI。此时最常用的手段就是通过Handler将一个消息post到UI线程中,然后再在Handler的handleMessage方法中进行处理。 但是有一个点要注意,那就是该Handler必须在主线程中创建!! 简单示例如下:
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
// 更新UI
}
}
MyHandler mHandler = new MyHandler() ;
// 开启新的线程
new Thread(){
public void run() {
// 耗时操作
mHandler.sendEmptyMessage(123) ;
};
}.start();
为什么必须要这么做呢?其实每个Handler都会关联一个消息队列,消息队列被封装在Lopper中,而每个Looper又会关联一个线程(ThreadLocal),也就是每个消息队列会关联一个线程。Handler就是一个消息处理器,将消息投递给消息队列,然后再由对应的线程从消息队列中挨个取出消息,并且执行。默认情况下,消息队列只有一个,即主线程的消息队列,这个消息队列是在ActivityThread.main方法中创建的,通过Lopper.prepareMainLooper()来创建,然后最后执行Looper.loop()来启动消息循环。那么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(); // 获取Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; // 获取消息队列
mCallback = null;
}
从Handler默认的构造函数中我们可以看到,Handler会在内部通过Looper.getLooper()来获取Looper对象,并且与之关联,最重要的就是消息队列。那么Looper.getLooper()又是如何工作的呢?我们继续往下看.
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static Looper myLooper() {
return sThreadLocal.get();
}
/**
* 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();
setMainLooper(myLooper());
myLooper().mQueue.mQuitAllowed = false;
}
private synchronized static void setMainLooper(Looper looper) {
mMainLooper = looper;
}
/** 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() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
我们看到myLooper()方法是通过sThreadLocal.get()来获取的,关于ThreadLocal的资料请参考ThreadLocal多线程实例详解。那么Looper对象又是什么时候存储在sThreadLocal中的呢? 眼尖的朋友可能看到了,上面贴出的代码中给出了一个熟悉的方法,prepareMainLooper(),在这个方法中调用了prepare()方法,在这个方法中创建了一个Looper对象,并且将该对象设置给了sThreadLocal。这样,队列就与线程关联上了!!!不同的线程是不能访问对方的消息队列的。再回到Handler中来,消息队列通过Looper与线程关联上,而Handler又与Looper关联,因此Handler最终就和线程、线程的消息队列关联上了。这就能解释上面提到的问题了,“为什么要更新UI的Handler必须要在主线程中创建?”。
就是因为Handler要与主线程的消息队列关联上,这样handleMessage才会执行在UI线程,此时更新UI才是线程安全的!!!
Looper与MessageQueue
创建了Looper后,如何执行消息循环呢?通过Handler来post消息给消息队列( 链表 ),那么消息是如何被处理的呢?答案就是在消息循环中,消息循环的建立就是通过Looper.loop()方法。源码如下 :
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
MessageQueue queue = me.mQueue; // 1、获取消息队列
// 代码省略
while (true) { // 2、死循环,即消息循环
Message msg = queue.next(); // 3、获取消息 (might block )
if (msg != null) {
if (msg.target == null) {
// No target is a magic identifier for the quit message.
return;
}
long wallStart = 0;
long threadStart = 0;
// 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);
wallStart = SystemClock.currentTimeMicro();
threadStart = SystemClock.currentThreadTimeMicro();
}
msg.target.dispatchMessage(msg); // 4、处理消息
// 代码省略
msg.recycle();
}
}
}
可以看到,loop方法中实质上就是建立一个死循环,然后通过从消息队列中挨个取出消息,最后处理消息的过程。对于Looper我们总结一下 : 通过Looper.prepare()来创建Looper对象(消息队列封装在Looper对象中),并且保存在sThreadLoal中,然后通过Looper.loop()来执行消息循环,这两步通常是成对出现的!!
最后我们看看消息处理机制,我们看到代码中第4步通过msg.target.dispatchMessage(msg)来处理消息。其中msg是Message类型,我们看源码 :
public final class Message implements Parcelable {
public int what;
public int arg1;
public int arg2;
public Object obj;
int flags;
long when;
Bundle data;
Handler target; // target处理
Runnable callback; // Runnable类型的callback
// sometimes we store linked lists of these things
Message next; // 下一条消息,消息队列是链式存储的
// 代码省略 ....
}
从源码中可以看到,target是Handler类型。实际上就是转了一圈,通过Handler将消息投递给消息队列,消息队列又将消息分发给Handler来处理。我们继续看
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
private final void handleCallback(Message message) {
message.callback.run();
}
/**
* 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);
}
}
可以看到,dispatchMessage只是一个分发的方法,如果Runnable类型的callback为空则执行handlerMessage来处理消息,该方法为空,我们会将更新UI的代码写在该函数中;如果callback不为空,则执行handleCallback来处理,该方法会调用callback的run方法。其实这是Handler分发的两种类型,比如我们post(Runnable callback)则callback就不为空,当我们使用Handler来sendMessage时通常不会设置callback,因此也就执行handlerMessage这个分支。我们看看两种实现 :
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
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)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this; // 设置消息的target为当前Handler对象
sent = queue.enqueueMessage(msg, uptimeMillis); // 将消息插入到消息队列
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
可以看到,在post(Runnable r)时,会将Runnable包装成Message对象,并且将Runnable对象设置给Message对象的callback字段,最后会将该Message对象插入消息队列。sendMessage也是类似实现 :
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
不管是post一个Runnbale还是Message,都会调用sendMessageDelayed(msg, time)方法。
子线程中创建Handler为何会抛出异常 ?
new Thread(){
Handler handler = null;
public void run() {
handler = new Handler();
};
}.start();
上面的代码有问题吗 ?
/**
* Default constructor associates this handler with the queue for the
* current thread.
*
* If there isn't one, this handler won't be able to receive messages.
*/
public Handler() {
// 代码省略
mLooper = Looper.myLooper(); // 获取myLooper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");// 抛出异常
}
mQueue = mLooper.mQueue;
mCallback = null;
}
我们可以看到,当mLooper对象为空时,抛出了该异常。这是因为该线程中的Looper对象还没有创建,因此sThreadLocal.get()会返回null。解决方法如下 :
new Thread(){
Handler handler = null;
public void run() {
Looper.prepare(); // 1、创建Looper,并且会绑定到ThreadLocal中
handler = new Handler();
Looper.loop(); // 2、启动消息循环
};
}.start();
在代码中我们加了2处,第一是通过Looper.prepare()来创建Looper,第二是通过Looper.loop()来启动消息循环。这样该线程就有了自己的Looper,也就是有了自己的消息队列。如果之创建Looper,而不启动消息循环,虽然不会抛出异常,但是你通过handler来post或者sendMessage也不会有效,因为虽然消息被追加到消息队列了,但是并没有启动消息循环,也就不会从消息队列中获取消息并且执行了!