Android消息机制 - Handler
不知不觉,发现自己已经写了几十篇文章,但都没有发布出来,从今天起会慢慢放出来互相学习,如有写得不对之处,欢迎讨论。
这篇博客是讲大名鼎鼎的Handler,Handler是Android消息机制的上层接口,Handler的作用是将一个任务切换到某个指定的线程中去执行,所以经常用于切换到主线程中更新UI。
Handler的运行需要底层的MessageQueue和Looper的支撑,MessageQueue是采用单链表的数据结构来存储消息列表,以队列的形式对外提供插入和删除工作。由于MessageQueue只是一个消息的的存储单元,它不能直接处理消息。
而Looper会以无限循环的形式去查找新消息,如果有的话就处理消息,否则一直阻塞。
Looper有个很重要的概念,就是ThreadLocal,可以在不同线程中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。
如何获取到当前线程的Looper呢? 没错答案就是ThreadLoca。
线程默认没有Looper,如果需要使用Handler就必须为线程创建Looper,主线程为ActivityThread,ActivityThread被创建时就会初始化Looper,这也是主线程默认可以使用Handler的原因。
一、问答
上面已经对Handler做了一个大概的总结了,表示看不懂,疑问还是很多,比如:
为什么有Handler ?-> 因为Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,那么程序就会抛出异常。
系统为什么不允许在子线程中访问UI呢? -> 因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,加上锁机制的话,缺点:首先加上锁机制会让UI访问的逻辑变得复杂,其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行,因此采用单线程模型来处理UI操作是最简单和高效的。
Handler的工作原理 -> 创建Handler的线程必须有Looper来构建内部的消息循环系统,否则就会报错,Handler通过send或post发送一个消息,接着调用MessageQueue的enqueueMessage方法将这个消息中的Runnable或者Handler的handleMessage方法就会被调用。有一点要注意,Looper是运行在创建Handler所在的线程中的,这样一来Hanlder中的业务逻辑就被切换到创建Handler所在的线程中去执行了。
举个栗子吧!
在布局中添加一个按钮,Activity的代码如下:
public class MainActivity extends AppCompatActivity {
private Button mBtnStart;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnStart = (Button)findViewById(R.id.btn_start);
mBtnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
consumingTime();
}
});
}
private void consumingTime(){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mBtnStart.setText("执行结束,更新UI");
}
}).start();
}
}
运行logcat如下,出现异常了???? 提示只有创建视图(主线程)的线程才能更新UI。
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
把以上Activity的consumingTime方法改一下
private void consumingTime(){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.sendEmptyMessage(0);
}
}).start();
}
Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
switch (message.what){
case 0:
mBtnStart.setText("执行结束,更新UI");
break;
}
return false;
}
});
运行终于成功!!!
二、进一步分析
2.1 Handler
源码的Handler调用了Handler(Callback callback, boolean async)
public Handler(Callback callback) {
this(callback, false);
}
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(); //重点1
if (mLooper == null) { //重点2
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; //重点3
mCallback = callback;
mAsynchronous = async;
}
获取本线程的Looper,如果没有Looper,会抛出RuntimeException。
那么上面的例子是如何运行的?答案就是:
Android的主线程就是ActivityThread,主线程的入口方法为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。
mLooper.mQueue就是得到MessageQueue(消息队列)。
我们都知道,Handler的工作主要包含消息的发送和接收过程,发送通过post 、send方法。
源码如下
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis); //向MessageQueue中插入一条消息
}
过程就是:Handler发送消息的过程仅仅是向消息队列中插入了一条消息(queue.enqueueMessage(msg, uptimeMillis)),MessageQueue的next方法就会返回这条消息给Looper,Looper收到消息后就会开始处理了,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入了处理消息的阶段。
一不小心说出整个过程。
2.2 MessageQueue
MessageQuboolean More ...enqueueMessage(Message msg, long when) {
if (msg.target == null) {
hrow new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
msg.recycle();
return false;
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { //重点1
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // 重点2
prev.next = msg;
}
// We can assume mPtr != because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
MessageQueue主要包含两个操作:插入和读取(伴随着删除操作),对应方法分别为:enqueueMessage和next,尽管MessageQueue叫消息队列,但是它的内部实现并不是用队列,实际上它是通过一个单链表的数据结构来维护消息列表。
enqueueMessage为单链表的插入操作
next方法是一个无限循环(重点2),有消息返回这条消息并从单链表中删除,没有消息,一直阻塞。
2.3 Looper
public final class More ...Looper {
private static final String TAG = "Looper";
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); //重点1
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
private Printer mLogging;
public static void More ...prepare() { //重点2
prepare(true);
}
private static void More ...prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
public static void More ...prepareMainLooper() { //重点3 主线程
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static Looper More ...getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
public static void More ...loop() { //重点4
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 (;;) { //重点4
Message msg = queue.next(); // might block
if (msg == null) {
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); //重点5
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 x"
+ Long.toHexString(ident) + " to x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
重点1:ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
重点2:创建Looper对象并存放在ThreadLocal中。
重点3:主线程创建Looper对象。
重点4:消息息循环,Looper会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。
回顾一下:主线程通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环。除了prepare方法,还提供了prepareMainLooper方法为ActivityThread创建Looper使用。
重点5:loop方法是一个死循环,
唯一跳出循环的方式是MessageQueue的next方法返回了null。当MessageQueue的next方法返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg),这里的msg.target 是发送这条消息的Handler对象,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了,但是这里不同的是,Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了。
Looper退出方法:quit和quitSafely。区别 quit会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全地退出。
再说两句
Handler使用异步来实现的,涉及到Handler、Looper、MessageQueue、Message等概念,需要更深入了解,才能更好发挥它的用处,封装出性能更好的框架。当使用只有一个后台异步处理时,相较于AsyncTask就会显得结构有些复杂。
推荐阅读
-
使用 PHP 消息队列实现 Android 与 Web 通信
-
Activemq中的KahaDB消息日志的恢复机制
-
Android采用消息推送实现类似微信视频接听
-
Android中handler倒计时
-
Android中ACTION_CANCEL的触发机制与滑出子view的情况
-
Android Handler的使用详解
-
Android的线程通信:消息机制原理(Message,Handler,MessageQueue,Looper),异步任务AsyncTask,使用JSON
-
iOS开发系列--通知与消息机制详解
-
Android垃圾回收机制及程序优化System.gc
-
详解Android Scroller与computeScroll的调用机制关系