Handler简单介绍

Handler简单介绍

目 录
1 Handler的基本介绍…
1.1什么是Handler…
1.2为什么要用Handler…
1.3Handler的四个组成部分…
1.3.1 Message…
1.3.2 Handler…
1.3.3 MessageQueue…
1.3.4 Looper…
1.4 Handler工作流程…
1.5 Handler的用法…
1.5.1 传递Message…
1.5.2 传递Runnable对象…
1.5.3 传递Callback…
2 Handler常见问题…
2.1 Thread、Looper和Handler的数量关系…
2.2 Looper轮询消息机制…
2.3 Handler实现发送延迟消息的方法…
2.4 消息队列解决延迟消息的堵塞问题方法…
2.5 Looper.looper()不会阻塞主线程原因…
2.6主线程的死循环一直运行不会消耗大量CPU资源…

1 Handler的基本介绍

1.1 什么是Handler

Handler是Android提供的一套更新UI的机制,也是一套异步消息处理机制。我们可以发送消息也可以接受消息。。当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作。

1.2 为什么要用Handler

子线程更新UI是不安全的,多线程去更新UI,而且没有加锁的话,会出现多线程并发问题,更新界面混乱。如果对更新UI的操作都进行加锁处理,性能下降。
所以Android在设计的时候,就封装了一套消息的创建、传递、处理机制,如果不遵循这种机制,就没有办法更新UI信息,就会抛出异常信息。

1.3 Handler的四个组成部分

1.3.1 Message

Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。使用Message的arg1和arg2便可携带int数据,使用obj便可携带Object类型数据。

1.3.2 Handler

Handler顾名思义就是处理者的意思,它只要用于在子线程发送消息对象Message,在UI线程处理消息对象Message,在子线程调用sendMessage方法发送消息对象Message,而发送的消息经过一系列地辗转之后最终会被传递到Handler的handleMessage方法中,最终在handleMessage方法中消息对象Message被处理。

1.3.3 MessageQueue

MessageQueue就是消息队列的意思,它只要用于存放所有通过Handler发送过来的消息。这部分消息会一直存放于消息队列当中,等待被处理。每个线程中只会有一个MessageQueue对象,请牢记这句话。其实从字面上就可以看出,MessageQueue底层数据结构是队列,而且这个队列只存放Message对象。

1.3.4 Looper

Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,然后每当MesssageQueue中存在一条消息,Looper就会将这条消息取出,并将它传递到Handler的handleMessage()方法中。每个线程只有一个Looper对象。

1.4 Handler工作流程

Handler简单介绍
图1-1 Handler工作流程示意图
如图1-1,首先在UI线程中我们创建了一个Handler实例对象,对handleMessage方法进行重写。然后创建子线程,在子线程需要更新UI时,新建一个Message对象,并且将消息的数据记录在这个消息对象Message的内部,比如arg1,arg2,obj等,然后通过前面的Handler实例对象调用sendMessge方法把这个Message实例对象发送出去,之后这个消息会被存放于MessageQueue中等待被处理,此时MessageQueue的管家Looper正在不停的把MessageQueue存在的消息取出来,通过回调dispatchMessage方法将消息传递给Handler的handleMessage方法,最终前面提到的消息会被Looper从MessageQueue中取出来传递给handleMessage方法,最终得到处理.

1.5 Handler的用法

1.5.1 传递Message

用于接受子线程发送的数据,并用此数据配合主线程更新UI,有以下方法:
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message.long)
sendMessageDalayed(Message.long)

1.5.2 传递Runnable对象

用于通过Handler绑定的消息队列,安排不同操作的执行顺序,主要有以下方法:
post(Runnable)
postAtTime(Runnable.long)
postDelayed(Runnable.long)

1.5.3 传递Callback对象

CallBack用于截获handler发送的消息,如果返回true,就截获成功,不会向下传递。

2 Handler常见问题

2.1 Thread、Looper和Handler的数量关系

1个线程Thread只能绑定1个循环器 Looper,但可以有多个处理者 Handler。
1个循环器Looper可绑定多个处理者Handler。
1个处理者Handler只能绑定1个1个循环器Looper。

2.2 Looper轮询消息机制

Looper不断轮询MessageQueue,当如果由新的消息就交给Handler处理,如果轮询不到新的消息,自身就处于阻塞状态。
Message msg = queue.next(); // might block
因为当MessageQueue没有消息时,next方法会一直阻塞在那里,因为MessageQueue的next方法阻塞了,就导致Looper的loop方法也一直在阻塞了。

2.3 Handler实现发送延迟消息的方法

以handler.postDelayed()为例
postDelayed(Runnable r, long delayMillis)调用sendMessageDelayed(Message msg, long delayMillis),得到当前时间加上延迟时间,再调用sendMessageAtTime(Message msg, long uptimeMillis)
调用MessageQueue的enqueueMessage()方法,enqueueMessage()方法中会将延迟的时间when放到msg上,并加入到messageQueue中。
loop()是在一个死循环中不断的从MessageQueue中取message,然后交给handler进行处理消息。在looper()中queue.next()方法获取当前时间。
如果头部的这个Message是有延迟而且延迟时间没到的(now < msg.when),会计算一下时间(保存为变量nextPollTimeoutMillis),然后在循环开始的时候判断如果这个Message有延迟,就调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞。

2.4 消息队列解决延迟消息的堵塞问题方法

1、postDelay()一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;
2、紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;
3、MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;
4、Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;直到阻塞时间到或者下一次有Message进队。

2.5 Looper.looper()不会阻塞主线程原因

查看ActivityThread.java
ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施。
因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。

2.6 主线程的死循环一直运行不会消耗大量CPU资源

这里涉及到LInux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。