一、概述

1. 什么是Handler

Handler是Android消息机制的上层接口,它为我们封装了许多底层的细节,让我们能够很方便的使用底层的消息机制。Handler的最常见应用场景之一便是通过Handler在子线程中间接更新UI。Handler的作用主要有两个:一是发送消息;二是处理消息,它的运作需要底层Looper和MessageQueue的支撑。MessageQueue即消息队列,它的底层用单链表实现;Looper则负责在一个循环中不断从MessageQueue中取消息,若取到了就交由Handler进行处理,否则便一直等待。关于Looper需要注意的一点是除了主线程之外的其他线程中默认是不存在Looper的。主线程中之所以存在,是因为在ActivityThread被创建时会完成初始化Looper的工作。

2. 为什么使用Handler

总的来说,Handler的作用是将一个任务(从当前线程)切换到指定的线程中去执行。我们知道Android只允许主线程去更新用户界面,而主线程需要时刻保持较高的响应性,因此我们要把一些耗时任务交给工作者线程去执行。那么问题来了,如果工作者线程执行完任务后想要更新UI该怎么破?我们希望的是主线程能够接收到工作者线程的通知,并且能根据工作者线程执行任务的结果对用户界面进行相应的更新。Handler就能让我们很方便的做到这些。Handler的工作过程大致如下图所示:

我们针对上图做下简单解释(详细的分析请见后文):首先我们在主线程中创建Handler对象(使用主线程的Looper)并定义handleMessage方法,这个Handler对象默认会关联主线程中的Looper。通过在工作者线程中使用该Handler对象发送消息,相应的消息处理工作(即handleMessage方法)会在主线程中运行,这样就成功地将更新UI任务从工作者线程切换到了主线程。

二、Handler的工作原理分析

总的来说,Handler对象在被创建时会使用当前线程的Looper来构建底层的消息循环系统(使用默认构造器的情况下),若当前线程不存在Looper,则会报错。Handler对象创建成功后,就可以通过Handler的send或post方法发送消息了。调用send/post方法发送消息时,实际上会调用MessageQueue的enqueueMessage方法将该消息加入到MessageQueue中。之后Looper发现有新消息会取出,并把它交给Handler处理。下面我们通过分析相关源码来详细介绍这一过程。在这之前我们需要先了解一下ThreadLocal的工作原理。

1. ThreadLocal的内部工作机制

ThreadLocal是一个线程内部的数据存储类。通过使用ThreadLocal,能够让同一个数据对象在不同的线程中存在多个副本,而这些副本互不影响。Looper的实现中便使用到了ThreadLocal。通过使用ThreadLocal,每个线程都有自己的Looper,它们是同一个数据对象的不同副本,并且不会相互影响。下面我们现在探索下ThreadLocal的工作原理,为分析Looper的工作原理做好铺垫。

(1)ThreadLocal使用示例

作为ThreadLocal的一个简单示例,我们先创建一个ThreadLocal对象:

private ThreadLocal<Integer> mIntegerThreadLocal = new ThreadLocal<Integer>();

然后创建两个子线程,并在不同的线程中为ThreadLocal对象设置不同的值:

 mIntegerThreadLocal.set(0);
Log.d(TAG, "In Main Thread, mIntegerThreadLocal = " + mIntegerThreadLocal.get()); new Thread("Thread 1") {
@Override
public void run(){
mIntegerThreadLocal.set(1);
Log.d(TAG, "In Thread 1, mIntegerThreadLocal = " + mIntegerThreadLocal.get());
}
}.start(); new Thread("Thread 2") {
@Override
public void run() {
Log.d(TAG, "In Thread 2, mIntegerThreadLocal = " + mIntegerThreadLocal.get());
}
}.start();

在以上代码中,我们在主线程中设置mIntegerThreadLocal的值为0,在Thread 1中该设置为1,而在Thread 2中未设置。我们看一下日志输出:

通过日志输出我们可以看到,主线程与Thread 1的值确实分别为我们为他设置的,而Thread 2中由于我们没有给它赋值,所以就为null。我们虽然在不同的线程中访问同一个数据对象,却可以获取不同的值。那么ThreadLocal是如何做到这一点的呢?下面我们通过源码来寻找答案。

(2)ThreadLocal的工作原理

我们首先要知道,Thread类内部有一个专门用来存储线程对象ThreadLocal数据的实例域,它的声明如下:

ThreadLocal.Values localValues;

