Android Handler机制简单分析

原创 cheny  2018-03-07 11:58  评论 0 条

版权说明 : 《Android Handler机制简单分析》于当前CSDN博客乘月网属同一原创,转载请说明出处,谢谢。


本文一切从简,将围绕以下流程展开叙述:

what?

接触Android的朋友都知道Handler机制用于多线程方面的通信,这好像是一句废话。


why?

我们知道java几个具有代表性的多线程通信方法,例如:

  1. "wait"和"notify"通知机制
    Java中每个类都是Oject的子类(万物皆对象,滑稽~ ~),也就具备Oject的"wait()"和"notify()"方法特性。简单举例说明:两个线程中,对于某类的对象a,在线程1中执行a.wait(),线程1则一直处于阻塞中,直到在线程2中执行a.notify(),线程1才被唤醒继续执行。

  2. "synchronized"线程锁机制
    多个线程共享一个变量,通过上锁( synchronized关键字 )限制线程们对该变量的访问,谁拿到锁,谁便可以对变量进行修改,待其他线程拿到锁访问该变量时,根据变量的变化作出相应的处理,以达到通信的目的。

  3. 此处省略n个字...

嗯,上述方法都是利用线程阻塞的方式进行通信。这若在Android中使用?你得先搞清楚3个问题:

  1. Android中多线程通信是为UI线程(主线程)+Worker线程(子线程)的交互服务的。

  2. 基于问题1,Android的UI线程不允许阻塞,否则会造成"ANR"( 想了解ANR? 传送门)

  3. 基于问题2,为避免"ANR",Android中所有的耗时操作(如网络请求,文件读写)须在子线程中完成,并通知进度或结果给主线程用于UI更新。

综上:
  既然java原生方法无法满足Android程序设计方面的要求,那只能另辟新径了。还好google比较良心,自己挖“坑”自己补,于是设计了一系列UI线程与Worker线程通信的方法,如:

  • activity.runOnUiThread(Runable action)(Activity类下的切换回UI线程的方法)

  • view.post(Runable action),view.postDelayed(Runnable action, long delayMillis)(View类下的切换回UI线程的方法)

  • 还有本文的主角Handler机制(异步消息处理机制)等等。


how?

先来一段Demo:

上述demo便是Handler的简单用法,希望大家能看懂。为了简练代码,请忽略内存泄漏~~~


analyze

好了,知道怎么用了,接下来就得知道为什么这样写可以切换到主线程,这就麻烦了,得看源码!!!

怎么看?直接通过demo看:
###1.mHandler = new Handler() {... }初始化Handler
- 来,我们来看看Handler构造方法在干嘛:

  上述代码,我特意把抛异常的说明翻译了一下,Excuse me?我并没有执行啊,怎么没报异常?怎么Looper.myLooper()有值的啊?

  其实这并不矛盾,在同一个线程中可以创建一个或多个Handler对象,但前提必须是当前线程已创建(通过Looper.prepare()创建)并保存或已存在唯一的Looper对象(不理解没关系,不了解Looper也没有关系,下文会继续说),Android所有线程之间的通信皆如此,主线程亦然。

  Android中,app运行入口是在ActivityThread类里的main函数开始的,没错,你没看错,就是java程序的入口main函数,android app也是java写的,当然也是main入口的,那么我们直接看核心源码来解释上面的疑问:

  • Looper类下相关代码:

小结:
  1.由于是app程序入口,main函数一定执行在主线程(UI线程)上,并且程序一开始就为主线程创建并保存好了Looper对象,以便为Handler子类H提供服务,既然已存在,当然不需要自行“Looper.prepare()”了。
  2.Android官方已经为我们提供了Handler机制代码模版↓↓↓

逻辑代码写法流程图:

  所以,可以这样归纳:主线程与子线程间通信不需要写Looper.prepare(...)和Looper.loop(),子线程与主线程以及子线程与子线程间的通信则需要
  3.MessageQueue(消息队列)对象是在Looper初始化的时候被创建,且一个线程中仅能创建一个Looper对象,所以一个线程中MessageQueue与Looper对象是一对一的关系。

