Handler机制,我们就会想到,他有四个重要的对象组成,分别是:Handler,Message,MessageQueue,Looper.

那Handler是如何工作的呢?

例如,你想刷新主界面的TextView,无奈你不在主线程,此时你就会包装好Message,然后声明一个Handler,让Handler将你的Message送往主线程(Looper),Handler将你的Message送到主线程后,还需要排队等待,等轮到你的时候,主线程就会告诉Handler,这个Message可以处理了,你负责分发一下,于是,Handler将该Message分发到相应的回调或者handleMessage( ) 方法中,于是,你就在该方法中更新了UI。

下面,我们就分别介绍一下,这四个对象,以及Handler机制的运行原理吧。

一、Message(消息)

Message.class位于android.os.包中。Message的构造函数为无参构造方法,且只有一个构造方法;

那么首先,我们想要用Message这个对象,我们就得知道,如何用他,他既然是一个对象,那秉承万物皆对象的原则,Message的创建也分为以下几种:

方式1、Message message = new Message();
方式2、Message message = Message.obtain();
方式3、Message message = new Handler().obtainMessage();

像是第一种,自然不必多说,这也是我们最常用的方式,通过无参构造,创建一个对象。

第二种,其实他是有八种创建方式的,分别如下:

static Message obtain()
static Message obtain(Message orig)
static Message obtain(Handler h)
static Message obtain(Handler h, Runnable callback)
static Message obtain(Handler h, int what)
static Message obtain(Handler h, int what, Object obj)
static Message obtain(Handler h, int what, int arg1, int arg2)
static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)

即通过静态构造方法来创建

当然,通过这种方式创建的,都会走第一个方法,下面,我们看一下通过obtain创建的方式的源码:

public static final Object sPoolSync = new Object();    //同步锁对象private static Message sPool;                           //全局池消息实例/*** 从全局池返回一个新的消息实例,允许我们在许多情况下避免分配新对象。*/public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;//......return m;}}return new Message();}

通过此方式创建,进行了一个空对象的判断,如果不为空,则获取第一个Message对象,通过此种方式创建,节省了很多内存资源

像是其他几种,基本上都是方法的重载。

第三种方法,通过其无参构造的源码我们发现:

public final Message obtainMessage(){return Message.obtain(this);
}

他也是调用了obtain方法来创建的,

public static Message obtain(Handler h) {Message m = obtain();m.target = h;return m;
}

此处大家注意一下,有一个Handler,即target,这个对象,我们后面会详细的讲述,此处,只需要记住一下几个

  • Message有8个静态方法可以创建Message实例
  • Message有两个重要的成员变量,分别为target 和callback,一个是Handler,一个是Runnable。
  • Message有4个公开变量what、arg1、arg2、obj 可以存储消息进行传递
  • Message还有一个包间成员变量next,它是Message类型,后面会使用到,知道有这个next就行

二、Handler

Handler.class 位于android.os包中,google给的解释就是消息的接收及处理者。

他的构造方法如下:

Handler()
Handler(Callback callback)
Handler(boolean async)
Handler(Callback callback, boolean async)
Handler(Looper looper)
Handler(Looper looper, Callback callback)
Handler(Looper looper, Callback callback, boolean async)

我们先来看第四个构造方法源码:

 public Handler(Callback callback, boolean async) {//......mLooper = Looper.myLooper();  //返回与当前线程关联的Looper对象,在后面Looper会讲到if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;  //返回Looper对象的消息队列,在后面MessageQueue会讲到mCallback = callback;  //接口回调mAsynchronous = async; //是否异步}public interface Callback {public boolean handleMessage(Message msg); //这个函数大家都很熟悉了,暂不细说,总之都知道是用来回调消息的}

此构造方法中做了三件事,

  • 获取当前线程的looper对象
  • 如果当前Looper不为空,则返回Looper对象的消息队列,并将消息队列复制给mQueue;
  • 利用CallBack来处理消息回调

我们结合实际的例子来解释一下Handler底层的工作原理

private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);//... 更新UI}};