这样一来,每个线程中就可以维护ThreadLocal对象的一个副本,而且这些副本不会互相干扰,ThreadLocal的get方法只要到localValues中去取数据就好了,set方法也只需操作本线程的localValues。我们来看一下set方法的源码:

 public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}

第3行通过values方法获取到当前线程的localValues并存入values变量中,接下来在第4行进行判断,若localValues为null,则调用initializeValues方法进行初始化,否则会调用put方法将value存进去。实际上,localValues内部有一个名为table的Object数组,ThreadLocal的值就存在这个数组中。下面我们来看以下put方法的源码,来了解如何将ThreadLocal的值保存到table数组中。

 void put(ThreadLocal<?> key, Object value) {
cleanUp(); // Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1; for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index]; if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
} if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
} // Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
} // Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}

我们主要关注一下第11行到第15行,从这几行代码我们可以了解到,ThreadLocal的值在table数组中的索引是key(即ThreadLocal对象)的reference字段所标识的对象的索引的加一。意思就是,若ThreadLocal对象的reference字段在table数组中的索引为i,那么ThreadLocal的值在table数组的索引就是i+1。

了解了set方法的大致逻辑后,我们再来看一下get方法都做了些什么:

 public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
} return (T) values.getAfterMiss(this);
}

第4行中,获取localValues。第5行若判断为null,则表示未进行设置(比如上面例子中的线程2),就会返回默认值;若判断非空就先获取table数组,然后再计算出index,根据index返回ThreadLocal的值。

经过以上对get和set方法的源码的分析,我们了解到了这两个方法实际上对不同的线程对象会分别操作它们内部的localValues,所以能够实现多个ThreadLocal数据对象的副本之间的互不干扰。了解了ThreadLocal的实现原理,下面我们来探索下Looper是怎么借助ThreadLocal来实现的。

2. Looper的内部工作机制

在介绍Looper的工作机制之前,我们先来简单的介绍下MessageQueue。MessageQueue对消息队列进行了封装,在它的内部使用单链表来保存消息。MessageQueue主要支持以下两个操作:

  • enqueueMessage:向消息队列中插入一个消息。
  • next:从消息队列中取出一个消息(会从队列中删除该消息)。next方法内有一个无限循环,若消息队列为空,它会阻塞在这直到取到消息。

大致了解了MessageQueue后,让我们一起来探索Looper的内部工作机制,看看它是如何漂亮的完成将任务切换到另一个线程这个工作的。我们首先来看一下Looper的构造方法:

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

我们可以看到Looper的构造方法中创建了一个MessageQueue对象。之前我们提到过Handler只有在存在Looper的线程中才能创建,而我们看到Looper的构造方法是private的,那么我们怎么为一个线程创建Looper呢?答案是使用Looper.prepare方法,这个方法的源码如下:

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

我们可以看到prepare方法内部调用了Looper的构造器来为当前线程初始化Looper,而且当前的线程的Looper已经初始化的情况下再调用prepare方法会抛出异常。

创建了Looper后,我们就可以开始通过Looper.loop方法进入消息循环了(注意,主线程中我们无需调用loop方法,因为ActivityThread的main方法中已经为我们调用了)。我们来看一下这个方法的源代码:

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

通过以上代码我们可以看到,在第13行会进入一个无限循环。接着在第14行,调用了MessageQueue的next方法,之前我们介绍过这个方法会一直阻塞直到从消息队列中取出一个消息。退出这个无限循环的唯一方法就是MessageQueue返回null。这可以通过调用Looper的quit方法来实现。当Looper的quit/quitSafely方法被调用时,会导致MessageQueue的quit/quitSafely方法被调用,这会导致消息队列被标记为“退出”状态,如此一来,MessageQueue的next方法就会返回null了。这告诉了我们,如果我们不调用Looper的quit方法,他就会在loop方法中的循环里一直运行下去。

若在第14行中成功从MessageQueue中取得了一个消息,接下来就会对这个消息进行处理。第27行调用了msg.target的dispatchMessage方法,其中msg.target指的是发送这条消息的Handler对象,也就是说这里调用的是发送消息的Handler对象的dispatchMessage方法。注意,Handler的dispatchMessage方法实在创建该Handler时所使用的Looper中执行的,这样一来,便成功地将任务切换到了Looper所在线程。接下来,我们以分析dispatchMessage方法的源码为切入点研究一下Handler的工作原理。

3. Handler的内部工作机制

