Handler


 本文主要详细去解读Android开发中最常使用的Handler,以及使用过程中遇到的各种各样的疑问。
 在Android开发的过程中,我们常常会将耗时的一些操作放在子线程(work thread)中去执行,然后将执行的结果告诉UI线程(main thread),熟悉Android的朋友都知道,UI的更新只能通过Main thread来进行。那么这里就涉及到了如何将
子线程的数据传递给main thread呢?
 Android已经为我们提供了一个消息传递的机制——Handler,来帮助我们将子线程的数据传递给主线程,其实,当熟悉了Handler的原理之后我们知道,Handler不仅仅能将子线程的数据传递给主线程,它能实现任意两个线程的数据传递。
 接下来,我们便详细的了解下Handler的原理及其使用。
 首先看一下Handler最常规的使用方式:

private Handler mHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {case MESSAGE_WHAT:Log.d(TAG, "main thread receiver message: " + ((String) msg.obj));break;}}};private void sendMessageToMainThreadByWorkThread() {new Thread(){@Overridepublic void run() {Message message = mHandler.obtainMessage(MESSAGE_WHAT);message.obj = "I am message from work thread";mHandler.sendMessage(message);}}.start();}/** 通常我们在主线程中创建一个Handler,* 然后重写该Handler的handlerMessage方法,可以看到该方法传入了一个参数Message,* 该参数就是我们从其他线程传递过来的信息。** 我们在来看下子线程中如何传递的信息,子线程通过Handler的obtainMessage()方法获取到一个Message实例,* 我们来看看Message的几个属性:* Message.what------------------>用来标识信息的int值,通过该值主线程能判断出来自不同地方的信息来源* Message.arg1/Message.arg2----->Message初始定义的用来传递int类型值的两个变量* Message.obj------------------->用来传递任何实例化对象* 最后通过sendMessage将Message发送出去。** Handler所在的线程通过handlerMessage方法就能收到具体的信息了,如何判断信息的来源呢?当然是通过what值啦。* 怎么样很简单吧*/

 文章的开头说过,Handler不仅仅是能过将子线程的数据发送给主线程,它适用于任意两个线程之间的通信。
 下面我们来看下两个子线程之间如何进行通信的。
 很简单啊,在一个线程创建Handler,另外一个线程通过持有该Handler的引用调用sendMessage发送消息啊!
 写程序可不能关说不练啊,我们把代码敲出来看一下!

private Handler handler;private void handlerDemoByTwoWorkThread() {Thread hanMeiMeiThread = new Thread() {@Overridepublic void run() {
//                Looper.prepare();handler = new Handler() {@Overridepublic void handleMessage(Message msg) {Log.d(TAG, "hanMeiMei receiver message: " + ((String) msg.obj));Toast.makeText(MainActivity.this, ((String) msg.obj), Toast.LENGTH_SHORT).show();}};
//                Looper.loop();}};Thread liLeiThread = new Thread() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message message = handler.obtainMessage();message.obj = "Hi MeiMei";handler.sendMessage(message);}};hanMeiMeiThread.setName("韩梅梅 Thread");hanMeiMeiThread.start();liLeiThread.setName("李雷 Thread");liLeiThread.start();/** 搞定,我们创建了两个Thread,liLeiThread和hanMeiMeiThread两个线程,很熟悉的名字啊!* 跟之前的代码没太大区别hanMeiMeiThread创建了Handler,liLeiThread通过Handler发送了消息。* 只不过此处我们只发送一个消息,所以没有使用what来进行标记* 运行看看,我们的李雷能拨通梅梅吗?* 啊哦,出错了* 05-13 17:08:17.709 20673-20739/? E/AndroidRuntime: FATAL EXCEPTION: 韩梅梅 ThreadProcess: design.wang.com.designpatterns, PID: 20673java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()at android.os.Handler.<init>(Handler.java:200)at android.os.Handler.<init>(Handler.java:114)*Can't create handler inside thread that has not called Looper.prepare()* -----------》它说我们创建的handler没有调用Looper.prepare();* 好的,我们在实例化Handler之前调用下该方法,看一下。加上是不是没有报错了呢。* 等等,虽然没有报错,但是hanMeiMeiThread也没有接到消息啊,消息呢?别急。* 我们在Handler实例化之后加上Looper.loop();看一看,运行一下,是不是收到消息了呢。* 只是为什么呢?* 接下来我们就去看看Handler是怎么实现的发消息呢,弄清楚了原理,这里的原因也就明了了。*/}

 好了,卖了半天的关子,终于要开始真正的主题了。
 首先我们来看下,为什么在子线程里实例化的时候不调用Looper.prepare()就会报错呢?

//我们先来看看new Handler();时出错的原因。后续讲解源码分析只贴出关键部分。
//如下是Handler构造函数里抛出上文异常的地方,可以看到,由于mLooper对象为空才抛出的该异常。
mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}
/*异常的原因看到了,接下来我们看看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));
}
/*可以看到,该方法在当前thread创建了一个Looper(), ThreadLocal主要用于维护线程的本地变量,
*/private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();
}
//而Looper的构造函数里面又为我们创建了一个MessageQueue()对象。

 了解到此,我们已经成功引出了Handler机制几个关键的对象了,Looper、MessageQueue、Message。
 那么,肯定也有人又产生新的疑问了——为什么在主线程中创建Handler不需要要用Looper.prepare()和Looper.loop()方法呢?
 其实不是这样的,App初始化的时候都会执行ActivityThread的main方法,我们可以看看ActivityThread的main()方法都做了什么?

        Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();
/*真相只有一个,是的在创建主线程的时候Android已经帮我们调用了Looper.prepareMainLooper()和Looper.loop()方法,所以我们在主线程能直接创建Handler使用。
*/

 我们接着来看Handler发送消息的过程:

//调用Handler不同参数方法发送Message最终都会调用到该方法
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);}

 sendMessage的关键在于enqueueMessage(),其内部调用了messageQueue的enqueueMessage方法