###2. Message msg = Message.obtain();子线程中发消息前创建Message对象

  • 先简单分析下Message.obtain()源码:

  • 下面介绍Message的flags属性:

  • 再看new Message()构造方法源码:

     嗯哼,如此简单明了的告诉你:其实我的构造方法没啥骚操作,但希望你优先使用Message.obtain()方式获取Message实例,避免铺张浪费。

小结
  关于Message对象的获取,优先考虑全局池(Message链表),有则取表头并作脱链(next= null)和清除"in use"状态(flags=0)的重置操作,无则“new”一个新对象,此时其flags默认值为0,next为null。这与上述翻译的“清除‘in use’状态的唯一时间”相对应。

下面是获取Message对象流程图
这里写图片描述

3.msg.what = MSG_DOWNLOAD_TASK......mHandler.sendMessage(msg);发送消息

  • 其实对于Message的what和obj用法大家应该很熟悉了,这里就顺便看一下源码的解释:

  • 接下来再回看Handler源码:

  • 看MessageQueue源码:

小结:
  因为handler发送消息最终走的是sendMessageAtTime()方法,所以enqueueMessage()方法下的when其实是指时间点。在若干线程中,任意时间发送多个消息,如果最终调用enqueueMessage时传入的when(即uptimeMillis
)值都相同,则它们被接收(处理)的时间点相同。上文谷歌在sendMessage()的注释中提到"当前时间"是指调用sendMessage时,传入的when,即"SystemClock.uptimeMillis() + delayMillis"与消息队列中已有的某些msgwhen值相同,需要按先来后到的顺序排到这些msg的最后。

下面是消息队列示意图:
这里写图片描述

  嗯哼,既然排好队了,那是不是就等着Looper来轮询了?Demo没有给出轮询代码,因为UI线程为我们写好了,你懂的。 接下来看analyze收尾篇↓↓↓

###4.Looper.loop();轮询消息

  • 看 "Looper.loop()" 源码:

  • 看看 "queue.next()" 获取消息流程:

  • 再看"msg.target.dispatchMessage(msg)"在干什么。高能预警↓↓↓

小结:
  "Looper.loop()"依赖两个for循环来维持消息轮询和分发,外环重复着三大任务:1.获取消息(queue.next())。2.分发消息(msg.target.dispatchMessage(msg))。3.回收消息(msg.recycleUnchecked())。
  外环由msg==null条件成立而终止,为了让轮询一直维持下去,queue.next()作为内环既要承担这个任务,也要筛选msg提供给外环分发:1.有合适的msg则返回给外环。2.有消息但没到分发时间点,则阻塞线程,最长阻塞nextPollTimeoutMillis毫秒唤醒,期间可能被一些因素唤醒,如有新消息进入队列。3.无消息,即mMessages==null,则一直阻塞线程,期间可能被一些因素唤醒,如有新消息进入队列。4.如果消息队列退出,即mQuitting==true,则返回null,此时外环因msg==null而终止。
  可能还有人在疑问:哪里能看得出handleMessage()已经切换到目标线程了?这个问题我还真被人问过,这里顺便回答一下:因为"handleMessage()"在"dispatchMessage()"下执行,而"dispatchMessage()"又在"loop()"下执行,"loop()"本身就运行在目标线程,这样够清晰了吗?嗯?

  至此,关于Handler机制的分析就告一段落了,写作期间因为各种原因中断了很多次,也隔了很久,导致思路对接不通,不清晰,望请原谅,后期会不断优化更新

本文地址:http://icheny.cn/android-handler%e6%9c%ba%e5%88%b6%e7%ae%80%e5%8d%95%e5%88%86%e6%9e%90/
关注我们:加我微信:扫描二维码乘月网的微信号,微信号:ausboyue
版权声明:本文为原创文章,版权归 cheny 所有,欢迎分享本文,转载请保留出处!

发表评论


表情