首先,我们接着上一步,看一下dispatchMessage方法的源码:

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

我们可以看到,这个方法中会首先判断msg.callback是否为null,若不为null则调用handleCallback方法。msg.callback是一个Runnable对象,实际上就代表着我们调用post方法放入MessageQueue中的Runnable对象。也就是说,若我们post了一个Runnable对象,就会调用handleCallback方法,这个方法的源码如下:

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

从以上代码我们可以看到,这个方法就是简单的调用了Runnable对象的run方法让它开始运行。

回到dispatchMessage方法的代码,若msg.callback为null,就会判断mCallback是否为null,若不为null则调用mCallback的handleMessage方法,否则调用handleMessage方法。实际上这两个handleMessage方法都是我们创建Handler对象时定义的消息处理函数,只不过分别对应了两种不同的创建Handler对象的方式。调用mCallback的handleMessage方法表示我们创建Handler对象时传入了一个实现了Callback接口的的对象,而调用handleMessage方法表示我们创建Handler对象时继承了Handler类并重写了handleMessage方法。那么mCallback是什么呢?让我们先看一下Handler的构造方法:

 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;
}

我们可以看到,mCallback被赋值为我们传入的第一个参数callback,callback即为实现了Callback接口的对象,Callback接口中只有一个方法,那就是handleMessage方法。

4. 主线程的消息循环

Android中的主线程也就是我们上面提到过的ActivityThread。我们上面介绍过,ActivityThread的main方法中会通过Looper.loop方法开启循环,相关源码如下:

public static void main(String[] args) {
... Looper.prepareMainLooper(); ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");
}

通过以上代码我们可以看到主线程在初始化时确实通过Looper.loop方法开启了消息循环。那么主线程使用了哪个Handler来与MessageQueue进行交互呢?实际上它使用了ActivityThread.H,它的定义如下:

 1 private class H extends Handler {
2 public static final int LAUNCH_ACTIVITY = 100;
3 public static final int PAUSE_ACTIVITY = 101;
4 public static final int PAUSE_ACTIVITY_FINISHING= 102;
5 public static final int STOP_ACTIVITY_SHOW = 103;
6 public static final int STOP_ACTIVITY_HIDE = 104;
7 public static final int SHOW_WINDOW = 105;
8 public static final int HIDE_WINDOW = 106;
9 public static final int RESUME_ACTIVITY = 107;
10 public static final int SEND_RESULT = 108;
11 public static final int DESTROY_ACTIVITY = 109;
12 public static final int BIND_APPLICATION = 110;
13 public static final int EXIT_APPLICATION = 111;
14 public static final int NEW_INTENT = 112;
15 public static final int RECEIVER = 113;
16 public static final int CREATE_SERVICE = 114;
17 public static final int SERVICE_ARGS = 115;
18 public static final int STOP_SERVICE = 116;
19
20 ...
21
22 public void handleMessage(Message msg) {
23 ...
24 switch (msg.what) {
25 case LAUNCH_ACTIVITY: {
26 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
27 final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
28
29 r.packageInfo = getPackageInfoNoCheck(
30 r.activityInfo.applicationInfo, r.compatInfo);
31 handleLaunchActivity(r, null);
32 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
33 } break;
34 case RELAUNCH_ACTIVITY: {
35 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
36 ActivityClientRecord r = (ActivityClientRecord)msg.obj;
37 handleRelaunchActivity(r);
38 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
39 } break;
40 case PAUSE_ACTIVITY:
41 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
42 handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
43 (msg.arg1&2) != 0);
44 maybeSnapshot();
45 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
46 break;
47
48 ...
49 }
50
51 }

从以上源码中我们可以看到,主线程使用的Handler中定义了一系列常量,代表了发生了各种事件(比如启动Activity、暂停Activity、显示Window)时应发给的主线程的消息标识。实际上这些消息是H在ApplicationThread中发送过来的。具体过程如下:ActivityThread通过ApplicationThread与AMS(Activity Manager Service)进行进程间通信(IPC)。AMS完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会通过H发送消息到ActivityThread的MessageQueue中,之后H的handlerMessage方法便会根据发来的消息进行相应的处理。这样就完成了将任务从ApplicationThread切换到ActivityThread的工作。

三、参考资料

1. Android SDK Sources

2. 《Android开发艺术探索》

