Android开发之Handler的前世今生

2020-12-15 11:27:32 浏览数 (1)

前言

谈到Android开发,就离不开线程操作,而面试中也会常常问到有关异步线程、多线程、Handler等问题,作为面试中中奖率如此之高的一个问题,我们今天不妨来瞅瞅这handler长啥样!

作者博客

http://cherylgood.cn/

目录

  1. 子线程更新UI的几种方式
  2. Handler官方定义
  3. Handler源码分析
  4. Handler图解
  5. 使用Handler注意事项

1

子线程更新UI的几种方式

  1. view.post(Runnable action)
  2. activity.runOnUiThread(Runnable action)
  3. AsyncTask
  4. Handler

2

Handler官方定义

首先我们看下handler的官方定义:

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

Handler允许你通过使用一个与线程的MessageQueue相关联的Message和Runnable对象去发送和处理消息。 每个处理程序实例与单个线程和该线程的消息队列相关联。 当您创建一个新的处理程序时,它绑定到正在创建它的线程的线程/消息队列 - 从那时起,它将向消息队列传递消息和可运行文件,并在消息发出时执行它们 队列。

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

Handler有两个主要用途:(1)在可预见的时间内去调度消息和作为一些点的可运行程序(2)将不同于自己的线程执行的操作排入队列中。

Scheduling messages is accomplished with the post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long) methods. The post versions allow you to enqueue Runnable objects to be called by the message queue when they are received; the sendMessage versions allow you to enqueue a Message object containing a bundle of data that will be processed by the Handler's handleMessage(Message) method (requiring that you implement a subclass of Handler).

消息的调度是通过post(Runnable),postAtTime(Runnable,long),postDelayed(Runnable,long),sendEmptyMessage(int),sendMessage(Message),sendMessageAtTime(Message,long)和sendMessageDelayed(Message,long)来完成的 。 后台版本允许你将接收到的消息队列调用的Runnable对象排入队列; sendMessage版本允许你将包含将由处理程序的handleMessage(Message)方法处理的数据包(要求您实现Handler的子类)的Message对象排入队列。

When posting or sending to a Handler, you can either allow the item to be processed as soon as the message queue is ready to do so, or specify a delay before it gets processed or absolute time for it to be processed. The latter two allow you to implement timeouts, ticks, and other timing-based behavior.

当发布或发送到Handler时,你可以在消息队列准备就绪后立即处理该项目或者指定一个延迟时间去处理该消息队列,或者指定一个具体时间处理该消息。 后两者允许您实现超时,定时和其他基于时间的行为。

When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, but from your new thread. The given Runnable or Message will then be scheduled in the Handler's message queue and processed when appropriate.

当为你的应用创建一个进程时,其主线程专用于运行一个消息队列,该消息队列负责管理顶级应用程序对象(activitys, broadcast receivers 等)及其创建的任何窗口。 你可以创建你自己的线程并通过Handler与主应用程序线程进行通信。 这可以通过从你的新线程中调用同样的post或sendMessage方法来实现。 给定的Runnable或Message将在Handler的消息队列中进行调度,并在适当时进行处理。

3

Handler源码分析

在查看Handler源码之前,我们先了解几个类: Handler 、Looper、MessageQueue、Message、ThreadLocation

Handler我们就不在介绍该类,上面的官方文档已给出了详细的介绍,我们来看下其余几个:

  1. ThreadLocal:每个使用该变量的线程提供独立的变量副本,每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal内部是通过map进行实现的;
  2. Looper:可以理解为循环器,就是扶着管理一个消息循环队列(MessageQueue)的;
  3. MessageQueue:消息队列,用来存放handler发布的消息
  4. Message:消息体,封装了我们传输消息所需的数据结构。

ok,那么我们从哪里开始看起呢,好吧, 从创建一个Handler实例为入口,首先我们看handler的构造方法:

