Android入门教程 | Handler,Looper与MessageQueue使用与分析

从源码角度分析Handler。有利于使用 Handler 和分析 Handler 的相关问题。认识Looper与Handler的关系。
Handler 简介
一个 Handler 允许发送和处理 Message,通过关联线程的 MessageQueue 执行 Runnable 对象。
每个 Handler 实例都和一个单独的线程及其消息队列绑定。 可以将一个任务切换到 Handler 所在的线程中去执行。一个用法就是子线程通过 Handler 更新 UI。
Handler主要有2种用法:
  • 做出计划,在未来某个时间点执行消息和Runnable
  • 线程调度,在其他线程规划并执行任务
【Android入门教程 | Handler,Looper与MessageQueue使用与分析】要使用好 Handler,需要了解与其相关的 MessageQueueMessageLooper;不能孤立的看Handler。 Handler就像一个操作者(或者像一个对开发者开放的窗口),利用MessageQueueLooper来实现任务调度和处理。
Handler持有 Looper 的实例,直接持有looper的消息队列。
属性与构造器 Handler 类中持有的实例,持有 looper,messageQueue 等等。
final Looper mLooper; // Handler持有Looper实例 final MessageQueue mQueue; // Handler持有消息队列 final Callback mCallback;

在 Handler 的构造器中,我们可以看到 Handler 获取了 Looper 的消息队列。
public Handler(Callback callback, boolean async) { // rustfisher 处理异常 mLooper = Looper.myLooper(); // rustfisher 处理特殊情况... mQueue = mLooper.mQueue; // 获取的是Looper的消息队列 }public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; // 获取的是Looper的消息队列 mCallback = callback; mAsynchronous = async; }`


Handler 使用方法 Handler 发送和处理消息的几个方法
  • void handleMessage( Message msg): 处理消息的方法,该方法通常被重写。
  • final boolean hasMessage(int what): 检查消息队列中是否包含有what属性为指定值的消息
  • final boolean hasMessage(int what ,Object object) : 检查消息队列中是否包含有what好object属性指定值的消息
  • sendEmptyMessage(int what): 发送空消息
  • final Boolean send EmptyMessageDelayed(int what ,long delayMillis): 指定多少毫秒发送空消息
  • final boolean sendMessage(Message msg): 立即发送消息
  • final boolean sendMessageDelayed(Message msg,long delayMillis): 多少秒之后发送消息
Handler.sendEmptyMessage(int what) 流程解析
获取一个Message 实例,并立即将 Message 实例添加到消息队列中去。
简要流程如下:
// 立刻发送一个empty消息 sendEmptyMessage(int what) // 发送延迟为0的empty消息这个方法里通过Message.obtain()获取一个Message实例 sendEmptyMessageDelayed(what, 0) // 计算消息的计划执行时间,进入下一阶段 sendMessageDelayed(Message msg, long delayMillis)// 在这里判断队列是否为null若为null则直接返回false sendMessageAtTime(Message msg, long uptimeMillis)// 将消息添加到队列中 enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)// 接下来是MessageQueue添加消息 // MessageQueue.java boolean enqueueMessage(Message msg, long when)

可以看到,最后是把message添加到了messageQueue中。
Handler 取消任务
要取消任务时,调用下面这个方法removeCallbacksAndMessages(Object token)
public final void removeCallbacksAndMessages(Object token) { mQueue.removeCallbacksAndMessages(this, token); }

通过调用 Message.recycleUnchecked() 方法,取消掉与此 Handler 相关联的Message。
相关的消息队列会执行取消指令
void removeCallbacksAndMessages(Handler h, Object object)

消息驱动与 Handler Android是消息驱动的,实现消息驱动有几个要素
  • 消息的表示:Message
  • 消息队列:MessageQueue
  • 消息循环,用于循环取出消息进行处理:Looper
  • 消息处理,消息循环从消息队列中取出消息后要对消息进行处理:Handler
初始化消息队列
在Looper构造器中即创建了一个MessageQueue,Looper持有消息队列的实例。
发送消息
通过 Looper.prepare 初始化好消息队列后就可以调用 Looper.loop 进入消息循环了,然后我们就可以向消息队列发送消息, 消息循环就会取出消息进行处理,在看消息处理之前,先看一下消息是怎么被添加到消息队列的。
消息循环
Java 层的消息都保存在了 Java 层 MessageQueue 的成员 mMessages 中,Native 层的消息都保存在了 Native Looper 的 mMessageEnvelopes 中,这就可以说有两个消息队列,而且都是按时间排列的。
Message 和 MessageQueue
与 Handler 工作的几个组件 Looper、MessageQueue 各自的作用:
  • Handler:它把消息发送给 Looper 管理的 MessageQueue ,并负责处理Looper分给它的消息
  • MessageQueue:管理 Message,由 Looper 管理
  • Looper:每个线程只有一个Looper,比如UI线程中,系统会默认的初始化一个Looper对象,它负责管理 MessageQueue,不断的从MessageQueue中取消息,并将相对应的消息分给Handler处理。
Message Message 属于被传递,被使用的角色。Message 是包含描述和任意数据对象的“消息”,能被发送给 Handler。Message 包含 2 个 int 属性和一个额外的对象。 虽然构造器是公开的,但获取实例最好的办法是调用Message.obtain()Handler.obtainMessage()。这样可以从他们的可回收对象池中获取到消息实例。一般来说,每个Message实例持有一个Handler。
Message部分属性值
/*package*/ Handler target; // 指定的Handler/*package*/ Runnable callback; // 可以组成链表 // sometimes we store linked lists of these things /*package*/ Message next;

从这里也不难看出,每个 Message 都持有 Handler 实例。如果 Handler 持有Activity的引用,Activity onDestroy 后 Message 却仍然在队列中,因为 Handler 与Activity的强关联,会造成 Activity 无法被 GC 回收,导致内存泄露。
因此在Activity onDestroy 时,与Activity关联的Handler应清除它的队列由Activity产生的任务,避免内存泄露。
重置自身的方法,将属性全部重置
public void recycle() void recycleUnchecked()

获取 Message 实例的常用方法,得到的实例与传入的 Handler 绑定
/** * Same as {@link #obtain()}, but sets the value for the target member on the Message returned. * @param hHandler to assign to the returned Message object's target member. * @return A Message object from the global pool. */ public static Message obtain(Handler h) { Message m = obtain(); m.target = h; return m; }

将消息发送给 Handler
/** * Sends this Message to the Handler specified by {@link #getTarget}. * Throws a null pointer exception if this field has not been set. */ public void sendToTarget() { target.sendMessage(this); // target 就是与消息绑定的Handler }

调用这个方法后,Handler 会将消息添加进它的消息队列MessageQueue中。
MessageQueue 持有一列可以被 Looper 分发的 Message。一般来说由 Handler 将 Message 添加到 MessageQueue 中。 获取当前线程的 MessageQueue 方法是Looper.myQueue()。通过 Looper.getMainLooper() 获取到主线程的 looper。
Looper 简介
Looper 与 MessageQueue 紧密关联。在一个线程中运行的消息循环。线程默认情况下是没有与之管理的消息循环的。
要创建一个消息循环,在线程中调用 prepare,然后调用 loop。即开始处理消息,直到循环停止。大多数情况下通过Handler来与消息循环互动。
Handler 与 Looper 在线程中交互的典型例子
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); // 为当前线程准备一个Looper // 创建Handler实例,Handler会获取当前线程的Looper // 如果实例化Handler时当前线程没有Looper,会报异常 RuntimeException mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); // Looper开始运行 } }

调用了 Looper.loop() 之后,looper 开始运行。当 looper 的 messageQueue 中没有消息时,相关的线程处于什么状态呢? 查看looper的源码,看到loop方法里面有一个死循环。queue.next()方法是可能会阻塞线程的。如果从queue中获取到null,则表明此消息队列正在退出。此时looper的死循环也会被返回。
for (; ; ) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; }

