什么是Handler

Handler是Android消息机制的上层接口,它为我们封装了许多底层的细节,让我们能够很方便的使用底层的消息机制。Handler的最常见应用场景之一便是通过Handler在子线程中间接更新UI。Handler的作用主要有两个:一是发送消息;二是处理消息,它的运作需要底层Looper和MessageQueue的支撑。

MessageQueue即消息队列,它的底层用单链表实现;Looper则负责在一个循环中不断从MessageQueue中取消息,若取到了就交由Handler进行处理,否则便一直等待。关于Looper需要注意的一点是除了主线程之外的其他线程中默认是不存在Looper的。主线程中之所以存在,是因为在ActivityThread被创建时会完成初始化Looper的工作。

为什么使用Handler

总的来说, Handler的作用是将一个任务(从当前线程)切换到指定的线程中去执行 。我们知道Android只允许主线程去更新用户界面,而主线程需要时刻保持较高的响应性,因此我们要把一些耗时任务交给工作者线程去执行。那么问题来了,如果工作者线程执行完任务后想要更新UI该怎么破?我们希望的是主线程能够接收到工作者线程的通知,并且能根据工作者线程执行任务的结果对用户界面进行相应的更新。Handler就能让我们很方便的做到这些。Handler的工作过程大致如下图所示:

我们针对上图做下简单解释(详细的分析请见后文):首先我们在主线程中创建Handler对象(使用主线程的Looper)并定义handleMessage方法,这个Handler对象默认会关联主线程中的Looper。通过在工作者线程中使用该Handler对象发送消息,相应的消息处理工作(即handleMessage方法)会在主线程中运行, 这样就成功地将更新UI任务从工作者线程切换到了主线程。

Handler的工作原理分析

总的来说,Handler对象在被创建时会使用当前线程的Looper来构建底层的消息循环系统(使用默认构造器的情况下),若当前线程不存在Looper,则会报错。Handler对象创建成功后,就可以通过Handler的send或post方法发送消息了。调用send/post方法发送消息时,实际上会调用MessageQueue的enqueueMessage方法将该消息加入到MessageQueue中。之后Looper发现有新消息会取出,并把它交给Handler处理。下面我们通过分析相关源码来详细介绍这一过程。在这之前我们需要先了解一下ThreadLocal的工作原理。

ThreadLocal的内部工作机制

ThreadLocal是一个线程内部的数据存储类。通过使用ThreadLocal,能够让同一个数据对象在不同的线程中存在多个副本,而这些副本互不影响。Looper的实现中便使用到了ThreadLocal。通过使用ThreadLocal,每个线程都有自己的Looper,它们是同一个数据对象的不同副本,并且不会相互影响。下面我们现在探索下ThreadLocal的工作原理,为分析Looper的工作原理做好铺垫。

ThreadLocal使用示例

作为ThreadLocal的一个简单示例,我们先创建一个ThreadLocal对象:

private ThreadLocal<Integer> mIntegerThreadLocal =new ThreadLocal<Integer>();

然后创建两个子线程,并在不同的线程中为ThreadLocal对象设置不同的值:

1 mIntegerThreadLocal.set(0);
2 Log.d(TAG, "In Main Thread, mIntegerThreadLocal = " + mIntegerThreadLocal.get());
3
4 new Thread("Thread 1") {
5   @Override
6   public void run(){
7     mIntegerThreadLocal.set(1);
8     Log.d(TAG, "In Thread 1, mIntegerThreadLocal = " + mIntegerThreadLocal.get());
9   }
10 }.start();
11
12 new Thread("Thread 2") {
13   @Override
14   public void run() {
15     Log.d(TAG, "In Thread 2, mIntegerThreadLocal = " + mIntegerThreadLocal.get());
16   }
17 }.start();

在以上代码中,我们在主线程中设置mIntegerThreadLocal的值为0,在Thread 1中该设置为1,而在Thread 2中未设置。我们看一下日志输出:

通过日志输出我们可以看到,主线程与Thread 1的值确实分别为我们为他设置的,而Thread 2中由于我们没有给它赋值,所以就为null。我们虽然在不同的线程中访问同一个数据对象,却可以获取不同的值。那么ThreadLocal是如何做到这一点的呢?下面我们通过源码来寻找答案。

