Android Handler 消息机制深度解析
Android 提供了 Handler 和 Looper 来满足线程间的通信。Handler 遵循先进先出原则,Looper 类用来管理特定线程内对象之间的消息交换。Handler 消息机制可以说是 Android 系统中最重要部分之一,下面我们将深入解析其工作原理。
Handler 的简单使用
为什么系统不允许子线程更新 UI
因为 UI 控件不是线程安全的。如果在多线程中并发访问可能会导致 UI 控件处于不可预期的状态。那为什么不对 UI 控件的访问加上锁机制呢?主要有两个缺点:
- 上锁会让 UI 控件变得复杂和低效。
- 上锁后会阻塞某些进程的执行。
对于手机系统来说,这两个缺点是不可接受的,所以最简单高效的方法就是采用单线程模型来处理 UI 操作。对开发者而言也不是很麻烦,只是通过 Handler 切换一下访问的线程即可。
Handler 的使用步骤
既然子线程不能直接更改界面,我们可以借助 Handler 来更新界面。主要步骤如下:
- new 出来一个 Handler 对象,复写 handleMessage 方法。
- 在需要执行更新 UI 的地方 sendEmptyMessage 或者 sendMessage。
- 在 handleMessage 里面的 switch 里面 case 不同的常量执行相关操作。
public class MainActivity extends ActionBarActivity {
private TextView mTextView;
private Handler mHandler;
private static final int UI_UPDATE1 = 0;
private static final int UI_UPDATE2 = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UI_UPDATE1:
mTextView.setText("通过 Handler 方法 1 修改 UI");
break;
case UI_UPDATE2:
mTextView.setText("通过 Handler 方法 2 修改 UI");
break;
}
}
};
mTextView = (TextView) findViewById(R.id.textview);
mTextView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("Test", "点击文字");
updateUi();
}
});
}
protected void updateUi() {
new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain();
msg.what = UI_UPDATE2;
mHandler.sendMessage(msg);
}
}).start();
}
}
对应的布局文件 activity_main.xml 如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.handlerdemo3.MainActivity">
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world"
android:layout_centerInParent="true"
android:clickable="true" />
</RelativeLayout>
消息机制的分析理解
安卓的异步消息处理机制就是 handler 机制。主线程 ActivityThread 被创建的时候就会创建 Looper,Looper 被创建的时候创建 MessageQueue。也就是说主线程会直接或间接创建出来 Looper 和 MessageQueue。
Handler 的工作机制简单来说是这样的:
- Handler 发送消息仅仅是调用 MessageQueue 的 enqueueMessage 向插入一条信息到 MessageQueue。
- Looper 不断轮询调用 MessageQueue 的 next 方法。
- 如果发现 message 就调用 handler 的 dispatchMessage,dispatchMessage 被成功调用,接着调用 handleMessage()
Handler 消息机制的源码分析
ThreadLocal 工作原理
首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过 ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
另外,说 ThreadLocal 使得各线程能够保持各自独立的一个对象,并不是通过 ThreadLocal.set() 来实现的,而是通过每个线程中的 new 对象的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过 ThreadLocal.set() 将这个新创建的对象的引用保存到各线程的自己的一个 map 中,每个线程都有这样一个 map,执行 ThreadLocal.get() 时,各线程从自己的 map 中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal 实例是作为 map 的 key 来使用的。
如果 ThreadLocal.set() 进去的东西本来就是多个线程共享的同一个对象,那么多个线程的 ThreadLocal.get() 取得的还是这个共享对象本身,还是有并发访问问题。
查看 ThreadLocal 的 set 方法:
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
查看 values 方法里面做了什么:
Values values(Thread current) {
return current.localValues;
}
通过上面代码我们可以知道,当 values 为空的时候才调用 initializeValues 方法进行初始化。其实就是各种赋值 table 数组,进行初始化。最后才是调用 values.put(this, value) 把 ThreadLocal 和 value 一起保存。
可以看出,Threadlocal 的值在 table 数组的存储位置总是 reference 的下一个位置。
接下来,查看 Threadlocal 的 get 方法。Get 方法的逻辑是:通过 values 方法取出当前线程的 localValues 对象,如果为 null,就返回初始值。如果 localValues 不为 null,取出其 table 数组,如果 reference 等于 table 数组 index 角标的值,就在 table[index + 1] 取出其 Threadlocal 值。
MessageQueue 工作原理
MessageQueue 中文翻译就是消息队列,它内部存储了一组信息,存放的是 Message,以队列的形式对外提供了插入和删除的工作(虽然名字叫做队列,但是其内部的存储结构是单链表)。主要插入和读取两个操作,这两个操作对应着两个方法:
- 插入(入队)enqueueMessage(Message msg, long when)
- 读取(出队)next()
查看 enqueueMessage 的源码:
boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null) {
throw new AndroidRuntimeException("Message must have a target.");
}
boolean needWake;
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;
}
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == && msg.isAsynchronous();
Message prev;
(;;) {
prev = p;
p = p.next;
(p == || < p.) {
;
}
(needWake && p.isAsynchronous()) {
needWake = ;
}
}
msg.next = p;
prev.next = msg;
}
}
(needWake) {
nativeWake(mPtr);
}
;
}
从 enqueueMessage 的实现来看,主要操作就是单链表的插入操作。接下来查看 next 方法的实现:
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
(now < msg.) {
nextPollTimeoutMillis = () Math.min(msg. - now, Integer.MAX_VALUE);
} {
mBlocked = ;
(prevMsg != ) {
prevMsg.next = msg.next;
} {
mMessages = msg.next;
}
msg.next = ;
(DEBUG) Log.v(TAG, + msg);
msg.markInUse();
msg;
}
} {
nextPollTimeoutMillis = -;
}
(mQuitting) {
dispose();
;
}
(pendingIdleHandlerCount <
&& (mMessages == || now < mMessages.)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
(pendingIdleHandlerCount <= ) {
mBlocked = ;
;
}
(mPendingIdleHandlers == ) {
mPendingIdleHandlers = [Math.max(pendingIdleHandlerCount, )];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
( ; i < pendingIdleHandlerCount; i++) {
mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = ;
;
{
keep = idler.queueIdle();
} (Throwable t) {
Log.wtf(TAG, , t);
}
(!keep) {
() {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = ;
nextPollTimeoutMillis = ;
}
}
}
从上面代码可以得出以下结论:
- next 方法是一个无限循环的方法,如果消息队列中没有消息,那么 next 方法会一直阻塞。
- 当有新消息到来,next 方法会返回这条消息,并将其从单链表中移除。
Looper 的工作原理
Looper 是一个轮询器,它的作用不断轮询 MessageQueue,当如果有新的消息就交给 Handler 处理,如果轮询不到新的消息,那就自身就处于阻塞状态。
查看 Looper 类的源码,可以发现他的构造方法里面创建了一个 MessageQueue,然后将当前线程的对象保存起来:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在 Looper 的构造方法中初始化了一个消息队列 MessageQueue 和一个线程 Thread。从这可看出:一个 Looper 对应着一个消息队列以及当前线程。当收到消息 Message 后系统会将其存入消息队列中等候处理。至于 Looper,它在 Android 的消息机制中担负着消息轮询的职责,它会不间断地查看 MessageQueue 中是否有新的未处理的消息;若有则立刻处理,若无则进入阻塞。
相信大家一定有遇到过,在子线程中创建 Handler 会报错。解决办法就是 new Handler 的时候加上 Looper.prepare()。而 Looper.prepare() 的内部实现逻辑就是创建一个 Looper:
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 的,但是为什么在主线程没有创建的 Looper 就可以使用 Handler?主线程是特别的。主线程,也就是 ActivityThread,当主线程被创建的时候,会调用 Looper 内的 prepareMainLooper 方法,创建 Looper,该方法是专门给主线程创建 Looper 用的。也正因为这点,所以我们在主线程创建了 Handler 就直接能用了。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
由于这个特殊性,Looper 还提供了一个 getMainLooper 方法,使得可以在任何地方获取主线程的 Looper。
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
接下来,我们看一下 Looper 的退出。Looper 提供了两个方法进行退出操作,分别是 quit 和 quitSafely,他们调用的是 MessageQueue 的 quit 方法。
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
MessageQueue 的 quit 方法:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
nativeWake(mPtr);
}
}
如果调用 Looper.quit 方法,最终会调用 removeAllMessagesLocked 方法,该方法逻辑:直接遍历所有的消息,并将消息强制回收。
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
如果调用 Looper.quitSafely 方法,最终会调 removeAllFutureMessagesLocked 方法,该方法逻辑:
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
一个无限 for 循环,只有 n.when > now 和 n == null 才会跳出循环,说明是等消息队列中已有的消息处理完毕后,才会跳出,然后执行回收。
Looper 这个类里面最重要的方法就是 loop() 开启消息循环这个方法了,看一下 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;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next();
if (msg == null) {
return;
}
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long me.mTraceTag;
(traceTag != ) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
{
msg.target.dispatchMessage(msg);
} {
(traceTag != ) {
Trace.traceEnd(traceTag);
}
}
(logging != ) {
logging.println( + msg.target + + msg.callback);
}
Binder.clearCallingIdentity();
(ident != newIdent) {
Log.wtf(TAG, +
Long.toHexString(ident) + +
Long.toHexString(newIdent) + + msg.target.getClass().getName() + + msg.callback + + msg.what);
}
msg.recycleUnchecked();
}
}
通过代码我们知道:looper 方法是一个死循环,唯一跳出的循环的方式是 MessageQueue 的 next 方法返回 null,但这几乎不可能,因为在 MessageQueue 的 next 方法中,假如没有消息加入队列,next 方法会一直阻塞,不会返回 null。如果我们不手动调用 quit 或者 quitSafely 方法的话,MessageQueue 的 next 方法是不可能返回 null 的。当 MessageQueue 没有消息时,next 方法会一直阻塞在那里,因为 MessageQueue 的 next 方法阻塞了,就导致 Looper 的 loop 方法也一直在阻塞了。
这里我们那一分为二的谈,loop 轮询不到消息:那么处于阻塞状态,然后就没有然后了,除了又轮询到了新的消息。loop 轮到了新的消息:Looper 就会处理消息。
- msg.target.dispatchMessage(msg),这里的 msg.target 就是指 Handler 对象。
- 到了最后,Handler 发送的消息又交给了自己的 dispatchMessage 方法来处理了。(这个 dispatchMessage 方法不是 Handler 自己调用时,是与 Handler 相相关的 Looper 间接调用的),这样下来,就成功地将逻辑切换到指定的线程当中去了。
Handler 的工作原理
Handler 的主要工作:消息的发送和接收。Handler 消息发送的形式有 post 和 send 两种。
查看 Handler 中 sendMessage 代码:
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
sendMessage 调用 sendMessageDelayed:
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
sendMessageDelayed 调用 sendMessageAtTime:
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 调用 enqueueMessage:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
得出结论:Handler 的发送消息仅仅是调用 MessageQueue 的 enqueueMessage 向插入一条信息到 MessageQueue,MessageQueue 就会返回这条消息给 Looper,Looper 会交给 msg.target.dispatchMessage(msg) 方法处理,就进入消息处理阶段。
查看 dispatchMessage 方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
首先,判断 Message.Callback 是否为 null,不为 null,就调用 handleCallback 方法:
private static void handleCallback(Message message) {
message.callback.run();
}
Callback 是一个 Runnable 对象,实际上就是 post 方法传递的 Runnable 参数。
其次,检查 mCallback 是否为 null,如果不为 null 就调用 mCallback.handleMessage 方法,查看 mCallback.handleMessage 方法:
public interface Callback {
public boolean handleMessage(Message msg);
}
源码中注释已经对 Callback 进行了解释:可以用来创建一个 Handler 的实例但不需要派生 Handler 的子类。在日常开发中,创建 Handler 最常见的方式就是派生一个 Handler 的子类并重写 handleMessage 方法来处理具体的消息,而 Callback 给我们提供了另外一种方式,不需要派生 Handler 的子类。
最后,调用 Handler 的 handleMessage 方法,这就是我们平时写 Handler 要实现的方法。
主线程的消息循环
Android 的主线程就是 ActivityThread,主线程的入口方法是 main 方法,在 main 中系统会通过 Looper.prepareMainLooper() 来创建主线程的 Looper 以及 MessageQueue,并通过 Looper.loop() 开启主线程消息循环。
public static void main(String[] args) {
SamplingProfilerIntegration.start();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
主线程开始循环后,ActivityThread 还需要一个 Handler 来和消息队列进行交互,这个 Handler 就是 ActivityThread.H。
ApplicationThread 通过 binder 与 Ams 通信,并将 Ams 的调用,通过 H 类(也就是 Handler)将消息发送到消息队列,然后进行相应的操作,H 收到消息后,就会将 ApplicationThread 中逻辑切换到 ActivityThread 中执行,也就是主线程中执行,这个过程就是主线程的消息循环。
至此,Handler 消息机制就分析完毕。如有错漏,欢迎指正。