boolean enqueueMessage(Message msg, long when) {...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 {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.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}/*从代码可以看出Message被存入MessageQueue时是将Message存到了上一个Message.next上, 形成了一个链式的列表,同时也保证了Message列表的时序性。*/

 Message的发送实际是放入到了Handler对应线程的MessageQueue中,那么,Message又是如何被取出来的呢?
 细心的朋友可能早早就发现了,之前抛出异常的地方讲解了半天的Loop.prepare()方法,一直没有说到Loop.loop()方法。同时,在之前的例子中也看到了,如果不调用Looper.loop()方法,Handler是接受不到消息的,所以我们可以大胆的猜测,消息的获取肯定和它脱不了关系!当然关怀疑还不行,我们还必须找出真相来证明我们的猜想?那还等什么,先看看loop()方法吧。

public static void loop() {
//可以看到,在调用Looper.prepare()之前是不能调用该方法的,不然又得抛出异常了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 loggerfinal 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.traceBegin(traceTag, msg.target.getTraceName(msg));}try {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();}}
/*
这里我们看到,mLooper()方法里我们取出了,当前线程的looper对象,然后从looper对象开启了一个死循环
不断地从looper内的MessageQueue中取出Message,只要有Message对象,就会通过Message的target调用
dispatchMessage去分发消息,通过代码可以看出target就是我们创建的handler。我们在继续往下分析Message的分发
*/
public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}
/*好了,到这里已经能看清晰了
可以看到,如果我们设置了callback(Runnable对象)的话,则会直接调用handleCallback方法
*/
private static void handleCallback(Message message) {message.callback.run();}
//即,如果我们在初始化Handler的时候设置了callback(Runnable)对象,则直接调用run方法。比如我们经常写的runOnUiThread方法:
runOnUiThread(new Runnable() {@Overridepublic void run() {}});
public final void runOnUiThread(Runnable action) {if (Thread.currentThread() != mUiThread) {mHandler.post(action);} else {action.run();}}/*
而如果msg.callback为空的话,会直接调用我们的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler对象是在主线程中创建的,
所以handler的handlerMessage方法的执行也会在主线程中。*/

 到这里,想必你应该清楚如何在不同的线程之间来使用Handler了吧。

最后总结一下:

  1. 在使用handler的时候,在handler所创建的线程需要维护一个唯一的Looper对象,
    Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个handler,
    但是只能有一个Looper和一个MessageQueue。
  2. Message在MessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个
    Message的next中,在取出的时候通过顶部的Message就能按放入的顺序依次取出Message。
  3. Looper对象通过loop()方法开启了一个死循环,不断地从looper内的MessageQueue中取出Message,
    然后通过handler将消息分发传回handler所在的线程。

最后附上一张自己理解画出来的流程图:

20180513192006823.png



Handler补充:

1. Handler在使用过程中,需要注意的问题之一便是内存泄漏问题。

为什么会出现内存泄漏问题呢?
首先Handler使用是用来进行线程间通信的,所以新开启的线程是会持有Handler引用的,
如果在Activity等中创建Handler,并且是非静态内部类的形式,就有可能造成内存泄漏。

  1. 首先,非静态内部类是会隐式持有外部类的引用,所以当其他线程持有了该Handler,线程没有被销毁,则意味着Activity会一直被Handler持有引用而无法导致回收。
  2. 同时,MessageQueue中如果存在未处理完的Message,Message的target也是对Activity等的持有引用,也会造成内存泄漏。
解决的办法:

 (1). 使用静态内部类+弱引用的方式:

  静态内部类不会持有外部类的的引用。

private Handler sHandler = new TestHandler(this);static class TestHandler extends Handler {private WeakReference<Activity> mActivity;TestHandler(Activity activity) {mActivity = new WeakReference<>(activity);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Activity activity = mActivity.get();if (activity != null) {//TODO:}}
}

 (2). 在外部类对象被销毁时,将MessageQueue中的消息清空。例如,在Activity的onDestroy时将消息清空。

@Override
protected void onDestroy() {handler.removeCallbacksAndMessages(null);super.onDestroy();
}

2. 在使用Handler时,通常是通过Handler.obtainMessage()来获取Message对象的,而其内部调用的是Message.obtain()方法,那么问题来了,为什么不直接new一个Message,而是通过Message的静态方法obtain()来得到的呢?

下面就通过代码来一探究竟

public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}

 其实在在Message中有一个static Message变量sPool,这个变量是用于缓存Message对象的,在obtain中可以看到当需要一个Message对象时,如果sPool不为空则会返回当前sPool(Message),而将sPool指向了之前sPool的next对象,(之前讲MessageQueue时讲过Message的存储是以链式的形式存储的,通过Message的next指向下一个Message,这里就是返回了sPool当前这个Message,然后sPool重新指向了其下一个Message),然后将返回的Message的next指向置为空(断开链表),sPoolSize记录了当前缓存的Message的数量,如果sPool为空,则没有缓存的Message,则需要创建一个新的Message(new Message)。

20180608180422985.jpg

 接着看下sPool中缓存的Message是哪里来的?

public void recycle() {if (isInUse()) {if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");}return;}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;sPoolSize++;}}}

 recycle()是回收Message的方法,在Message处理完或者清空Message等时会调用。
recycleUnchecked()方法中可以看到,将what、arg1、arg2、object等都重置了值,如果当前sPool(Message缓存池)的大小小于允许缓存的Message最大数量时,将要回收的Message的next指向sPool,将sPool指向了回收的Message对象(即将Message放到了sPool缓存池的头部)

20180608180812257.jpg

总结:

由此可见,使用obtain获取Message对象是因为Message内部维护了一个数据缓存池,回收的Message不会被立马销毁,而是放入了缓存池,
在获取Message时会先从缓存池中去获取,缓存池为null才会去创建新的Message。


3. Handler sendMessage原理解读。

 引入问题!

  1. sendMessageDelayed是如何实现延时发送消息的?
  2. sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message?

详细分析请移步下篇文章:Handler进阶之sendMessage原理探索

最全Handler解读,持续补充...相关推荐

  1. php程序员面试题(持续补充,偏中级面试题)

    博主最近找工作,记录了不少面试题,有些还是挺值的学习的,这里记录一下.有些我会给出参考链接,有些需要大家自己百度了,持续补充.. 一.公司一 1.php的接口和抽象类有什么区别,应用场景有哪些 htt ...

  2. Kubernetes 弹性伸缩全场景解读(五) - 定时伸缩组件发布与开源

    作者| 阿里云容器技术专家刘中巍(莫源) 导读:Kubernetes弹性伸缩系列文章为读者一一解析了各个弹性伸缩组件的相关原理和用法.本篇文章中,阿里云容器技术专家莫源将为你带来定时伸缩组件  kub ...

  3. 本地环境可以,生产环境不行(持续补充)

    "我本地是可以的啊,为什么生产上就不行了???" 上面那句话相信大家都有说过,现在似乎已经被戏称为"程序员最大的谎言",呵呵--我就来以我个人的遭遇来总结下教训 ...

  4. 【Matlab 绘图——持续补充中】

    Matlab 绘图--持续补充中 目录 plot() 函数 常用线型.颜色.symbol loglog,semilogx,semilogy,plotyy 图片完善--标题title,坐标轴名称xlab ...

  5. Pandas 函数方法汇总一览查询(持续补充改进)

    Pandas 函数方法汇总一览查询(持续补充改进) 文章目录 Pandas 函数方法汇总一览查询(持续补充改进) 初衷 Pandas 最最常用函数罗列 Pandas 函数用法示例 初衷 NumPy.P ...

  6. Vim中的常用命令总结(持续补充完善)

    文章目录 前言 VIM命令 Vim的三种模式 (1) 命令模式 (2) 插入模式 (3) 退出模式 跳转 (1) gg :跳转到文件开头 (2) Shift + g :跳转到文件结尾 分割窗口 (1) ...

  7. oracle 删除补全日志组_Oracle 补充日志(Supplemental Logging)说明

    补充日志:只要是针对 UPDATE 命令的,是对重做日志记录中 变更矢量块的补充信息,增加了变更矢量记载的记录量.日志挖掘器(LogMiner).闪回事务查询.闪回事务等都需要 补充日志的支持.尤其是 ...

  8. 纳思达:完美布局打印全产业链,持续开拓芯片品类

    纳思达:完美布局打印全产业链,持续开拓芯片品类 ​​1. 纳思达:全产业链布局的国产打印行业龙头 1.1 二十年深耕打印显像行业 实现"耗材+芯片+整机+服务"全产业链覆盖.纳思达 ...

  9. Android Handler消息机制原理最全解读(持续补充中)

     本文主要详细去解读Android开发中最常使用的Handler,以及使用过程中遇到的各种各样的疑问. Handler  在Android开发的过程中,我们常常会将耗时的一些操作放在子线程(work ...

最新文章

  1. 小程序 座位管理系统(一)(纯前端)
  2. mysql5 7安装教程_MySQL57安装教程
  3. root目录空间不够的问题
  4. MM引擎新应用——爱车加油记
  5. python中ttk和tkinter_python tkinter中ttk组件如何使用?
  6. 计算机桌面上的声音图标没了怎么办,Win7电脑右下角声音图标不见了怎么办?...
  7. Fish Redux 使用指南
  8. Android 系统(35)---Android 进程间通信的几种实现方式
  9. javacript 数据类型
  10. nlp任务分类及适用方法
  11. aso优化时高权重的积分墙关键词_怎样做好积分墙关键词的优化
  12. 哪上班 | 好工作近在咫尺
  13. 计算机硬件管理措施,浅谈计算机硬件的维护与管理措施
  14. java软件工程师工作业绩_java软件工程师,工作总结
  15. 【最全面教程】搞定配置MySQL的各种幺蛾子!!
  16. 判断是否是回文字符串两种方法
  17. 解读照明设备出口沙特具体法规要求!
  18. juce: 跨平台的C++用户界面库
  19. 如何禁用自动创建复制拓扑知识一致性检查器(MSKB)
  20. JAVA毕业设计高校网上报销系统计算机源码+lw文档+系统+调试部署+数据库

热门文章

  1. 如何做好Web 安全测试
  2. windows服务器新建管理员用户_用户和组管理
  3. alter procedure是什么意思_板上钉钉的意思
  4. 金融数据分析与挖掘实战练习2.1-2.4
  5. Python基本操作(二)注释、分隔
  6. 为什么学前端不先学框架?
  7. 身为前端工程师,对你来说,你认为最重要的是什么?
  8. 零基础的同学看过来,如何系统学习前端,保证让你不亏
  9. 零基础学前端,自学还是培训机构?
  10. 谁说 JavaScript 简单的?