ThreadLocal的工作原理

我们首先要知道,Thread类内部有一个专门用来存储线程对象ThreadLocal数据的实例域,它的声明如下:

ThreadLocal.Values localValues;

这样一来,每个线程中就可以维护ThreadLocal对象的一个副本,而且这些副本不会互相干扰,ThreadLocal的get方法只要到localValues中去取数据就好了,set方法也只需操作本线程的localValues。我们来看一下set方法的源码:

1 public void set(T value) {
2   Thread currentThread = Thread.currentThread();
3   Values values = values(currentThread);
4   if (values == null) {
5     values = initializeValues(currentThread);
6   }
7   values.put(this, value);
8 }

第3行通过values方法获取到当前线程的localValues并存入values变量中,接下来在第4行进行判断,若localValues为null,则调用initializeValues方法进行初始化,然后会调用put方法将value存进去。实际上,localValues内部有一个名为table的Object数组,ThreadLocal的值就存在这个数组中。

了解了set方法的大致逻辑后,我们再来看一下get方法都做了些什么:

1 public T get() {
2   // Optimized for the fast path.
3   Thread currentThread = Thread.currentThread();
4   Values values = values(currentThread);
5   if (values != null) {
6     Object[] table = values.table;
7     int index = hash & values.mask;
8     if (this.reference == table[index]) {
9       return (T) table[index + 1];
10    }
11  } else {
12    values = initializeValues(currentThread);
13  }
14
15  return (T) values.getAfterMiss(this);
16 }

第4行中,获取localValues。第5行若判断为null,则表示未进行设置(比如上面例子中的线程2),就会返回默认值;若判断非空就先获取table数组,然后再计算出index,根据index返回ThreadLocal的值。

经过以上对get和set方法的源码的分析,我们了解到了这两个方法实际上对不同的线程对象会分别操作它们内部的localValues,所以能够实现多个ThreadLocal数据对象的副本之间的互不干扰。了解了ThreadLocal的实现原理,下面我们来探索下Looper是怎么借助ThreadLocal来实现的。

Looper的内部工作机制

在介绍Looper的工作机制之前,我们先来简单的介绍下MessageQueue。MessageQueue对消息队列进行了封装,在它的内部使用单链表来保存消息。MessageQueue主要支持以下两个操作:

  • enqueueMessage:向消息队列中插入一个消息。
  • next:从消息队列中取出一个消息(会从队列中删除该消息)。next方法内有一个无限循环,若消息队列为空,它会阻塞在这直到取到消息。

大致了解了MessageQueue后,让我们一起来探索Looper的内部工作机制,看看它是如何漂亮的完成将任务切换到另一个线程这个工作的。我们首先来看一下Looper的构造方法:

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

我们可以看到Looper的构造方法中创建了一个MessageQueue对象。之前我们提到过Handler只有在存在Looper的线程中才能创建,而我们看到Looper的构造方法是private的,那么我们怎么为一个线程创建Looper呢?答案是使用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));
}

我们可以看到prepare方法内部调用了Looper的构造器来为当前线程初始化Looper,而且当前的线程的Looper已经初始化的情况下再调用prepare方法会抛出异常。

创建了Looper后,我们就可以开始通过Looper.loop方法进入消息循环了( 注意,主线程中我们无需调用loop方法,因为ActivityThread的main方法中已经为我们调用了 )。我们来看一下这个方法的源代码:

ublic 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; // 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 blockif (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 loggerPrinter logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}msg.target.dispatchMessage(msg);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();}
}

通过以上代码我们可以看到,在第13行会进入一个无限循环。接着在第14行,调用了MessageQueue的next方法,之前我们介绍过这个方法会一直阻塞直到从消息队列中取出一个消息。退出这个无限循环的唯一方法就是MessageQueue返回null。这可以通过调用Looper的quit方法来实现。当Looper的quit/quitSafely方法被调用时,会导致MessageQueue的quit/quitSafely方法被调用,这会导致消息队列被标记为“退出”状态,如此一来,MessageQueue的next方法就会返回null了。这告诉了我们,如果我们不调用Looper的quit方法,他就会在loop方法中的循环里一直运行下去。

