Android Handler机制的工作原理详析

写在前面

上一次写完Binder学习笔记之后,再去看一遍Activity的启动流程,因为了解了Binder的基本原理,这次看印象会更深一点,学习效果也比以前好很多。本来打算直接来写Activity的启动流程的,但总觉得Handler也需要写一下,知道Handler和Binder的原理后,再去看Activity的启动流程,应该也没什么问题了。虽然网上已经有很多Handler相关的文章了,而且Handler机制的上层原理也并不难,还是决定写一下,因为我想构建自己的知识体系。也希望给看我博客的朋友们一个无缝衔接的阅读体验。

Handler机制涉及到的类主要有Handler、Message、Looper、MessageQueue、ThreadLocal等。虽然我们最熟悉的是Handler和Message这两个类,但是在我们开始可以使用Handler之前,Looper是为我们做了一些事情的。

本文的源码是基于android-28的

Looper

在使用Handler之前,我们必须得初始化Looper,并让Looper跑起来。

Looper.prepare();
...
Looper.loop();

执行上面两条语句之后,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));
}

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

必须保证一个线程中有且只有一个Looper对象,所以在初始化Looper的时候,会检查当前线程有没有Looper对象。Looper的初始化会创建一个MessageQueue。创建完Looper后会放到ThreadLocal中去,关于ThreadLocal,后面会说到。

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;

 ...

 for (;;) {
 Message msg = queue.next(); // might block
 if (msg == null) {
 // No message indicates that the message queue is quitting.
 return;
 }

 ...

 final long traceTag = me.mTraceTag;

 if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
 Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
 }

 try {
 // target指的是Handler
 msg.target.dispatchMessage(msg);
 } finally {
 if (traceTag != 0) {
 Trace.traceEnd(traceTag);
 }
 }

 ...

 msg.recycleUnchecked();
 }
}

方法比较长,所以只把最核心的代码放了出来。省略掉的代码中有一个比较有意思的:我们可以指定一个阈值比如说200,当Message的处理超过200ms时,就会输出Log。这可以在开发中帮助我们发现一些潜在的性能问题。可惜的是,设置阈值的方法是隐藏的,无法直接调用,所以这里就不放出代码了,感兴趣的朋友自己翻一下源码吧。

简化后的代码可以看出逻辑十分简单,可以说Looper在当中扮演着搬砖工的角色,从MessageQueue中取出Message,然后交给Handler去分发,再去MessageQueue中取出Message...无穷无尽,就像愚公移山一样。

看到这里,应该多多少少会觉得有点不对劲,因为这里是一个死循环,按道理来说会一直占着CPU资源的,并且消息也总有处理完的时候,难道处理完就从消息队列返回Null,然后Looper结束吗?显然不是,注意看注释might block。

MessageQueue

答案就在MessageQueue里面,直接来看一下next():

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

 // Process the quit message now that all pending messages have been handled.
 if (mQuitting) {
 dispose();
 return null;
 }

 // If first time idle, then get the number of idlers to run.
 // Idle handles only run if the queue is empty or if the first message
 // in the queue (possibly a barrier) is due to be handled in the future.
 if (pendingIdleHandlerCount < 0
 && (mMessages == null || now < mMessages.when)) {
 pendingIdleHandlerCount = mIdleHandlers.size();
 }
 if (pendingIdleHandlerCount <= 0) {
 // No idle handlers to run. Loop and wait some more.
 mBlocked = true;
 continue;
 }

 if (mPendingIdleHandlers == null) {
 mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
 }
 mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
 }

 // Run the idle handlers.
 // We only ever reach this code block during the first iteration.
 for (int i = 0; i < pendingIdleHandlerCount; i++) {
 final IdleHandler idler = mPendingIdleHandlers[i];
 mPendingIdleHandlers[i] = null; // release the reference to the handler

 boolean keep = false;
 try {
 keep = idler.queueIdle();
 } catch (Throwable t) {
 Log.wtf(TAG, "IdleHandler threw exception", t);
 }

 if (!keep) {
 synchronized (this) {
 mIdleHandlers.remove(idler);
 }
 }
 }

 // Reset the idle handler count to 0 so we do not run them again.
 pendingIdleHandlerCount = 0;

 // While calling an idle handler, a new message could have been delivered
 // so go back and look again for a pending message without waiting.
 nextPollTimeoutMillis = 0;
 }
}

代码有点长,这次不打算省略掉一些了,因为这里面还有一个小彩蛋。

方法中最重要的应该就是这一行了

nativePollOnce(ptr, nextPollTimeoutMillis);

