Android多线程分析之三:Handler,Looper的实现

CC 许可,转载请注明出处

在前文《Android多线程分析之二:Thread的实现》中已经详细分析了Android Thread 是如何创建,运行以及销毁的,其重点是对相应 native 方法进行分析,今天我将聚焦于 Android Framework 层多线程相关的类:Handler, Looper, MessageQueue, Message 以及它们与Thread 之间的关系。可以用一个不太妥当的比喻来形容它们之间的关联:如果把 Thread 比作生产车间,那么 Looper 就是放在这车间里的生产线,这条生产线源源不断地从 MessageQueue 中获取材料 Messsage,并分发处理 Message (由于Message 通常是完备的,所以 Looper 大多数情况下只是调度让 Message 的 Handler 去处理 Message)。正是因为消息需要在 Looper 中处理,而 Looper 又需运行在 Thread 中,所以不能随随便便在非 UI 线程中进行 UI 操作。 UI 操作通常会通过投递消息来实现,只有往正确的 Looper 投递消息才能得到处理,对于 UI 来说,这个 Looper 一定是运行在 UI 线程中。

在编写 app 的过程中,我们常常会这样来使用 Handler:

Handler mHandler = new Handler();
mHandler.post(new Runnable(){
@Override
public void run() {
// do somework
}
});

或者如这系列文章第一篇中的示例那样: 

    private Handler mHandler= new Handler(){
@Override
public void handleMessage(Message msg) {
Log.i("UI thread", " >> handleMessage()"); switch(msg.what){
case MSG_LOAD_SUCCESS:
Bitmap bitmap = (Bitmap) msg.obj;
mImageView.setImageBitmap(bitmap); mProgressBar.setProgress(100);
mProgressBar.setMessage("Image downloading success!");
mProgressBar.dismiss();
break; case MSG_LOAD_FAILURE:
mProgressBar.setMessage("Image downloading failure!");
mProgressBar.dismiss();
break;
}
}
}; Message msg = mHandler.obtainMessage(MSG_LOAD_FAILURE, null);
mHandler.sendMessage(msg);

那么,在 Handler 的 post/sendMessage 背后到底发生了什么事情呢?下面就来解开这个谜团。

首先我们从 Handler 的构造函数开始分析:

    final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;
final boolean mAsynchronous; public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
} 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()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
} public Handler() {
this(null, false);
}

上面列出了 Handler 的一些成员变量:

mLooper:线程的消息处理循环,注意:并非每一个线程都有消息处理循环,因此 Framework 中线程可以分为两种:有 Looper 的和无 Looper 的。为了方便 app 开发,Framework 提供了一个有 Looper 的 Thread 实现:HandlerThread。在前一篇《Thread的实现》中也提到了两种不同 Thread 的 run() 方法的区别。

/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
*/
public class HandlerThread extends Thread {
Looper mLooper;
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
} public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
} /**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
} // If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
}

这个 HandlerThread 与 Thread 相比,多了一个类型为 Looper 成员变量 mLooper,它是在 run() 函数中由 Looper::prepare() 创建的,并保存在 TLS 中:

     /** 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) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

然后通过 Looper::myLooper() 获取创建的 Looper:

    /**
* 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();
}

最后通过 Looper::Loop() 方法运行消息处理循环:从 MessageQueue 中取出消息,并分发处理该消息,不断地循环这个过程。这个方法将在后面介绍。

Handler 的成员变量 mQueue 是其成员变量 mLooper 的成员变量,这里只是为了简化书写,单独拿出来作为 Handler 的成员变量;成员变量 mCallback 提供了另一种使用Handler 的简便途径:只需实现回调接口 Callback,而无需子类化Handler,下面会讲到的:

    /**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*/
public interface Callback {
public boolean handleMessage(Message msg);
}

成员变量 mAsynchronous 是标识是否异步处理消息,如果是的话,通过该 Handler obtain 得到的消息都被强制设置为异步的。