这就是我们经常使用的Handler最简化的案例,当然,这样创建,容易引起内存泄露,所以,最好还是使用弱引用的方式,对于内存泄露问题,我们后面专门一片博客再来讲述吧

先看这里

当你创建Hanlder时,底层做的事情如下

  • 拿到mHandler所在线程的Looper,当前mHandler是在Activity中创建的,很明显,当前的线程就是主线程,所以 mHandler的成员变量mLooper = Looper.myLooper(),此处就已经将当前的主线程Looper赋值过去了。
  • 紧接着,判断mLooper 是否为空,明显不为空,所以又会将主线程的消息队列赋值给mQueue。告诉Handler,你要是有消息,就送到这个消息队列中来,我(Looper)会一个个按顺序处理,处理完后我就会告诉你,你再处理。

很明显,Handler并不是最终的决策者,每一次的Handler的处理对象,必然需要经过Looper,所以Looper才是Handler的主体,也是最重要部分,并且由源码可以看出来,每一个Handler只能对应一个Looper

说到Handler,我们肯定会想到sendMessage()方法,那么现在,我们就来看看SendMessage他们之间的调用方式

通过这张图,我们可以看出,不管是什么样的途径发送消息,最终都会通过sendMessageAtTime方法,通过enqueueMessage方法来发送消息,

 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {MessageQueue queue = mQueue;  //获得当前的消息队列if (queue == null) {   //若是在创建Handler时没有指定Looper,就不会有对应的消息队列queue ,自然就会为nullRuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis); }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进行处理的,为了方便后面的Looper进行调用

那么发送消息之后,交给了Looper进行处理了,那Handler是如何接收到Looper通知进行处理消息的呢?这时候,我们看源码就知道他用了一个disPathMessage()方法进行处理消息

 /*** 在这里处理系统消息*/public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}/*** 子类必须实现这个来接收消息*/public void handleMessage(Message msg) {}

当Looper处理完Message后,会使用到Message的target,即上面说到的target,即发送消息的那个Handler,Looper会调用Handler的dispatchMessage()方法分发消息,所以前面在enqueueMessage()发送消息的时候,为什么非得指明Message的target就是这个道理。

回到dispatchMessage()这个方法:
1、首先会判断Message的callback是否为空,此处的callback就是前面我们在Message中说到的,在静态方法创建Message时,可以指定的callback,若不为空,则将结果回调到callback中;
2、若Handler的mCallback 不为空,也一样的道理。
3、平时我们都没有传入这个callback,而是直接实现handleMessage()这个方法,在这个方法中处理更新UI任务。

以上就是Handler发送和接收消息的基本过程:把消息发送到队列—>然后喝茶等待—>接收消息—>分发消息—>在回调中处理。

三、MessageQueue

Handler发送消息会调用MessageQueue的enqueueMessage()方法