可以看到,有多个构造方法,但是最终都会调用到最后一个。我们看倒数第二个,有这么一句: mLooper = Looper.myLooper();这里我们看到了个looper,可以看到,在handler里面会有一个mlooper对象与之关联,我们先不看mlooper是怎么来的,我们先把下面的看完;继续看下面一句: mQueue = mLooper.mQueue;我们的handler里面也有一个队列的对象,实际上mQueue就是MessageQueue,后面我们会讲解到。好的,继续往下看, mCallback = callback;一般情况下mCallback是null,我们通常new 一个Handler是不是调用的无参构造方法?callback的作用后面也会讲解到,好的最后一句: mAsynchronous = async;表示我们的执行过程是异步的还是同步的,一般情况下,默认是异步的。

小结: Handler会存有Looper对象以及消息队列mQueue,通过关联looper与mQueue,可以想象,handler要把message插入消息队列中,最直接的方式当然是拿到消息队列的实例,实现消息的发送;

看了Handler的构造,接下来看Looper.mLooper:

可以看到,Looper.myLooper内部是调用了sThreadlocal.get();这个sThreadLocal其实就是我们之前说的ThreadLocal类的实例,他负责存储当先线程的Looper实例;是不是真的呢?我们看下sThreadLocal在哪里赋值的,很好,我们找到了一个prepare方法,看名字是准备的意思,也就是为我们准备我们需要的looper对象,继续看:

首先我们可以看到,会先判断sThreadLocal.get() != null,说明Looper.prepare()只能被调用一次哦,不然就会抛出异常,这样做是为了保证一个线程只有一个looper存在,然后我们的可以看到里面通过new Looper(quitAllowed)获得当先线程的looper,我们继续看Looper的构造方法:

在looper的构造方法里,主要做了两件事:

  1. 创建一个looper管理的消息队messageQueue;
  2. 获得当前的线程;

小结:Looper里面会存储当前的线程,以及所管理的消息队列mQueue,一个Looper只会管理一个消息队列MessageQueue;

从上面的代码中我们可以知道,在new 一个handler的同时,我们就获得了一个handler实例、一个当前线程的looper、一个looper管理的messagequeue,好像拥有了这三个对象,我们就可以发送消息了哦。

大家都知道looper从创建之后,就会开始循环,在looper类的顶部,官方给出了一段代码:

当我们使用handler发消息时,步骤是:

  1. 调用 Looper.prepare(); 初始化所需的looper以及messageQueue
  2. 实例化一个handler对象,我们可以在handleMessage获得message做一些操作,此时handleMessage方法是在当前的Looper中执行的,也就是说,如果当前的looper是UI Looper,那么你可以更新UI,如果当前looper不是UI Looper,那么你更新UI肯定会报错,你可能会说,我用handler时,好像都不用调用Looper.prepare();,我怎么知道我当前的looper是UI的还是不是呢,其实系统一般默认都帮我们获取了UI 的Looper,后面我们会讲解到;
  3. 调用 Looper.loop();让Looper跑起来吧!

Looper.prepare();我们前面已经分析过了,主要是实例化一个messageQueue,而且只能调用一次;那么我们重点就转移懂到 Looper.loop();看源码:

调用final Looper me = myLooper();获得一个looper,myLooper方法我们前面分析过,返回的是sThreadLocal中存储的Looper实例,当me==null抛出异常;所以,在looper执行loop跑起来之前,我们要记得调用prepare()哦。当获得当前的looper后,调用 final MessageQueue queue = me.mQueue; 获取looper管理的MessageQueue;然后我们可以看到一个很有意思的for语句: for (;;) {...} 这就是循环的开始了,此时我在想,我的天,这不是个无限死循话么?怎么可能呢?当然有退出的条件,不然不就傻逼了么!

我们可以看到:他会从looper的queue中获取message,当message==null,循环停止!

循环起来了,咱的looper也没闲着,他一直知道它的工作是什么,我们可以看到:msg.target.dispatchMessage(msg);通过调用msg对象里的target对象的dispatchMessage(msg)方法把消息处理了。其实msg对象里的target对象就是我们new出来的handler,我们后面会讲到。

小结:

