使用过AsyncTask、EventBus、Volley以及Retrofit,必须好好了解handler运行机制
我们都知道在UI线程中不能进行耗时操作,例如数据读写、网络请求。Android 4.0开始,在主线程中进行网络请求甚至会抛出Android.os.NetworkOnMainThreadException。这个时候,我们就会开始依赖Handler。我们在子线程进行耗时操作后,将请求结果通过Handler的sendMessge**() 方法发送出去,在主线程中通过Handler的handleMessage 方法处理请求结果,进行UI的更新。
后来随着AsyncTask、EventBus、Volley以及Retrofit 的出现,Handler的作用似乎被弱化,逐渐被大家遗忘。其实不然,AsyncTask其实是基于Handler进行了非常巧妙的封装,Handler的使用依然是其核心。Volley同样也是使用到了Handler。因此,我们有必要了解一下Handler的实现机制。
神奇的Handler
记得很久之前的一天,我在阅读别人的代码时,看到了这样一段:
- new Handler().postDelayed(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(mContext, "I'm new Handler !", Toast.LENGTH_SHORT).show();
- }
- }, 1000);
第一印象就是,这不是在子线程中进行UI操作吗?这代码有问题吧,于是乎立刻在自己电脑上写了个demo试了一下,结果发现真的没有问题。在一阵懵逼过后,我又写出下面的代码,测试一下子线程中到底能不能进行UI操作。
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();
- }
- }).start();
结果很明显,程序一启动立刻就奔溃了。并抛出异常java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
。
于是乎我又在try block 之前添加了Looper.prepare()这行代码。再次运行程序虽然没有奔溃,但也没有任何反应,Toast也没显示。
那么Handler到底是什么呢?他怎么就这么神奇。
实现机制解析
首先,我们从整体上了解一下,在整个Handler机制中所有使用到的类,主要包括Message,MessageQueue,Looper以及Handler。
好了,为了方便后面的叙述,我们就首先了解一下这个类图中使用到几个类,及其关键方法。
Message
首先看一下Message这个类的定义(截取部分)
- public final class Message implements Parcelable {
- public int what;
- public int arg1;
- public int arg2;
- public Object obj;
- /*package*/ Handler target;
- /*package*/ Runnable callback;
- /**
- * 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();
- }
- /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
- */
- public Message() {
- }
- }
看到这个类的前四个属性,大家应该很熟悉,就是我们使用Handler时经常用到的那几个属性。用来在传递我们特定的信息。其次我们还可以总结出以下信息:
- Message 实现了Parcelable 接口,也就是说实现了序列化,这就说明Message可以在不同进程之间传递。
- 包含一个名为target的Handler 对象
- 包含一个名为callback的Runnable 对象
- 使用obtain 方法可以从消息池中获取Message的实例,也是推荐大家使用的方法,而不是直接调用构造方法。
MessageQueue
MessageQueue顾名思义,就是上面所说的Message所组成的queue。
首先看一下构造方法:
- MessageQueue(boolean quitAllowed) {
- mQuitAllowed = quitAllowed;
- mPtr = nativeInit();
- }
接收一个参数,决定当前队列是否允许被终止。同时调用 一个native方法,初始化了一个long类型的变量mPtr。
同时,在这个类当中,还定义了一个next 方法,用于返回一个Message 。
- Message next() {
- // Return here if the message loop has already quit and been disposed.
- // This can happen if the application tries to restart a looper after quit
- // which is not supported.
- final long ptr = mPtr;
- if (ptr == 0) {
- return null;
- }
- ……
- }
由于这个方法中有一些native调用,未能完全理解,只知道会返回一个Message对象。
这个next方法相当于是队列出栈,有出栈必然有进栈,enqueueMessage 方法就是完成这个操作;这个我们后面再说。
Looper
上面说到了MessageQueue,那么这个Queue又是由谁创建的呢?其实就是Looper。关于Looper有两个关键方法:
prepare() 和 loop()
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。也就是说执行prepare方法时,必然执行最后一行代码
sThreadLocal.set(new Looper(quitAllowed));
我们再看Looper(quitAllowed)方法:
- private Looper(boolean quitAllowed) {
- mQueue = new MessageQueue(quitAllowed);
- mThread = Thread.currentThread();
- }
这样,MessageQueue 就被创建了。这里也可以看到,默认情况下,一个MessageQueue的quiteAllow=true。
这里使用到的sThreadLocal 是一个ThreadLocal对象。简单来说,使用它可以用来解决多线程程序的并发问题。使用set方法,将此线程局部变量的当前线程副本中的值设置为指定值;使用get方法,返回此线程局部变量的当前线程副本中的值。
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;
- for (;;) {
- Message msg = queue.next(); // might block
- if (msg == null) {
- // No message indicates that the message queue is quitting.
- return;
- }
- msg.target.dispatchMessage(msg);
- msg.recycleUnchecked();
- }
- }
首先看第一句代码执行的方法:
- /**
- * Return the Looper object associated with the current thread. Returns
- * null if the calling thread is not associated with a Looper.
- */
- public static @Nullable Looper myLooper() {
- return sThreadLocal.get();
- }
很明显,这样返回的Looper就是刚才prepare时set进去的那个,因为都是在同一线程。再明确一下,一个线程对应一个Looper。
这样就确保我们可以在不同的线程中创建各自的Handler,进行各自的通信而不会互相干扰
回到代码,后面逻辑就很简单了,在一个死循环中,通过队列出栈的形式,不断从MessageQueue 中取出新的Message,然后执行msg.target.dispatchMessage(msg) 方法,还记的前面Message类的定义吗,这个target属性其实就是一个Handler 对象,因此在这里就会不断去执行Handler 的dispatchMessage 方法。如果取出的Message对象为null,就会跳出死循环,一次Handler的工作整个就结束了。
Handler
上面说了这么多终于轮到Handler,那么就看看在Handler中到底发生了什么。回到我们一开始的代码。
- new Handler().postDelayed(new Runnable() {
- @Override
- public void run() {
- String currentName=Thread.currentThread().getName();
- Toast.makeText(mContext, "I'm new Thread "+currentName, Toast.LENGTH_SHORT).show();
- }
- }, 4000);
这里我们用Toast弹出了当前线程的name,结果发现这个线程的名字居然是main,这也是必然结果
让我们一步一步看看,神奇的Handler到底是怎样工作的。就从这个代码开始解读。首先看一下Handler的构造方法。
- public Handler() {
- this(null, 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();
- 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;
- }
这里做的事情很简单,就是完成了一些初始化的工作,调用Looper.myLooper()赋值给当前mLooper,关联MessageQueue;这里由于代码中调用的是不带任何参数的构造函数,因此会创建一个mCallback=null且非异步执行的Handler 。
接下看postDelayed 方法。
- public final boolean postDelayed(Runnable r, long delayMillis)
- {
- return sendMessageDelayed(getPostMessage(r), delayMillis);
- }
- private static Message getPostMessage(Runnable r) {
- Message m = Message.obtain();
- m.callback = r;
- return m;
- }
这里通过getPostMessage(Runnable r) 方法,把我们在Activity里写的Runnable 这个线程赋给了Message 的callback这个属性。
平时大家使用Handler也发现了,他为我们提供了很多方法
因此,上面的postDelayed经过了各种辗转反侧,最终来到了这里:
- 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);
- }
经过之前的构造方法,mQueue显然不为null,继续往下看
- private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
- msg.target = this;
- if (mAsynchronous) {
- msg.setAsynchronous(true);
- }
- return queue.enqueueMessage(msg, uptimeMillis);
- }
注意,注意,注意 这里进行了一次赋值:
msg.target = this;
前面提到,这个target就是一个Handler对象,因此这里Message就和当前Handler关联起来了。enqueueMessage,哈哈,这就是我们之前在MessageQueue中提到的进栈操作的方法,我们看一下:
- boolean enqueueMessage(Message msg, long when) {
- if (msg.target == null) {
- throw 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(TAG, e.getMessage(), e);
- msg.recycle();
- return false;
- }
- 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这个对象特有的一些属性,以及当前的状态是否inUse,是否已经被quit等进行一些额外的判断。
这样,我们就完成消息入队的操作。还记得我们在Looper中说过,在loop方法中,会从MessageQueue中取出Message 并执行他的dispatchMessage 方法。
dispatchMessage
- public void dispatchMessage(Message msg) {
- if (msg.callback != null) {
- handleCallback(msg);
- } else {
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- handleMessage(msg);
- }
- }
到这里,就很明确了,在之前的postDelayed 方法中,已经通过getPostMessage,实现了 m.callback = r;这样这里就会执行第一个if语句:
- private static void handleCallback(Message message) {
- message.callback.run();
- }
这样,就会执行我们在Activity 的Runnable 中的run 方法了,也就是显示Toast。
到了这里,我们终于明白了,使用Handler 的postDelay 方法时,其Runnable中的run方法并不是在子线程中执行,而是把这个Runnable赋值给了一个Message对象的callback属性,而这个Message会被传递到创建Handler所在的线程,也就是这里的主线程,所以这个Toast的显示依旧是在主线程中。这也和postDelay API 中所声明的内容是一致的。
/**
* Causes the Runnable r to be added to the message queue, to be run * after the specified amount of time elapses. * The runnable will be run on the thread to which this handler * is attached. */
到这里,一开始所说的第一个代码块所执行的逻辑已经理清楚了,但是还是有一点疑问,我们并没有在Handler的构造方法中看到Looper 的prepare()方法和loop() 方法被执行,那么他们到底是在哪里执行的呢?这个问题我也是疑惑了很久,最终才明白是在
ActivityThread的main方法中执行。简单来说,ActivityThread是Java层面一个Android程序真正的入口。关于ActivityThread更多的内容可以看看这篇文章。
ActivityThread-main方法(截取主要部分)
- public static void main(String[] args) {
- 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.loop();
- throw new RuntimeException("Main thread loop unexpectedly exited");
- }
这个类藏的比较深,你可以在Android-SDK\sources\android-24\android\app 这个目录中找到。
也就是说,在一个Android 程序启动之初,系统会帮我们为这个主线程创建好Looper。只不过这个方法名字比较特殊,叫做prepareMainLooper。
- public static void prepareMainLooper() {
- prepare(false);
- synchronized (Looper.class) {
- if (sMainLooper != null) {
- throw new IllegalStateException("The main Looper has already been prepared.");
- }
- sMainLooper = myLooper();
- }
- }
注意这里调用prepare时传递的参数值为false,和我们之前创建普通Looper时是不同的,这也 可以理解,因为这是主线程,怎么可以被允许被外部代码终止呢。
到这里,我们终于完整的理解了开头我们提到的第一个代码块的内容了。
至于第二种使用写法出错的原因也在明显不过了,主线程会在程序启动时在main方法中帮我们主动创建Looper,调用loop方法;而我们自己创建的线程就得我们主动去调用Looper.prepare(),这样才能保证MessageQueue被创建,程序不会奔溃;但是我们所期望的Toast依然没有显示出来,这是为什么呢?因为,我们没有调用loop方法。消息被加入队列了,但是没有办法弹出。因此我们将代码修改如下:
- new Thread(new Runnable() {
- @Override
- public void run() {
- Looper.prepare();
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();
- Looper.loop();
- }
- }).start();
这样就没问题了,Toast就可以显示出来了。实际上,平时写代码肯定不会这么写,这里只是为了说明问题。
handleMessage
回想一下,我们使用Handler最常见的场景:
- handler = new MyHandler();
- private class MyCallback implements Callback {
- @Override
- public void onFailure(Call call, IOException e) {
- Message msg = new Message();
- msg.what = 100;
- msg.obj = e;
- handler.sendMessage(msg);
- }
- @Override
- public void onResponse(Call call, Response response) throws IOException {
- Message msg = new Message();
- msg.what = 200;
- msg.obj = response.body().string();
- handler.sendMessage(msg);
- }
- }
上面的代码是OKHttp的回调方法,由于其回调方法不处于UI 线程,因此需要我们通过Handler将结果发送到主线中取执行。
那么这又是怎样实现的呢?
前面我们截图说过,Handler为我们提供许多sendMessage 相关的方法,因此这里我们在onResponse 中执行的sendMessage 经过层层传递,殊途同归依然会回到MessageQueue的enqueueMessage方法,也就是说所有的sendMessageXXX方法完成的工作无非就是队列入栈的工作,就是将包含特定信息的Message加入到MessageQueue中。而我们也知道,通过loop方法,会从MessageQueue中取出Message,执行每一个Message 所对应Handler的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属性必然是空的,而且在实例化Handler时,调用的是其无参构造函数 ,因此这个时候,就会执行最后一行代码handleMessage(msg) ;
- /**
- * Subclasses must implement this to receive messages.
- */
- public void handleMessage(Message msg) {
- }
空的 ! 没错,这个方法就是空的,因为这是需要我们在Handler的继承类中自己实现的方法呀。比如下面这样;
- class MyHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- loading.setVisibility(View.GONE);
- switch (msg.what) {
- case 100:
- Object e = msg.obj;
- Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();
- break;
- case 200:
- String response = (String) msg.obj;
- tv.setText(response);
- break;
- default:
- break;
- }
- }
- }
我们在handleMessage方法中,实现了自己的处理逻辑。
总结
好了,这就是Handler的实现机制,这里再做一次总结称述。
- 通过Looper的prepare方法创建MessageQueue
- 通过loop方法找到和当前线程匹配的Looper对象me
- 从me中取出消息队列对象mQueue
- 在一个死循环中,从mQueue中取出Message对象
- 调用每个Message对象的msg.target.dispatchMesssge方法
- 也就是Handler的dispatchMessage 方法
- 在dispatchMessage 根据Message对象的特点执行特定的方法。
至此,终于弄明白了Handler的运行机制。
使用过AsyncTask、EventBus、Volley以及Retrofit,必须好好了解handler运行机制的更多相关文章
- AsyncTask与ProgressDialog使用笔记(安卓在背景运行耗时任务)
AsyncTask用在需要在ui线程中调用.在背景线程中执行耗时任务.并且在ui线程中返回结果的场合.下面就是一个在背景中运行的AsyncTask的实现DownloadDBTask, Android中 ...
- 浅谈我为什么选择用Retrofit作为我的网络请求框架
比较AsyncTask.Volley.Retrofit三者的请求时间 使用 单次请求 7个请求 25个请求 AsyncTask 941ms 4539ms 13957ms Volley 560ms 22 ...
- Android实战之你应该使用哪个网络库?
前言 目前基本上每个应用都会使用HTTP/HTTPS协议来作为主要的传输协议来传输数据.即使你没有直接使用HTTP协议,也会有成堆的SDK会包含这些协议,譬如分析.Crash反馈等等.当然,目前也有很 ...
- Xutils, OKhttp, Volley, Retrofit对比
Xutils这个框架非常全面,可以进行网络请求,可以进行图片加载处理,可以数据储存,还可以对view进行注解,使用这个框架非常方便,但是缺点也是非常明显的,使用这个项目,会导致项目对这个框架依赖非常的 ...
- 安卓开发常用网络请求框架OkHttp、Volley、XUtils、Retrofit对比
网络请求框架总结1.xutils 此框架庞大而周全,这个框架可以网络请求,同时可以图片加载,又可以数据存储,又可以 View 注解,使用这种框架很方便,这样会使得你整个项目对它依赖性太强,万一 ...
- Android Volley 库的使用
本文内容 什么是 Volley 库 Volley 能做什么 Volley 架构 环境 演示 Volley 库的使用 参考资料 Android 关于网络操作一般都会介绍 HttpClient 以及 Ht ...
- Android Volley 库通过网络获取 JSON 数据
本文内容 什么是 Volley 库 Volley 能做什么 Volley 架构 环境 演示 Volley 库通过网络获取 JSON 数据 参考资料 Android 关于网络操作一般都会介绍 HttpC ...
- Android网络框架源码分析一---Volley
转载自 http://www.jianshu.com/p/9e17727f31a1?utm_campaign=maleskine&utm_content=note&utm_medium ...
- 深入了解Android中的AsyncTask
AsyncTask,即异步任务,是Android给我们提供的一个处理异步任务的类.通过此类,可以实现UI线程和后台线程进行通讯,后台线程执行异步任务,并把结果返回给UI线程. 我们知道,Androi ...
随机推荐
- 机器学习基石笔记:05 Training versus Testing
train:A根据给定训练集D在H中选出g,使得Ein(g)约等于0: test:g在整个输入空间X上的表现要约等于在训练集D上的表现,使得Eout(g)约等于Ein(g). 如果|H|小,更易保证t ...
- BonnMotion支持的几种移动模型
BonnMotion是一款基于java的移动场景产生和分析工具,常用来研究mobile ad hoc network的特征.其产生的移动场景可以导入到几款网络模拟器中进行模拟分析,例如:NS2,NS3 ...
- 自己动手实现爬虫scrapy框架思路汇总
这里先简要温习下爬虫实际操作: cd ~/Desktop/spider scrapy startproject lastspider # 创建爬虫工程 cd lastspider/ # 进入工程 sc ...
- msysGit在GitHub代码托管
(转:http://www.cnblogs.com/xing901022/p/4388190.html) 代码的管理,在日常开发中是很重要的环节,程序员的修炼三部曲——版本控制,单元测试,项目自动化. ...
- 如何把开源项目发布到Jcenter
转载自:https://www.jianshu.com/p/f66972f0607a 首先我们应该注册一个JFrog Bintray的账号 Jfrog Bintray官网 这里我们可以注意到那个绿色的 ...
- 十余年软件开发经历,经验总结和程序一览(涉及Socket、WPF、vc++、CAD、图像、GIS)
前言 本文主要介绍我开发的几款软件产品,大概介绍一下功能.可以让读者了解本人的开发能力,所擅长的方面.有相关开发需求可以联系作者. 本人开发软件多年,从事的行业也多种多样:自然接触了不同的业务,开发了 ...
- Zuul过滤器
1.Zuul过滤器生命周期Zuul大部分功能都是通过过滤器来实现的,Zuul定义了4种标准的过滤器类型,这些过滤器类型对应于请求的典型生命周期.a.pre: 这种过滤器在请求被路由之前调用.可利用这种 ...
- Controller:EOS区块链核心控制器
Controller是EOS区块链的核心控制器,其功能丰富.责任重大. 关键字:EOS,区块链,controller,chainbase,db,namespace,using,信号槽,fork_dat ...
- CynosDB技术详解——存储集群管理
本文由腾讯云数据库发表 前言 CynosDB是架构在CynosFS之上的分布式关系数据库系统,为最大化利用存储资源,平衡资源之间的竞争,检查资源使用情况,需要一套高效稳定的分布式集群管理系统(SCM: ...
- shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有
前言 开心一刻 老公酷爱网络游戏,老婆无奈,只得告诫他:你玩就玩了,但是千万不可以在游戏里找老婆,不然,哼哼... 老公嘴角露出了微笑:放心吧亲爱的,我绝对不会在游戏里找老婆的!因为我有老公! 老婆: ...