源码如下:

 boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {  //判断msg的所属Handlerthrow new IllegalArgumentException("Message must have a target.");}//......synchronized (this) {  //因为是队列,有先后之分,所以用了同步机制//......msg.when = when;Message p = mMessages;  //对列中排在最后的那个Message //......if (p == null || when == 0 || when < p.when) {    //若队列为空,或者等待时间为0,或者比前面那位的等待时间要短,就插队msg.next = p;  //此处的next就是前面我们在Message提到的,指向队列的下一个结点mMessages = msg;//......} else { //......Message prev;for (;;) {     //此处for循环是为了取出一个空的或者when比当前Message长的一个消息,然后进行插入prev = p;p = p.next;if (p == null || when < p.when) {break;}//......}msg.next = p;       // 置换插入prev.next = msg;  // 置换插入}//......}return true;}

以上就是消息队列插入消息的过程原理,通过单向链表的数据结构来存储消息。既然有了插入消息的方法供Handler插入消息,那么应该有对应的取出消息的方法,供Looper调用取出消息处理,它就是Message next()这个方法

四、Looper

Looper在Handler机制中扮演着关键的一环,他是循环处理消息的发动机,永不停息(永动机),它不断的从消息队列中取出的消息,处理,然后分发处理事件。每个线程都可以且只能绑定一个Looper。主线程之所以能处理消息,也是因为在APP启动时,在ActivityThread中的main()方法中就已经启动了Looper循环

我们先看一下Looper在ActivityThread().main()方法中的启动流程

main()方法源码如下:

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");//跟踪事件写入系统跟踪缓存区SamplingProfilerIntegration.start();//“抽样分析集成工具”,是集成Dalvik抽样分析器框架// CloseGuard defaults to true and can be quite spammy.  We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);//CloseGuard 是一种资源清理机制,资源应该被显式关闭清理。setEnabled()明显就是使其失效,不过会在debug builds的时候重现变得有效Environment.initForCurrentUser();//此处初始化用户使用环境// Set the reporter for event logging in libcoreEventLogger.setReporter(new EventLoggingReporter());//给lib库设置事件打印记者// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);//确保可信证书存储在正确的地方以便于查找CA证书。Process.setArgV0("<pre-initialized>");//Process是管理系统流程的工具。Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {//判断当前主线程的Handler是否为空。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();throw new RuntimeException("Main thread loop unexpectedly exited");}

我们看到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();}}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只能有一个主线程。

Looper.loop();

最重要的一句来了,前面Looper.prepareMainLooper()开启主线程,而这里的loop()就是处理主线程程序的关键段,所有通过Handler传到主线程的Message都会缓存在Looper的消息队列Queue里面,loop()里有个for(;;)循环,不断的从消息队列里面拿出消息进行处理,然后就有了事件的信息交流啦。