looper主要做了如下工作:

  1. 将自己与当前线程关联在一起,通过ThreadLocal存储当前线程的looper,确保当前线程只有一个looper实例;
  2. 创建一个MessageQueue与当前looper绑定,通过prepare方法控制looper只能有一个messageQueue实例;
  3. 调用loop()方法,不断从MessageQueue中去取消息,通过调用msg.target.dispatchMessage(msg)处理;

分析完了looper、接下来当然是hanlder发送消息了,我们又回到了handler中,我们通过handler发消息,自然少不了我们得sendMessag方法,那么我们就从它入手吧:

可以看到我们的sendMessage有多种方法,但最终都会调用enqueueMessage方法,我们看enqueueMessage方法源码:

可以看到里面会讲当前的this赋值给msg.target,this 当前就是我们当前的handler了,这也就是之前在分析looper时说的,通过调用msg.target. dispatchMessage(msg)方法处理消息;后面后调用queue.enqueueMessage(msg, uptimeMillis);把消息放入当前的looper的MessageQueue队列中去处理,消息的发送流程就分析完了,发送了,接下来就是处理消息了!

我们用handler时,都是在handleMessage方法中处理消息的,那么我们就从handleMessage方法入手:

可以看到handleMessage是一个空的方法,我们看handleMessage在哪被调用的呢?

可以看到handleMessage在dispatchMessage中被调用了,奇怪,怎么有两个handleMessage方法呢?大家不要弄混了哦,我们handler的handleMessage方法返回值时void,所以mCallback.handleMessage肯定不是我们handler的了;

第一个县判断msg.callback!=null 调用 handleCallback(msg); 然后我们追进去看:

看到了run(),是不是想到了Runnable?其实message中的callback就是Runnable,我们可以从Message的创建函数中看到:

我们继续回到dispatchMessage方法:也就是说,如果我们的给massge设置了callback,那么我们的handleMessage方法就不会被执行了,当然,一般我们的massge.callback都是null的。后面就会继续判断mCallback!=null如果成立则调用mCallback.handleMessage(msg) mCallback其实是一个回调接口,可以看到,如果mCallback.handleMessage(msg)返回true,就不会执行我们的Handler.handleMessage方法,所以我们其实可以通过给handler添加Callback来实现一个message的过滤或者拦截功能。

我们的Handler.handleMessage经过重重阻挠,最终终于可以执行了。

总结:

  1. 在Looper.prepare()中会通过sThreadLocal保存一个looper实例,控制当前线程只能有一个looper实例;
  2. 创建looper实例时,会创建一个MessageQueue与looper关联;
  3. 因为looper只会存在一个实例,所以 当前线程也会只存在一个MessageQueue队列;
  4. 调用Looper.loop()让looper跑起来吧,然后looper就可以不停的从MessageQueue把消息拿出来,然后通过调用msg.target.dispatchMessage(msg)处理消息,也是让消息最终进入我们的Handler.handleMessage方法,被我们给处理了;所以我们在实例化handler时需要重写handleMessage方法;
  5. 实例化Handler时,handler中会获得当前线程的looper以及looper的messageQueue;
  6. 在调用sendMessage发送消息时,最终会调用enqueueMessage方法,在enqueueMessage方法里会将msg.target=handler,讲handler关联到msg中,这样looper在取出messageQueue中的消息时,才知道该消息是要发给那个handler处理的,将handler与msg关联后,就将msg加入队列中去了,等待looper处理。

4

Handler图解

5

使用Handler注意事项

  1. 创建massage对象时,推荐使用obtain()方法获取,因为Message内部会维护一个Message池用于Message的复用,这样就可以避免 重新new message而冲内心分配内存,减少new 对象产生的资源的消耗。
  2. handler 的handleMessage方法内部如果有调用外部activity或者fragment的对象,一定要用弱饮用,handler最好定义成static的,这样可以避免内存泄漏;为什么呢?因为一但handler发送了消息。而handler内部有对外部变量的引用,此时handler已经进入了looper的messageQueue里面。此时activity或者fragment退出了可是区域,但是handler内部持有其引用且为强引用时,其就不会立即销毁,产生延迟销毁的情况。

0 人点赞