同是否有无 Looper 来区分 Thread 一样,Handler 的构造函数也分为自带 Looper 和外部 Looper 两大类:如果提供了 Looper,在消息会在该 Looper 中处理,否则消息就会在当前线程的 Looper 中处理,注意这里要确保当前线程一定有 Looper。所有的 UI thread 都是有 Looper 的,因为 view/widget 的实现中大量使用了消息,需要 UI thread 提供 Looper 来处理,可以参考view.java:

view.java

    public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
ViewRootImpl.java

    private void performTraversals() {
....
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(attachInfo.mHandler);
...
} static RunQueue getRunQueue() {
RunQueue rq = sRunQueues.get();
if (rq != null) {
return rq;
}
rq = new RunQueue();
sRunQueues.set(rq);
return rq;
} /**
* The run queue is used to enqueue pending work from Views when no Handler is
* attached. The work is executed during the next call to performTraversals on
* the thread.
* @hide
*/
static final class RunQueue {
...
void executeActions(Handler handler) {
synchronized (mActions) {
final ArrayList<HandlerAction> actions = mActions;
final int count = actions.size(); for (int i = 0; i < count; i++) {
final HandlerAction handlerAction = actions.get(i);
handler.postDelayed(handlerAction.action, handlerAction.delay);
} actions.clear();
}
}
}

从上面的代码可以看出,作为所有控件基类的 view 提供了 post 方法,用于向 UI Thread 发送消息,并在 UI Thread 的 Looper 中处理这些消息,而 UI Thread  一定有 Looper 这是由 ActivityThread 来保证的:

public final class ActivityThread {
...
final Looper mLooper = Looper.myLooper();
}

UI 操作需要向 UI 线程发送消息并在其 Looper 中处理这些消息。这就是为什么我们不能在非 UI 线程中更新 UI 的原因,在控件在非 UI 线程中构造 Handler 时,要么由于非 UI 线程没有 Looper 使得获取 myLooper 失败而抛出 RunTimeException,要么即便提供了 Looper,但这个 Looper 并非 UI 线程的 Looper 而不能处理控件消息。为此在 ViewRootImpl 中有一个强制检测 UI 操作是否是在 UI 线程中处理的方法 checkThread():该方法中的 mThread 是在 ViewRootImpl 的构造函数中赋值的,它就是 UI 线程;该方法中的 Thread.currentThread() 是当前进行 UI 操作的线程,如果这个线程不是非 UI 线程就会抛出异常CalledFromWrongThreadException。

    void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}

如果修改《使用Thread异步下载图像》中示例,下载完图像 bitmap 之后,在 Thread 的 run() 函数中设置 ImageView 的图像为该 bitmap,即会抛出上面提到的异常:

W/dalvikvm(796): threadid=11: thread exiting with uncaught exception (group=0x40a71930)
E/AndroidRuntime(796): FATAL EXCEPTION: Thread-75
E/AndroidRuntime(796): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
E/AndroidRuntime(796): at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4746)
E/AndroidRuntime(796): at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:823)
E/AndroidRuntime(796): at android.view.View.requestLayout(View.java:15473)
E/AndroidRuntime(796): at android.view.View.requestLayout(View.java:15473)
E/AndroidRuntime(796): at android.view.View.requestLayout(View.java:15473)
E/AndroidRuntime(796): at android.view.View.requestLayout(View.java:15473)
E/AndroidRuntime(796): at android.view.View.requestLayout(View.java:15473)
E/AndroidRuntime(796): at android.widget.ImageView.setImageDrawable(ImageView.java:406)
E/AndroidRuntime(796): at android.widget.ImageView.setImageBitmap(ImageView.java:421)
E/AndroidRuntime(796): at com.example.thread01.MainActivity$2$1.run(MainActivity.java:80)

Handler 的构造函数暂且介绍到这里,接下来介绍:handleMessage 和 dispatchMessage:

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