若在第14行中成功从MessageQueue中取得了一个消息,接下来就会对这个消息进行处理。第27行调用了msg.target的dispatchMessage方法,其中msg.target指的是发送这条消息的Handler对象,也就是说这里调用的是发送消息的Handler对象的dispatchMessage方法。注意,Handler的dispatchMessage方法实在创建该Handler时所使用的线程中执行的,这样一来,便成功地将任务切换到了Looper所在线程。接下来,我们以分析dispatchMessage方法的源码为切入点研究一下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); }
}

我们可以看到,这个方法中会首先判断msg.callback是否为null,若不为null则调用handleCallback方法。msg.callback是一个Runnable对象,实际上就代表着我们调用post方法放入MessageQueue中的Runnable对象。也就是说,若我们post了一个Runnable对象,就会调用handleCallback方法,这个方法的源码如下:

private static void handleCallback(Message message) { message.callback.run();
}

从以上代码我们可以看到,这个方法就是简单的调用了Runnable对象的run方法让它开始运行。

回到dispatchMessage方法的代码,若msg.callback为null,就会判断mCallback是否为null,若不为null则调用mCallback的handleMessage方法,否则调用handleMessage方法。实际上这两个handleMessage方法都是我们创建Handler对象时定义的消息处理函数,只不过分别对应了两种不同的创建Handler对象的方式。调用mCallback的handleMessage方法表示我们创建Handler对象时传入了一个实现了Callback接口的的对象,而调用handleMessage方法表示我们创建Handler对象时继承了Handler类并重写了handleMessage方法。那么mCallback是什么呢?让我们先看一下Handler的构造方法:

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

我们可以看到,mCallback被赋值为我们传入的第一个参数callback,callback即为实现了Callback接口的对象,Callback接口中只有一个方法,那就是handleMessage方法。

主线程的消息循环

Android中的主线程也就是我们上面提到过的ActivityThread。我们上面介绍过,ActivityThread的main方法中会通过Looper.loop方法开启循环,相关源码如下:

public static void main(String[] args) { ... Looper.prepareMainLooper(); ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");
}

通过以上代码我们可以看到主线程在初始化时确实通过Looper.loop方法开启了消息循环。那么主线程使用了哪个Handler来与MessageQueue进行交互呢?实际上它使用了ActivityThread.H,它的定义如下:

