前言
今天为大家叙述Android中消息处理机制Handler,本篇文章内容属于Android范畴,所以只懂Java不懂Android的同学可以选择绕道。
1 Handler基本使用
1.1 Handler概念
Handler是Android中内置的一种消息处理的机制,那什么又是消息处理机制呢?一般来说在进行异步操作的时候会用到消息处理机制,打个比方:现在有个需求需要从网络上请求一张图片,然后将这张图片显示在屏幕上,在Android中网络请求需要放在子线程,而图片显示又必须在主线程中进行,这样就会产生一种冲突,怎样解决这种冲突呢?前两节我们讲的线程间通信是时候派上用场了,没错,通过线程间通信可以解决这个问题,但是,我每一次加载图片都要噼里啪啦自己去写线程间通信,一旦有特殊需求还要对其改东改西,是不是也太繁琐了?Google爸爸早就想到这一点了,所以给我们提供了Handler来解决这个问题,当从网络请求到数据后通过Handler将数据发送给主线程,主线程拿到数据后作出相应的操作即可,非常的方便。
1.2 Handler的基本使用
说完了Handler基本概念再来说一下Handler基本用法:
首先在需要接收消息的线程中创建一个Handler,此处我们在主线程中创建
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 1:
//obj即为另一个线程发送来的数据
Object obj = msg.obj;
Log.i("myHandler", obj+" threadName "+Thread.currentThread().getName());
break;
}
}
};
发送消息
new Thread(new Runnable() {
@Override
public void run() {
//可进行耗时操作
Message message = new Message();
message.what = 1;
message.obj= 1;
handler.sendMessage(message);
}
}).start();
打印结果
I/myHandler: 消息 threadName main
最后接收数据是在主线程中进行的,完美实现线程间通信,操作也非常简单。
Handler还有很多用法,本篇文章侧重点为Hanlder源码部分,所以对Hnadler基本使用就不一一列举,感兴趣的同学可以自行了解。
2 Handler源码分析
2.1 Handler机制组成部分
Android中的Handler机制是由四部分组成的,分别是:
- Handler:用来发送消息和处理消息
- Message:消息的载体
- MessageQueue:存储消息的队列
- Lopper:用来衔接MessageQueue和Handler
下面我对这四部分源码逐个进行解析
2.2 Message
Message承载消息形式
Message是一个消息载体,在实际线程间通信中需要传递的数据类型五花八门,Android也考虑到了这一点,所以为我们提供了能够承载任意格式数据的Message。
消息类型大概有以下几种:
- int what:一般用于设置消息码,用于区分不同的消息
- Object obj:可以承载任意类型的对象
- Bundle data:以键值对的形式进行存储数据
能够承载的类型非常丰富,基本可以满足我们实际开发需求
Message创建方式
在Message中可以通过直接new Message()来获取一个Message对象,但Message还给我们提供了另一种创建方式:通过调用obtain()
我们来看一下obtain()源码
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
注释:从全局池返回一个新的消息实例。允许我们避免在很多情况下分配新对象。
Message缓存
obtain()注释中提到了消息池的概念,也就是说通过obtain()获取Message可以对消息进行复用,那么它内部是这样对消息进行复用的呢?我们再来分析一下obtain()里面的内容:
熟悉数据结构的同学可能已经看出来,内部缓存是通过一个单向链表来实现的,
- 首先取出链表的第一个节点也就是头节点mPool(Message m = sPool;)
- 取出后将mPool指向下一节点 (sPool = m.next;)
- 把取出的节点后继(next)置为空 ( m.next = null;)
- 将flags置为0,用于更改缓存标识( m.flags = 0; // clear in-use flag)
- 将链表的长度sPoolSize减1(sPoolSize--;)
下面我再来结合一张草图给大家分析Message从缓存取出过程:
取消息之前内部缓存为链表A,MessageA为头节点,执行取出操作后把MessageA取出,将其next置为null,然后将MessageB赋值给头结点sPool,链表B为取出Message后的内部缓存结构,整个流程就是这样,很简单。
说完了从缓存中取消息再来说一下将消息加入缓存
加入缓存是通过调用recycle()方法进行的,来看一下源代码:
public void recycle() {
//判断当前是否处于可回收状态
if (isInUse()) {
//判断当前SDK版本
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
//当前是否处于可回收状态
boolean isInUse() {
return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
}
//版本判断
public static void updateCheckRecycle(int targetSdkVersion) {
if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
gCheckRecycle = false;
}
}
首先判断当前是否处于可回收状态(Message正在被MessageQueue中或者正在被Looper处理是不可以进行回收的),如果不可回收再判断当前版本,版本大于等于LOLLIPOP就抛出异常,小于LOLLIPOP不做处理直接return。
如果当前处于可回收状态,执行recycleUnchecked()方法进行回收操作
在这不要纠结这个版本判断,Google只是在后来认为如果消息处于不可回收状态你偏要回收,这时就抛一个异常给你,让你知道你这样做是错误的,一般来说尽量不要手动调用recycle()进行回收,因为Looper会在内部在合适的时机调用recycleUnchecked()来对消息进行回收。
我们来看一下recycleUnchecked()源码:
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
//判断缓存链表是否达到最大长度
if (sPoolSize < MAX_POOL_SIZE) {
//将后继指向之前的头节点
next = sPool;
//将当前消息节点置为头节点
sPool = this;
//链表长度加1
sPoolSize++;
}
}
}
首先将消息中的各种属性清空,然后再判断当前缓存链表是否小于最大缓存长度MAX_POOL_SIZE(默认为50),如果小于最大缓存长度,将当前消息节点置为头节点,最后把链表长度加1。
Message的源码差不多就是这个样子,可以直接通过new Message()获取一个消息载体,但如果频繁发送消息,建议使用obtain()获取Message。
2.3 Looper
在写这一小节之前我纠结了很久,因为Handler、MessageQueue、Looper三者关系犹如一组三角恋,互相都有着说不清的关系,到底先写哪一个呢?网络上大部分文章都是首先分析MessageQueue,我觉得这样不是很妥当,因为MessageQueue是依附于Looper存在的,并且Looper是 MessageQueue与Handler的桥梁,所以我决定首先分析Looper的源码。
讲源码之前先给分析一张图(该图摘自这篇文章),来排除大家的一些疑点
首先Handler通过sendMessage()发送一个消息到消息队列MessageQueue中,然后Looper会从消息队列中取消息,如果取到就讲消息叫由Handler处理,整个流程就是这样。下满我们开始源码部分
首先需要说明一点,一个线程只允许有一个Looper,而主线程中的Looper不需要我们自己创建,Android已经帮我们创建。下面来看一下Android程序的入口ActitivtyThread的main()方法的伪代码(只截取了部分与Looper有关的代码):
public static void main(String[] args) {
//为主线程创建一个Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//开启一个Looper
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
首先Looper调用了自身的静态方法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() {
//为当前线程创建一个Looper
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//获取当前线程也就是主线程的Looper
sMainLooper = myLooper();
}
}
注释大致意思:为主线程创建一个Looper,这个方法由Android系统调用,开发者永远不要手动调用这个方法。
在叙述这段代码之前我先为大家普及一段概念,前面我们也说到,Looper在每个线程是独立存在的,Looper的线程独立是怎么实现的呢?其实是通过ThreadLocal进行实现的,那什么是ThreadLocal呢?在这我跟大家简单描述一下,ThreadLocal有两个重要方法,一个set(obj)、一个get(),调用set(obj)方法可以往当前线程存入一个对象obj,而get可以获取到当前线程的obj对象,举个例子:
现在有线程A和线程B,线程A通过ThreadLocal调用了set(objA),线程B通过ThreadLocal调用了set(objB),在线程A调用ThreadLocal的get()方法会获取到objA,在线程B调用ThreadLocal的get()方法会获取到objB,就是这么简单。
讲完了线程独立我们接着往下分析,先来看prepare(false)源码:
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//创建一个Looper对象存入到当前线程中
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
//创建消息队列MessageQueue
mQueue = new MessageQueue(quitAllowed);
//获取当前线程
mThread = Thread.currentThread();
}
通过调用prepare()方法可以往当前线程存于一个Looper对象,如果Looper已在当前线程存在,再次调用会抛出异常。我们可以看到,消息队列MessageQueue是跟随Looper一同创建的,这样也就保证了Looper和MessageQueue线程中一对一的关系。
为主线程初始化Looper后紧接着会调用myLooper()获取到当前线程的Looper
public static @Nullable Looper myLooper() {
//获取当前线程的Looper对象
return sThreadLocal.get();
}
最后将myLooper()返回的Looper对象赋值给sMainLooper主线程Looper就创建完毕。
如果我们需要在子线程中创建Looper步骤基本类似,可以调用prepare()方法初始化Looper,调用myLooper()获取到Looper即可。
前面我们有提到,Looper是Handler和MessageQueue的桥梁,负责从MessageQqueue中取消消息然后交由Handler处理,那么它是怎样实现这种操作的呢?
在ActivityThread的main()方法中调用了Looper的一个方法loop(),那这个方法是干什么用的呢?我们来看一下源代码:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
//获取到当前的Looper对象
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
//如果通过next()取出的结果为null代表消息队列已经退出则跳出循环
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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
//将消息交给Handler处理
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
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();
}
}
整体的流程就是,通过调用myLooper()方法获取到当前线程的Looper对象,然后通过me.mQueue获取到对应的消息队列MessageQueue,获取完毕后开启一个无限的for(;;)循环,每次循环都会从MessageQueue中取出一个消息,注意next()方法为阻塞式方法,如果取出的消息为空就代表消息队列已经结束跳出循环结束方法,如果取到了消息,通过msg.target获取到发送消息的Handler,单后将消息发送给Handler进行处理,最后会调用Message的recycleUnchecked()对消息进行回收。
处理消息流程差不多就是这个样子。在Looper中也给我们提供了停止Looper的方法,通过调用其quit() 方法
public void quit() {
mQueue.quit(false);
}
其实最终调用的还是MessageQueue的quit(),具体怎么退出我再MessageQueue中再详细讲述。
疑点
通过上面的叙述我们知道了一个Android程序启动的时候会在主线程创建一个Looper对象,并且会调用Looper的loop()方法,而loop()方法又是一个没有结束标记的死循环,按照我们常规的思维方式执行了loop()方法后主线程会被一直阻塞出现卡死现象,但事实上并不是这样的,我们Activity的启动View的渲染都是在主线程中进行的,说明主线程没有卡死,关于这点Android是怎么处理的呢?这牵扯到了Android Binder IPC和Linux pipe/epoll机制,由于我学艺不精就不在这误人子弟了,如果真的想了解可以去阅读这篇文章,这里会给你一个满意的答案。
本来是准备一篇文章把消息处理机制写完,写到这发现内容是有点多,由于篇幅原因MessageQueue和Handler源码我会在下篇文章进行描述,下篇文章,不见不散。