前面提到有两种方式来设置处理消息的代码:一种是设置 Callback 回调,一种是子类化 Handler。而子类化 Handler 其子类就要实现 handleMessage 来处理自定义的消息,如前面的匿名子类示例一样。dispatchMessage 是在 Looper::Loop() 中被调用,即它是在线程的消息处理循环中被调用,这样就能让 Handler 不断地处理各种消息。在 dispatchMessage 的实现中可以看到,如果 Message 有自己的消息处理回调,那么就优先调用消息自己的消息处理回调:

    private static void handleCallback(Message message) {
message.callback.run();
}

否则看Handler 是否有消息处理回调 mCallback,如果有且 mCallback 成功处理了这个消息就返回了,否则就调用 handleMessage(通常是子类的实现) 来处理消息。

在分析 Looper::Loop() 这个关键函数之前,先来理一理 Thread,Looper,Handler,MessageQueue 的关系:Thread 需要有 Looper 才能处理消息(也就是说 Looper 是运行在 Thread 中),这是通过在自定义 Thread 的 run() 函数中调用 Looper::prepare() 和 Looper::loop() 来实现,然后在 Looper::loop() 中不断地从 MessageQueue 获取由 Handler 投递到其中的 Message,并调用 Message 的成员变量 Handler 的 dispatchMessage 来处理消息。

下面先来看看 Looper 的构造函数:

    final MessageQueue mQueue;
final Thread mThread;
volatile boolean mRun; private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}