深入探索Android中的Handler的更多相关文章

  1. Android中使用Handler造成内存泄露的分析和解决

    什么是内存泄露?Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引用所指向 ...

  2. Android中使用Handler造成内存泄露

    1.什么是内存泄露? Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引用 ...

  3. Android中的Handler的机制与用法详解

    概述: 很多android初学者对android 中的handler不是很明白,其实Google参考了Windows的消息处理机制, 在Android系统中实现了一套类似的消息处理机制.在下面介绍ha ...

  4. android中的Handler和Runnable

    最近在做一个项目,在网络请求时考虑用Handler进行处理,然后就研究了一下Handler和Runnable 首先在看一下java中的Runnable The Runnable interface s ...

  5. 探索Android中的Parcel机制(上)

    一.先从Serialize说起 我们都知道JAVA中的Serialize机制,译成串行化.序列化……,其作用是能将数据对象存入字节流其中,在须要时又一次生成对象.主要应用是利用外部存储设备保存对象状态 ...

  6. Android中利用Handler实现消息的分发机制(三)

    在第二篇文章<Android中利用Handler实现消息的分发机制(一)>中,我们讲到主线程的Looper是Android系统在启动App的时候,已经帮我们创建好了,而假设在子线程中须要去 ...

  7. 转:Android中的Handler的机制与用法详解

    注:Message类的用法: message的几个参数都可以携带数据,其中arg1与arg2可以携带int类型,what是用户自定义的int型,这样接受者可以了解这个消息的信息. 说明:使用Messa ...

  8. 深入源代码解析Android中的Handler,Message,MessageQueue,Looper

    本文主要是对Handler和消息循环的实现原理进行源代码分析.假设不熟悉Handler能够參见博文< Android中Handler的使用>,里面对Android为何以引入Handler机 ...

  9. android中的Handler

    android的Handler   前言 学习android一段时间了,为了进一步了解android的应用是如何设计开发的,决定详细研究几个开源的android应用.从一些开源应用中吸收点东西,一边进 ...

随机推荐

  1. Effective Java 77 For instance control, prefer enum types to readResolve

    The readResolve feature allows you to substitute another instance for the one created by readObject ...

  2. 在Myeclipse中配置Maven

    第一步:下载maven安装包,配置环境变量M2_HOME;变量值为maven的解压目录. 第二步:在eclipse4.0之前的版本需要安装maven插件,方法即:将maven插件包复制到eclipse ...

  3. python datetime模块用strftime 格式化时间

    1 2 3 #!usr/bin/python import datetime datetime.datetime.now() 这个会返回 microsecond.因此这个是我们不需要的.所以得做一下修 ...

  4. centos下yum搭建安装linux+apache+mysql+php环境

    一.脚本YUM源安装: 1.yum install wget                                                     #安装下载工具wget 2.wge ...

  5. centos7下docker 部署javaweb

    LXC linux container 百度百科:http://baike.baidu.com/link?url=w_Xy56MN9infb0hfYObib4PlXm-PW02hzTlCLLb1W2d ...

  6. css3 animation动画技巧

    一,css3 animation动画前言 随着现在浏览器对css3的兼容性越来越好,使用css3动画来制作动画的例子也越来越广泛,也随着而来带来了许多的问题值得我们能思考.css3动画如何让物体运动更 ...

  7. 《TCP/IP详解 卷一》读书笔记-----TCP数据流

    1.Delayed Acknowledgements:TCP通常不会在收到数据之后立即返回一个ACK,而是会有一个延时,希望能ACK报文段中带上一些数据,通常这个延时为200ms 2.Nagle Al ...

  8. 【读书笔记《Android游戏编程之从零开始》】8.Android 游戏开发常用的系统控件(系统控件常见问题)

    Android 中常用的计量单位Android有时候需要一些计量单位,比如在布局Layout文件中可能需要指定具体单位等.常用的计量单位有:px.dip(dp).sp,以及一些不常用的pt.in.mm ...

  9. tarjan算法求割点cojs 8

    tarjan求割点:cojs 8. 备用交换机 ★★   输入文件:gd.in   输出文件:gd.out   简单对比时间限制:1 s   内存限制:128 MB [问题描述] n个城市之间有通讯网 ...

  10. 传统高斯模糊与优化算法(附完整C++代码)

    高斯模糊(英语:Gaussian Blur),也叫高斯平滑,是在Adobe Photoshop.GIMP以及Paint.NET等图像处理软件中广泛使用的处理效果,通常用它来减少图像噪声以及降低细节层次 ...