简单来说,当nextPollTimeoutMillis == -1时,挂起当前线程,释放CPU资源,当nextPollTimeoutMillis >= 0时会延时指定的时间激活一次线程,让代码继续执行下去。这里涉及到了底层的pipe管道和epoll机制,就不再讲下去了(其实是因为讲不下去了)。这也就可以回答上面的问题了,当没有消息的时候只需要让线程挂起就行了,这样可以保证不占用CPU资源的同时保住Looper的死循环。

然后我们来看消息是如何取出来的。MessageQueue中有一个Message,Message类中又有一个Message成员next,可以看出Message是一个单链表结构。消息的顺序是根据时间先后顺序排列的。一般来说,我们要取的Message就是第一个(这里先不考虑异步消息,关于异步消息以后会讲到的,又成功给自己挖了一个坑哈哈),如果当前时间大于等于Message中指定的时间,那么将消息取出来,返回给Looper。由于此时nextPollTimeoutMillis的值为0,所以当前面的消息处理完之后,Looper就又来取消息了。

如果当前的时间小于Message中指定的时间,那么设置nextPollTimeoutMillis值以便下次唤醒。还有另外一种当前已经没有消息了,nextPollTimeoutMillis会被设置为-1,也就是挂起线程。别急,还没那么快呢,接着往下看。

紧接着的逻辑是判断当前有没有IdleHandler,没有的话就continue,该挂起就挂起,该延时就延时,有IdleHandler的话会执行它的queueIdle()方法。这个IdleHandler是干什么的呢?从名字应该也能猜出个一二来,这里就不再展开讲了。关于它的一些妙用可以看我之前写的Android 启动优化之延时加载。执行完queueIdle()方法后,会将nextPollTimeoutMillis置为0,重新看一下消息队列中有没有新的消息。

Handler

上面将取消息的流程都讲清楚了,万事俱备,就差往消息队列中添加消息了,该我们最熟悉的Handler出场了。Handler往队列中添加消息,主要有两种方式:

Handler.sendXXX();
Handler.postXXX();

第一种主要是发送Message,第二种是Runnable。无论是哪种方式,最终都会进入到MessageQueue的enqueueMessage()方法。