Looper 的构造函数很简单,创建MessageQueue,保存当前线程到 mThread 中。但它是私有的,只能通过两个静态函数 prepare()/prepareMainLooper() 来调用,前面已经介绍了 prepare(),下面来介绍 prepareMainLooper():

    /**
* 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 实现的,不过传入的参数为 false,表示 main Looper 不允许中途被中止,创建之后将looper 保持在静态变量 sMainLooper 中。整个 Framework 框架只有两个地方调用了 prepareMainLooper 方法:

SystemServer.java 中的 ServerThread,ServerThread 的重要性就不用说了,绝大部分 Android Service 都是这个线程中初始化的。这个线程是在 Android 启动过程中的 init2() 方法启动的:

    public static final void init2() {
Slog.i(TAG, "Entered the Android system server!");
Thread thr = new ServerThread();
thr.setName("android.server.ServerThread");
thr.start();
}
class ServerThread extends Thread {
@Override
public void run() {
...
Looper.prepareMainLooper();
...
Looper.loop();
Slog.d(TAG, "System ServerThread is exiting!");
}
}

以及 ActivityThread.java 的 main() 方法:

    public static void main(String[] args) {
....
Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread();
thread.attach(false); if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
} AsyncTask.init(); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");
}

ActivityThread 的重要性也不言而喻,它是 Activity 的主线程,也就是 UI 线程。注意这里的 AsyncTask.init() ,在后面介绍 AsyncTask 时会详细介绍的,这里只提一下:AsyncTask 能够进行 UI 操作正是由于在这里调用了 init()。

有了前面的铺垫,这下我们就可以来分析 Looper::Loop() 这个关键函数了:

   /**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static 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) {
// No message indicates that the message queue is quitting.
return;
} msg.target.dispatchMessage(msg); msg.recycle();
}
}

loop() 的实现非常简单,一如前面一再说过的那样:不断地从 MessageQueue 中获取消息,分发消息,回收消息。从上面的代码可以看出,loop() 仅仅是一个不断循环作业的生产流水线,而 MessageQueue 则为它提供原材料 Message,让它去分发处理。至于 Handler 是怎么提交消息到 MessageQueue 中,MessageQueue 又是怎么管理消息的,且待下文分解。

Android多线程分析之三:Handler,Looper的实现的更多相关文章

  1. Android多线程分析之四:MessageQueue的实现

    Android多线程分析之四:MessageQueue的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前面两篇文章<Androi ...

  2. Android多线程分析之一:使用Thread异步下载图像

    Android多线程分析之一:使用Thread异步下载图像 罗朝辉 (http://www.cnblogs.com/kesalin) CC 许可,转载请注明出处   打算整理一下对 Android F ...

  3. Android多线程分析之中的一个:使用Thread异步下载图像

    Android多线程分析之中的一个:使用Thread异步下载图像 罗朝辉 (http://blog.csdn.net/kesalin) CC 许可.转载请注明出处 打算整理一下对 Android Fr ...

  4. Android多线程分析之二:Thread的实现

    Android多线程分析之二:Thread的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处   在前文<Android多线程分析之一 ...

  5. Android多线程分析之五:使用AsyncTask异步下载图像

    Android多线程分析之五:使用AsyncTask异步下载图像 罗朝辉 (http://www.cnblogs.com/kesalin) CC 许可,转载请注明出处 在本系列文章的第一篇<An ...

  6. Android多线程:深入分析 Handler机制源码(二)

    前言 在Android开发的多线程应用场景中,Handler机制十分常用 接下来,深入分析 Handler机制的源码,希望加深理解 目录 1. Handler 机制简介 定义一套 Android 消息 ...

  7. Android多线程机制和Handler的使用

    参考教程:iMooc关于Handler,http://www.imooc.com/learn/267 参考资料:Google提供Android文档Communicating with the UI T ...

  8. Android之消息机制Handler,Looper,Message解析

    PS:由于感冒原因,本篇写的有点没有主干,大家凑合看吧.. 学习内容: 1.MessageQueue,Looper,MessageQueue的作用. 2.子线程向主线程中发送消息 3.主线程向子线程中 ...

  9. android 多线程Thread,Runnable,Handler,AsyncTask

    先看两个链接: 1.http://www.2cto.com/kf/201404/290494.html 2. 链接1: android 的多线程实际上就是java的多线程.android的UI线程又称 ...

随机推荐

  1. 主机使用代理上网,虚拟机Linux如何连外网

    VMware虚拟机的三种联网方法及原理 一.Brigde--桥接  :默认使用VMnet0 1.原理: Bridge  桥"就是一个主机,这个机器拥有两块网卡,分别处于两个局域网中,同时在& ...

  2. oracle创建用户并导入dmp文件

    SQL命令行执行以下命令:SQL> conn sys/111111 as sysdba; SQL> CREATE USER TEST11 IDENTIFIED BY "11111 ...

  3. c++ 课堂作业(1)

    一.题目 Create a program that asks for the radius of a circle and prints the area of that circle, using ...

  4. linux下svn常用命令

    (如果是第一次提交文件,很可能会出现“svn:'.'不是工作副本”,即当前目录不是工作副本,这个时候需要用到import: eg:svn import . url) 1.将文件checkout到本地目 ...

  5. 如何用Matlab将cell数据写入文件

    我们知道,一般的文件读写函数是不接受直接将cell内容(非数值)直接写入文件的, 例如:dlmwrite('o.txt', C, 'delimiter', '\t');%C 为cell类型数据,会报错 ...

  6. 最小生成树算法——Kruskal算法

    #include<stdio.h> #include<algorithm> #include<windows.h> using namespace std; str ...

  7. js获取div相对屏幕的坐标位置

    1:div相对屏幕的坐标位置 function getDivPosition(div){ var x = div.getBoundingClientRect().left; var y = div.g ...

  8. 猜字符游戏之java

    package days06; //需求......,问题,为什么要用do{}while???import java.util.Scanner;public class RepeatOfGussing ...

  9. 解决Web部署 svg/woff/woff2字体 404错误

    在IIS上部署web项目的时候,发现浏览器报找不到woff.woff2字体的错误,导致浏览器加载字体报404错误,由于服务器IIS不认SVG,WOFF/WOFF2 这几个文件类型,只要在IIS上添加M ...

  10. c#对数据库访问完应关闭连接

    1.对数据库的连接SqlConnection con = new SqlConnection(constr);使用完成后,应该至少应该close或dispose关闭.否则会导致数据库例如(SQl200 ...