下面来看Looper.loop()方法

 public static void loop() {final Looper me = myLooper();   //获得当前的Looperif (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;  //获取当前Looper的消息队列//......for (;;) {Message msg = queue.next();  //取出队头的消息if (msg == null) {// 如果消息为空,则跳过,继续执行下一个messagereturn;}//......try {msg.target.dispatchMessage(msg);//......} finally {//......}//......msg.recycleUnchecked();  //回收可能正在使用的消息}}

由此可以看出,Looper的消息处理,无非就是循环的对消息进行取出,分发,处理,回收的过程

由此Handler机制可以简述为:

Handler将Message发送到Looper的消息队列中,即MessageQueue,等待Looper的循环读取Message,处理Message,然后调用Message的target,即附属的Handler的dispatchMessage()方法,将该消息回调到handleMessage()方法中,然后完成更新UI操作。

Handler机制原理----全解相关推荐

  1. 我的世界做计算机原理,我的世界计分板运算机制原理详解

    我的世界里面计分板的制作对新手玩家来说可能比较困难,特别是各种运算机制需要对电子电路比较了解,下面是计分板的计算机制和原理详解,看明白的话不妨自己试着制作计分板. 计分板运算机制原理详解 输入12 加 ...

  2. 生成对抗网络gan原理_中国首个“芯片大学”即将落地;生成对抗网络(GAN)的数学原理全解...

    开发者社区技术周刊又和大家见面了,萌妹子主播为您带来第三期"开发者技术联播".让我们一起听听,过去一周有哪些值得我们开发者关注的重要新闻吧. 中国首个芯片大学,南京集成电路大学即将 ...

  3. 中国首个“芯片大学”即将落地;生成对抗网络(GAN)的数学原理全解

    开发者社区技术周刊又和大家见面了,萌妹子主播为您带来第三期"开发者技术联播".让我们一起听听,过去一周有哪些值得我们开发者关注的重要新闻吧. 中国首个芯片大学,南京集成电路大学即将 ...

  4. Handler机制原理解析(二)prepare,loop,post

    Handler机制原理解析(二)prepare,loop,post 上一篇已经介绍了Handler机制的原理,如果不熟悉可以看Handler机制原理解析(一).这一篇,介绍下Handler周边的知识点 ...

  5. Handler机制原理解析(一)

    Handler机制原理解析(一) 我们都知道,在Android中,主线程也叫UI线程是负责界面更新的,子线程或者工作线程适合做网络请求,数据库等耗时操作.如果在主线程中执行耗时操作可能引发ANR异常. ...

  6. android Handler机制原理解析(一篇就够,包你形象而深刻)

    首先,我将Handler相关的原理机制形象的描述为以下情景: Handler:快递员(属于某个快递公司的职员) Message:包裹(可以放置很多东西的箱子) MessageQueue:快递分拣中心( ...

  7. java反射原理_java反射原理是什么?java反射机制原理详解

    前面给大家介绍了一下什么是java反射机制,那么下面要给大家介绍的就是java反射机制的原理,那么它的原理究竟是怎样的呢?下面就通过下面来做一下详细的了解吧. 首先我们再来介绍一下java反射机制. ...

  8. redis.conf 7.0 配置和原理全解,生产王者必备

    我是 Redis, 当程序员用指令 ./redis-server /path/to/redis.conf 把我启动的时候,第一个参数必须是redis.conf 文件的路径. 这个文件很重要,就好像是你 ...

  9. 生成对抗网络(GAN)的数学原理全解

    ©PaperWeekly 原创 · 作者|孙裕道 学校|北京邮电大学博士生 研究方向|GAN图像生成.情绪对抗样本生成 论文标题: A Mathematical Introduction to Gen ...

  10. java反射机制原理详解_java反射机制的详细讲解

    一 , 什么是java反射机制? JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象 ...

最新文章

  1. KDD2021最佳论文奖揭晓!胡侠获新星奖,论文接收率仅15%
  2. 文献记录(part4)--Sparse Biclustering of Transposable Data
  3. jer中无html文件,index.html
  4. mybatis 配置 mysql连接池_spring 5.x 系列第5篇 —— 整合 mybatis + druid 连接池 (xml配置方式)...
  5. Centos出现-bash: unzip: command not found的解决办法
  6. volatile深入
  7. 卢伟冰曝Redmi K30 Pro搭载骁龙865,却惨遭交罚款
  8. SpringBoot:javalist和set区别
  9. WebService传输DataSet压缩与解压缩
  10. UNIX 环境高级编程(八)—— fork 函数
  11. jstl中的日期格式化
  12. 初识powerpoint的计算机教案,《初识PowerPoint》教案
  13. java.lang.InstantiationException: class has no zero argument constructor
  14. 微博视频php解析,微博视频的地址解析下载
  15. 华硕fl5600l笔记本拆机,在光驱位加装固态硬盘
  16. 怎么修改php网页图片大小,如何改变图片大小
  17. py交易----实验吧
  18. android 饼图花边框,echarts 饼图给外层加边框
  19. 学习lumberjack framework(中文版)
  20. B1019 数字黑洞

热门文章

  1. 利用Echarts+阿里云地图选择器绘制可交互的行政区划地图
  2. 计算机管理如何格式化u盘,优盘格式化,教您如何格式化u盘
  3. 教你如何清除计算机病毒
  4. iOS经典讲解之Apple Pay开发
  5. STM32 独立按键扫描功能大全-支持连击、组合连击、任意连击
  6. python下载整个网站_python 下载整个站点
  7. java 读写乱码_java读写文件出现乱码的解决方法
  8. “封号斗罗” 程序员修炼之道:通向务实的最高境界
  9. c语言令牌桶原理,基于多 goroutine 实现令牌桶
  10. 【python】Windows系统中python解释器下载及安装过程