boolean enqueueMessage(Message msg, long when) {
 ...

 synchronized (this) {
 ...

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

一般情况下,我们通过Handler发送消息的时候,会通过SystemClock.uptimeMillis()获取一个开机时间,然后MessageQueue就会根据这个时间来对Message进行排序。所以enqueueMessage()方法中就分了两种情况,一种是直接可以在队头插入的。一种是排在中间,需要遍历一下,然后寻一个合适的坑插入。when == 0对应的是Handler的sendMessageAtFrontOfQueue()和postAtFrontOfQueue()方法。needWake的作用是根据情况唤醒Looper线程。

上面有一点还没有讲,就是Looper从MessageQueue中取出Message后,会交由Handler进行消息的分发。

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

优先级顺序是Message自带的callback,接着是Handler自带的callback,最后才是handleMessage()这个回调。

ThreadLocal

还记得Looper中有一个ThreadLocal吧,把它放到最后来讲是因为它可以单独拿出来讲,不想在上面干扰到整个流程。

ThreadLocal是一个数据存储类,它最神奇的地方就是明明是同一个ThreadLocal对象,但是在不同线程中可以存储不同的对象,比如说在线程A中存储了"Hello",而在线程B中存储了"World"。它们之间互相不干扰。

在Handler机制中,由于一个Looper对应着一个线程,所以将Looper存进ThreadLocal最合适不过了。

ThreadLocal比价常用的就set()和get()方法。分别来看看怎么实现的吧。

public void set(T value) {
 Thread t = Thread.currentThread();
 ThreadLocalMap map = getMap(t);
 if (map != null)
 map.set(this, value);
 else
 createMap(t, value);
}

首先是去获取ThreadLocalMap,找得到的话直接设置值,找不到就创建一个。

ThreadLocalMap getMap(Thread t) {
 return t.threadLocals;
}

看到这里,大概也能明白了。每个线程Thread中有一个ThreadLocalMap对象。通过ThreadLocal.set()方法,实际上是去获取当前线程中的ThreadLocalMap,线程不同,获取到的ThreadLocalMap自然也不同。
再来看看这个ThreadLocalMap是什么来头。看类的注释中有这么一句话:

ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.

从注释中可以知道这就是一个定制的HashMap,并且它的Entry类指定了Key只能为ThreadLocal类型的。所以直接将它看成是一个HashMap就好了。

get()方法也好理解,就是从Map中取出值而已。大概看一下就好了。

public T get() {
 Thread t = Thread.currentThread();
 ThreadLocalMap map = getMap(t);
 if (map != null) {
 ThreadLocalMap.Entry e = map.getEntry(this);
 if (e != null) {
 @SuppressWarnings("unchecked")
 T result = (T)e.value;
 return result;
 }
 }
 return setInitialValue();
}

写在最后

虽然在开始写之前,觉得Handler机制比较简单,好像没啥必要写,但真正要写起来的时候还是得去深入了解代码的细节,然后才发现有些地方以前理解得也不够好。能理解和能写出来让别人理解,其实是不同的层次了。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Android传感器SensorEventListener之加速度传感器

    这个类(我的是Activity中)继承SensorEventListener接口 先获取传感器对象,再获取传感器对象的类型 //获取传感器管理对象 SensorManager mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); // 获取传感器的类型(TYPE_ACCELEROMETER:加速度传感器) Sensor mSensor = mSensorManager.getDefaultSensor(

  • Android四大组件之Activity详解

    一.Activity的生命周期 首先,我们来了解一下Activity典型的生命周期 一个Activity从启动到结束会以如下顺序经历整个生命周期: onCreate()->onStart()->onResume()->onPause()->onStop()->onDestory().包含了六个部分,还有一个onRestart()没有调用, 下面就来一一介绍 onCreate():当 Activity 第一次创建时会被调用.当 Activity 第一次创建时会被调用.这是生命周

  • 深入Android HandlerThread 使用及其源码完全解析

    关联篇:深入Android的消息机制源码详解-Handler,MessageQueue与Looper关系 关联篇:Handler内存泄漏及其解决方案 本篇我们将来给大家介绍HandlerThread这个类,以前我们在使用线程执行一个耗时任务时总会new一个Thread的线程去跑,当任务执行完后,线程就会自动被销毁掉,如果又由新的任务,我们又得新建线程.....我们假设这样的一个情景,我们通过listview去加载图文列表,当我们往下滑动时,这时需要不断去请求网络资源,也就是需要不断开线程去加载

  • Android使用Handler实现定时器与倒计时器功能

    什么是Handler Handler是Android消息机制的上层接口,它为我们封装了许多底层的细节,让我们能够很方便的使用底层的消息机制.Handler的最常见应用场景之一便是通过Handler在子线程中间接更新UI.Handler的作用主要有两个:一是发送消息:二是处理消息,它的运作需要底层Looper和MessageQueue的支撑.MessageQueue即消息队列,它的底层用单链表实现:Looper则负责在一个循环中不断从MessageQueue中取消息,若取到了就交由Handler进

  • Android亮屏速度分析总结

    前面聊的 最近在调试项目的亮屏速度,我们希望在按下power键后到亮屏这个时间能达到500MS以内,在Rockchip 3399和3288上面的时间都不能达到要求,因此引发了一系列的调试之路. 计算按下power键到亮屏的时间 Android 唤醒时间统计 刚开始的时候,我只在android阶段统计时间,也能看到时间的差异,但是不是最准确的,我统计的时间日志如下 01-18 09:13:40.992 683 772 D SurfaceControl: Excessive delay in set

  • 详解Android平台JSON预览(JSON-handle)

    开发中需要用到json,在浏览器显示的json非常乱,难以理解.有没有让人一目了然的工具,让json看起来非常直观呢,json-handle随之而出,包含火狐和chrome两种插件,官方地址:http://jsonhandle.sinaapp.com/ Chrome常用的插件 JSON-handle,用过的都知道. 对于经常在浏览器调试json的你,json-handle是个不二的选择.最近在做接口加密,所有的数据( request 和 response )都是加密数据,无法沟通 fildder

  • 详解Android使用Handler造成内存泄露的分析及解决方法

    一.什么是内存泄露? Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收:另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收. Android中使用Handler造成内存泄露的原因 private Han

  • Android使用Handler实现打地鼠游戏

    本文实例为大家分享了Android使用Handler实现打地鼠的具体代码,供大家参考,具体内容如下 1.实现效果 如下图所示: 2.代码实现 新建一个名为DiglettDemo的项目,activity_main.xml代码如下: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/a

  • Android实例HandlerThread源码分析

    HandlerThread 简介: 我们知道Thread线程是一次性消费品,当Thread线程执行完一个耗时的任务之后,线程就会被自动销毁了.如果此时我又有一 个耗时任务需要执行,我们不得不重新创建线程去执行该耗时任务.然而,这样就存在一个性能问题:多次创建和销毁线程是很耗 系统资源的.为了解这种问题,我们可以自己构建一个循环线程Looper Thread,当有耗时任务投放到该循环线程中时,线程执行耗 时任务,执行完之后循环线程处于等待状态,直到下一个新的耗时任务被投放进来.这样一来就避免了多次

  • Android线程中Handle的使用讲解

    Android UI线程是不安全的,子线程中进行UI操作,可能会导致程序的崩溃,解决办法:创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了 定义类继承Handler public class BallHandler extends Handler{ ImageView imageview; Bitmap bitmap; public BallHandle

随机推荐