Handler消息机制 源码解读
2016-05-16 09:02
337 查看
基本概念
Handler消息机制的作用
大家知道子线程没有办法对UI界面上的内容进行操作,如果操作,将抛出异常:CalledFromWrongThreadException,为了让子线程能间接操作UI界面,Android中引入了Handler消息传递机制,通过Handler切换到主线程进行UI操作。Handler、Looper、MessageQueue、Message的关系是什么?
Handler用于发送和处理消息。而发出的Message经过一系列的周转后,最终会传递回Handler中,最后更新UI。Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之前交换数据。
MessageQueue是消息队列,用于存放Message。Message在消息队列中,等待Looper取出。每个线程中只会有一个MessageQueue对象。
Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个无限循环中,每当MessageQueue中存在一个Message,Looper对象就会将其取出,传递到Handler中进行处理。每个线程中只会有一个Looper对象。
Handler消息机制疑问
从上面的基本概念中,我们不难会产生疑问,比如:在Message的周转过程中,是怎么切换到主线程的?
如果当前线程中new了多个Handler,它们发送消息后会错乱吗?会不会找错Handler对象。
Handler的post方法和sendMessage有什么区别?
怎么保证每个线程中只能存在一个Looper和MessageQueue?
子线程为什么不能直接new Handler?
主线程为什么不用手动创建Looper?
主线程中的Looper无限循环,为什么没有造成ANR?
下面,我将从源码角度,对以上疑问进行探索。看完本篇博客,相信你心里就会有答案了。
初识API
Message
我们先来简单认识一下Messagepublic int what; public int arg1; public int arg2; public Object obj;
上面几个的用法,大家都知道,就不用介绍了。我们来看看下面的。
Handler target; Runnable callback;
Message中可以保存Handler对象,也可以保存Runnable,具体用法看完本篇博客就知道了。
还记得
Message.obtain用法吗?
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; sPoolSize--; return m; } } return new Message(); }
因为Message被处理完过后,是不会直接回收的,默认会保存一定数量的Message以供复用。我们可以使用
Message.obtain复用,以免创建多余的Message。
MessageQueue
MessageQueue是什么?我们可以给它理解成一个集合,比如List,我们可以添加消息,也可以读取消息,移除消息。当然MessageQueue的内部是通过一个单链表的数据结构来实现的,理解起来可能有点费劲,我们只需知道有两个重要的方法enqueueMessage插入消息,
next取出消息。
//======================插入消息================================= boolean enqueueMessage(Message msg, long when) { //... //省略了部分代码 msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; } //======================取出消息================================= Message next() { //... //省略了部分代码 int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } //... //省略了部分代码 } }
工作原理
发送消息(将Message提交到MessageQueue)
Handler平时用的都比较多,一般都会直接使用mHandler.sendMessage进行发送消息,然后重写Handler的
handleMessage进行接收和处理消息。那么
mHandler.sendMessage到底做了什么事呢?
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); }
我们可以发现,在Handler内部,其实调用的是
sendMessageDelayed,然后
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中我们看到了什么?没错
MessageQueue,而且
MessageQueue不可为空,否则会抛出异常,你可能为疑问,这个
MessageQueue是从哪里来的,不要急,下面马上就会介绍,在Handler的构造方法那里就能看到。我们暂且不管
MessageQueue是怎么工作的,我们只需知道,我们当前的任务是将
Message塞进
MessageQueue中,我们接下来看看
enqueueMessage中做了什么。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
我们可以发现,这里调用了MessageQueue中的
queue.enqueueMessage方法将Message插入到队列中去,到此为止,我们完成了第一步。
我们还能发现这一行
msg.target = this;,没错,将当前的Handler对象绑定给了Message,这也就能解释,为什么Message一番周转之后,仍然知道该传递给哪个Handler对象。
取出消息(将Message从MessageQueue中取出)
将Message塞给了
MessageQueue后,现在就该轮到
Looper登场了。Looper是怎么从
MessageQueue取出中取出
Message的呢。
Looper的属性
先来简单看一下Looper的三个属性。static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; final MessageQueue mQueue;
可以看出
Looper中会绑定一个对应的
MessageQueue,还有一个线程变量
sThreadLocal。
子线程中为什么不能直接创建Handler
在介绍之前,我们先来解释下,为什么不能直接在子线程中new Handler。大家都知道在线程中使用Handler如下,但是可能不知道为什么要这么写。
Looper.prepare(); Handler handler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; Looper.loop();
那么,我们先来看一下Handler构造方法,Handler构造方法有两种,一种是显示指定Looper对象的,另一种是不显示指定Looper的(会默认获取当前线程的Looper),所有不显示指定Looper的构造方法都会在内部转为调用以下构造。
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; }
我们可以发现只要mLooper为空,就会抛出异常。不为空的话就连同
MessageQueue赋值给当前Handler,你可能又想问,Looper的
MessageQueue是怎么来的,莫急,待会会介绍Looper的构造方法。我们先看看
Looper.myLooper()方法
public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
sThreadLocal是什么鬼?
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
看到这里终于明白了,原来用了
ThreadLocal啊,
ThreadLocal是Java用来存储线程变量的api。也就是说,假如我们在主线程使用
sThreadLocal存储了一个Looper,在子线程也使用了
sThreadLocal存储了一个Looper,他们互不干扰。每个线程取出来的值都是不一样的。这也就保证了,每一个线程都有一个单独的Looper。
那么如何在子线程使用Handler呢?相信大家都有思路了,只要保证
sThreadLocal.get()能取到值就行,我们可以在new 之前给当前线程一个Looper对象。api中已经提供了方法,
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)); }
而且也可以看出,每个线程中只能设置一个Looper对象,否则还是会抛出异常。为什么要强制只能有一个Looper对象呢?当然是因为Android的单线程模型啊。如果允许多个Looper那么和在子线程中直接处理没有任何区别,会导致一系列的并发问题。
sThreadLocal.set(new Looper(quitAllowed))给当前线程绑定一个Looper,我们来看一下Looper的构造。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
从源码可以看出,与此同时,也给Looper绑定了一个MessageQueue对象。
Looper取消息
那么,Looper到底是怎么从MessageQueue中取出Message的呢。我们来看下Looper.loop()
public static void loop() { final Looper me = myLooper();//获取当前线程的Looper if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; //获取Looper对应的消息队列 //... //省略了部分代码 for (;;) {//死循环 Message msg = queue.next(); //从消息队列中取出一下个Message //... //省略了部分代码 msg.target.dispatchMessage(msg);//msg.target就是Handler,调用Handler的dispatchMessage方法处理消息 //... //省略了部分代码 msg.recycleUnchecked();//回收Message } }
流程非常清晰,通过一个死循环,不停调用
MessageQueue的
next()方法,取出Message,然后看到了没
msg.target,前面我们发送消息时,绑定的Handler对象。经历了一番周转变换了线程,又交给了Handler对象的
dispatchMessage中进行处理消息。
处理消息(Handler调用dispatchMessage
处理消息)
在介绍处理消息之前,我们先来认识一下Handler的其他使用方法。我们知道只要重写Handler的handleMessage方法,然后就可以接收消息了。但是,我如果不想重写
handleMessage呢?有没有其他方法?当然是有的,请往下看。
Handler的构造参数
前面我也提到了,Handler的构造方法分为两种,显示指定Looper与否,其实这两种都是一样的,没有本质上的区别。但是我们忽略了Handler构造方法中的其他参数,现在再来看一遍。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; }
有没有发现一个Callback参数,其实它是一个接口,也是用于处理消息的。
public interface Callback { public boolean handleMessage(Message msg); }
使用mHandler.post发送消息。
mHandler.sendMessage这种方法大家都很常用,也很简单。大家都知道Handler有个post方法,那么它和sendMessage有什么区别呢?我们来看一下源码。
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }
从上面可以看出,使用post,仍然会包装为Message,然后调用
sendMessageDelayed进行发送消息。先来看一下到底是怎么包装的,如下
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
包装给了一个callback,就是前面提到的Runable。
Handler处理消息
在介绍完Handler的其他用法后,现在回到Handler的dispatchMessage中。
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
看到没有,
if (msg.callback != null),也就是说,如果是使用post发送的,会调用
handleCallback进行处理。
private static void handleCallback(Message message) { message.callback.run(); }
直接走了Runable的run方法。不会走Handler的
handleMessage。
然后
if (mCallback != null)判断那个接口有没有实现,如果实现了,直接走接口。最后才是调用Handler内部的
handleMessage方法。
最后
主线程为什么不用手动创建Looper?我们来看一个类
ActivityThread,这个类是Android的入口,我们可以找到一个久违的方法
public static void main(String[] args)。
public static void main(String[] args) { //... //省略了部分代码 Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); //... //省略了部分代码 Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
从上面可以看出,Android在启动一个应用时,已经默认给主线程开启了Looper。
主线程中的Looper无限循环,为什么没有造成ANR?
什么情况下会造成ANR呢?只有当主线程的Handler处理一个消息耗时过长才会ANR。
Looper无限循环,不是一个简单的死循环,当消息队列中没有消息时,就会阻塞休眠,释放cpu资源。
我们Activity之所以有生命周期,也依赖于Handler,是因为
ApplicationThread根据系统
ActivityManageService发来的事件,然后发送消息来调度生命周期的。以下是
ActivityThread中处理消息相关的部分代码。
public void handleMessage(Message msg) { //.. //省略了部分代码 switch (msg.what) { case LAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); final ActivityClientRecord r = (ActivityClientRecord) msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; case PAUSE_ACTIVITY: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2, (msg.arg1&2) != 0); maybeSnapshot(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; //.. //省略了部分代码 } }
END
相关文章推荐
- 华为OJ——字串的连接最长路径查找
- 计数排序(JAVA)
- TCP之————三次握手和四次挥手
- 第十一周Android学习笔记
- 通过layer设置圆角
- 通过添加uiTOOLBAR制作毛玻璃效果
- 播放远程音乐
- SqlServer2008--建立触发器实例
- 第十二周项目1:阅读程序并写出结果(3)
- 上传本地项目到github
- Ex2010-08 Rapid Migration Ex2010 to Ex2013
- 第十二周实践项目1————实现复数类中的运算符重载之综合
- 设置控制器支持方向
- 应用程序在后台时,也可以更新界面
- 系统自带分享
- JavaScript代码性能优化总结(推荐)
- 一些ios牛人的博客
- C/C++报错
- 三种视频播放方式
- iPhone 屏幕尺寸对于像素点