调用 looper 的 quit 方法,实际上调用了 mQueue.quit(false)。消息队列退出后,looper 的 loop 死循环也被退出了。
进入 MessageQueue 的next方法去看,发现里面也有一个死循环。没有消息时,这个死循环会阻塞在nativePollOnce这个方法。
Message next() { // ... for (; ; ) { // ... nativePollOnce(ptr, nextPollTimeoutMillis); // 处理message对象

我们知道 Thread 有 New(新建,未运行),RUNNABLE(可运行),BLOCKED,WAITING(线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll),TIMED_WAITING,TERMINATED(已经执行完毕)这几种状态。
消息队列中没有消息,在nativePollOnce方法中“等待”。nativePollOnce效果上大致等同于Object.wait(),但它们的实现完全不同。nativePollOnce使用 epoll, 而 Object.wait 使用 futex
“等待”时,相关线程则处于WAITING状态。
Looper中的属性 Looper 持有 MessageQueue;唯一的主线程 Looper sMainLooper;Looper 当前线程 mThread; 存储 Looper 的 sThreadLocal
// sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal sThreadLocal = new ThreadLocal(); private static Looper sMainLooper; // guarded by Looper.classfinal MessageQueue mQueue; // Handler会获取这个消息队列实例(参考Handler构造器) final Thread mThread; // Looper当前线程

ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。
Looper方法 准备方法,将当前线程初始化为 Looper。退出时要调用 quit
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实例存入了sThreadLocal }

prepare方法新建 Looper 并存入 sThreadLocal sThreadLocal.set(new Looper(quitAllowed))
ThreadLocal
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }

当要获取 Looper 对象时,从 sThreadLocal 获取
// 获取与当前线程关联的Looper,返回可以为null public static @Nullable Looper myLooper() { return sThreadLocal.get(); }

在当前线程运行一个消息队列。结束后要调用退出方法quit()
public static void loop()

准备主线程 Looper。Android 环境会创建主线程 Looper,开发者不应该自己调用这个方法。
UI线程,它就是 ActivityThread,ActivityThread 被创建时就会初始化 Looper,这也是在主线程中默认可以使用 Handler 的原因。
public static void prepareMainLooper() { prepare(false); // 这里表示了主线程Looper不能由开发者来退出 synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }

获取主线程的 Looper。我们开发者想操作主线程时,可调用此方法
public static Looper getMainLooper()

同一个 Thread 的不同 Handler 与 UI 线程对应的 MainLooper,可以关联多个 Handler。 多个 Handler 之间的计划任务不会互相影响。比如有 2 个关联了 UI 线程的 handler。
Handler mMainHandler1; Handler mMainHandler2; private void initUtils() { mMainHandler1 = new Handler(Looper.getMainLooper()); mMainHandler2 = new Handler(Looper.getMainLooper()); Log.d(TAG, "mMainHandler1 post 任务"); mMainHandler1.postDelayed(new Runnable() { @Override public void run() { Log.d(TAG, "mMainHandler1的演示任务已执行 rustfisher"); } }, 1500); mMainHandler2.removeCallbacksAndMessages(null); }

mMainHandler2 取消它的任务并不会影响 mMainHandler1。
Handler 相关面试题
1. ?线程?定不能更新 UI 吗?
答:不?定。
  • Activity存在?种审计机制,这个机制会在Activity完全显示之后?作,如果?线程在Activity完全显示
    之前更新UI是可?的;
  • SurfaceView:多媒体视频播放,也可以在?线程中更新UI
  • Progress:进度相关控件,也可以在?线程中更新UI
2. 给我说说 Handler 的原理
3. Handler 导致的内存泄露你是如何解决的?
4. 如何使?Handler让?线程和?线程通信?
  • 发送消息的?线程
    package com.cdc.handler; import android.os.Handler; import android.os.Message; import android.os.SystemClock; //发送消息的?线程 public class Thread1 extends Thread { private Handler handler; public Thread1(Handler handler){ super.setName("Thread1"); this.handler=handler; } @Override public void run() { Message msg = Message.obtain(); msg.what = 1; msg.obj = System.currentTimeMillis()+""; handler.sendMessage(msg); System.out.println((Thread.currentThread().getName() + "----发送了消 息!" + msg.obj)); SystemClock.sleep(1000); } }

  • 接收消息的?线程
    package com.cdc.handler; import android.os.Handler; import android.os.Looper; //接收消息的?线程 public class Thread2 extends Thread{ private Handler handler2; public Handler getHandler(){//注意哦,在run执?之前,返回的是null return handler2; } public Thread2(){ super.setName("Thread2"); } @Override public void run() { //在?线程??新建Handler的实例,需要先调?Looper.prepare(); 否则会报 错:Can't create handler inside thread that has not called Looper.prepare() Looper.prepare(); handler2 = new Handler(){ public void handleMessage(android.os.Message msg) { //这?处理消息 System.out.println(("收到消息了:" + Thread.currentThread().getName() + "----" + msg.obj)); }; }; Looper.loop(); } }

  • 调?
    private Handler myHandler=null; private Thread2 thread1; private Thread1 thread2; @OnClick(R.id.handler3) public void handler3(){ thread1=new Thread2(); thread1.start(); myHandler=thread1.getHandler(); while(myHandler==null){ SystemClock.sleep(100); myHandler=thread1.getHandler(); } thread2=new Thread1(myHandler); thread2.start(); }

5. HandlerThread是什么 & 原理 & 使?场景?
6. IdleHandler是什么?
7. ?个线程能否创建多个Handler,Handler和Looper之间的对应关系?
8 为什么Android系统不建议?线程访问UI?
?先,UI控件不是线程安全的,如果多线程并发访问UI控件可能会出现不可预期的状态
那为什么系统不对UI控件的访问加上锁机制呢? 缺点有两个:
  • 加上锁机制会让UI访问的逻辑变得复杂;
  • 锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执?
鉴于这两个缺点,最简单且?效的?法就是采?单线程模型来处理UI操作,所以源码ViewRootImpl中会有对线程的?个判断,代码如下: frameworks/base/core/java/android/view/ViewRootImpl.java
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }

对于开发者来说也不是很麻烦,只是通过 handler 切换?下 UI 访问的执?线程即可
9. Looper 死循环为什么不会导致应?卡死?
10. 使? Handler 的 postDealy 后消息队列有什么变化?
Android零基础入门教程视频参考

    推荐阅读