rivate class H extends Handler { public static final int LAUNCH_ACTIVITY = 100; public static final int PAUSE_ACTIVITY = 101; public static final int PAUSE_ACTIVITY_FINISHING= 102; public static final int STOP_ACTIVITY_SHOW = 103; public static final int STOP_ACTIVITY_HIDE = 104; public static final int SHOW_WINDOW = 105; public static final int HIDE_WINDOW = 106; public static final int RESUME_ACTIVITY = 107;public static final int SEND_RESULT = 108;public static final int DESTROY_ACTIVITY = 109;public static final int BIND_APPLICATION = 110;public static final int EXIT_APPLICATION = 111;public static final int NEW_INTENT = 112;public static final int RECEIVER = 113;public static final int CREATE_SERVICE = 114;public static final int SERVICE_ARGS = 115;public static final int STOP_SERVICE = 116;...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 RELAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");ActivityClientRecord r = (ActivityClientRecord)msg.obj;handleRelaunchActivity(r);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;...}}

从以上源码中我们可以看到,主线程使用的Handler中定义了一系列常量,代表了发生了各种事件(比如启动Activity、暂停Activity、显示Window)时应发给的主线程的消息标识。实际上这些消息是H在ApplicationThread中发送过来的。具体过程如下:ActivityThread通过ApplicationThread与AMS(Activity Manager Service)进行进程间通信(IPC)。AMS完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会通过H发送消息到ActivityThread的MessageQueue中,之后H的handlerMessage方法便会根据发来的消息进行相应的处理。这样就完成了将任务从ApplicationThread切换到ActivityThread的工作。

Android学习总结(3)——Handler深入详解相关推荐

  1. Android学习笔记(6)——详解持久化技术

    第六章 数据存储权方案--详解持久化技术 6.1 持久化技术简介 概述:Android 系统中主要提供了3种方式用于简单地实现数据持久化功能,即文件存储.SharedPreference存储以及数据库 ...

  2. Android学习笔记5——Button属性详解

    前言 Button组件是在我们在开发中最常用到的组件.Button组件,俗称"按钮",在APP界面当中少不了按钮,那么按钮的属性和使用方法是怎么样的呢? Button常用属性 因为 ...

  3. android编程断网的时候回调,Android Handler的详解及实例

    Android Handler的详解 Handler我们常常用于通知主线程做相对应的操作,但是如果使用不但的话就会造成内存泄露,所以记录写正确的Handler写法. Handler handler = ...

  4. Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(下)

       本文原创, 转载请注明出处:http://blog.csdn.net/qinjuning 上篇文章<<Android中measure过程.WRAP_CONTENT详解以及xml布局文 ...

  5. Android 多线程之HandlerThread 完全详解

    转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/52426353 出自[zejian的博客] 关联文章: Android ...

  6. Android vector标签 PathData 画图超详解

    此文章来源于https://www.cnblogs.com/yuhanghzsd/p/5466846.html点击打开链接 Android vector标签 PathData 画图超详解 SVG是一种 ...

  7. android标尺自定义view,android尺子的自定义view——RulerView详解

    项目中用到自定义尺子的样式: 原效果为 因为跟自己要使用的view稍有不同 所以做了一些修改,修改的注释都放在代码中了,特此记录一下. 首先是一个自定义View: public class RuleV ...

  8. android任务 进程 线程详解,Android任务、进程、线程详解

    singleTop模式,基本上于standard分歧,仅正在请求的Activity反好位于栈顶时,无所区别.此时,配放成singleTop的Activity,不再会构制新的实例加入到Task栈外,而是 ...

  9. Android 系统属性读取和设置详解

    Android 系统属性读取和设置详解 一.在adb中进行属性读取和设置 1.Settings Provider设置和读取 获取 设置 2.SystemProperties属性读取和设置 二.Andr ...

  10. Android 多线程之IntentService 完全详解

    转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/52426425 出自[zejian的博客] 关联文章: Android ...

最新文章

  1. c++ memset 语言_自学C语言不知道从哪儿下手?学习框架都帮你列好了!
  2. logistic回归和最大熵
  3. arm-eabi-gcc: error trying to exec 'cc1': execvp: No such file or directory
  4. 云架构师是做什么的_为什么以及如何成为云架构师
  5. ip变更 mysql无法启动_ubuntu mysql 更改IP导致mysql无法启动
  6. js和jquery获取父级元素、子级元素、兄弟元素的方法{转}
  7. search engine
  8. react中的核心概念
  9. 计算机组成原理-面试题
  10. usb加密狗破解软件
  11. “5杯水,怎么分给6个领导?” 答案暴露认知层次...
  12. python---酒鬼漫步
  13. 运放输入偏置电流方向_运放参数的详细解释和分析-part1,输入偏置电流和输入失调电流【TI FAE 分享】...
  14. 常用短链接生成工具推荐
  15. 【IPFS应用开发】ipfs安卓节点编译 ipfs andriod node
  16. uos应用_统信 UOS 私有化应用商店解决方案发布,支持应用分发管理
  17. 【电源专题】案例:单节18650电池供电的设备在3.6V时候怎么电量就只剩下一格了?
  18. 如何下载门头沟区卫星地图高清版大图
  19. 三、 mysqlbinlog使用简介
  20. DVWA问题2:Could not connect to the MySQL service.

热门文章

  1. 力扣559. N 叉树的最大深度(JavaScript)
  2. 收款码合并有什么弊端_为什么明明有个人收款码了,还要申请微信收款商业版?...
  3. 服务器2003光盘修复,windows光盘修复系统
  4. hive 修改分桶数 分桶表_疯狂Hive之DDL操作二(三)
  5. python 面授_5天Python实战营(面授)
  6. w10无法连到家庭组计算机,一键W10装机版无法进入家庭组如何处理
  7. oracle的local,11G R2 Oracle Local Registry(OLR)
  8. json解析对应的value为null_解决golang json解析出现值为空的问题
  9. Java三层结构的概念_Java中的mvc和三层结构究竟是什么关系
  10. ffmpeg打开音频设备