Android入门教程 | Handler,Looper与MessageQueue使用与分析
安卓开发好多年
2021年11月25日 17:40

从源码角度分析Handler。有利于使用 Handler 和分析 Handler 的相关问题。认识Looper与Handler的关系。

一个 Handler 允许发送和处理 Message,通过关联线程的 MessageQueue 执行 Runnable 对象。 每个 Handler 实例都和一个单独的线程及其消息队列绑定。 可以将一个任务切换到 Handler 所在的线程中去执行。一个用法就是子线程通过 Handler 更新 UI。

Handler主要有2种用法:

  • 做出计划,在未来某个时间点执行消息和Runnable

  • 线程调度,在其他线程规划并执行任务

要使用好 Handler,需要了解与其相关的 , 和 ;不能孤立的看Handler。 Handler 就像一个操作者(或者像一个对开发者开放的窗口),利用 和 来实现任务调度和处理。

Handler 持有 Looper 的实例,直接持有looper的消息队列。

属性与构造器

Handler 类中持有的实例,持有 looper,messageQueue 等等。

代码块
JavaScript
自动换行
复制代码
final Looper mLooper; // Handler持有Looper实例
final MessageQueue mQueue; // Handler持有消息队列
final Callback mCallback;
复制成功

在 Handler 的构造器中,我们可以看到 Handler 获取了 Looper 的消息队列。

代码块
JavaScript
自动换行
复制代码
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 实例添加到消息队列中去。 简要流程如下:

代码块
JavaScript
自动换行
复制代码
// 立刻发送一个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 取消任务

要取消任务时,调用下面这个方法

代码块
JavaScript
自动换行
复制代码
public final void removeCallbacksAndMessages(Object token) {
    mQueue.removeCallbacksAndMessages(this, token);
}
复制成功

通过调用  方法,取消掉与此 Handler 相关联的Message。

相关的消息队列会执行取消指令

代码块
JavaScript
自动换行
复制代码
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 中,这就可以说有两个消息队列,而且都是按时间排列的。

与 Handler 工作的几个组件 Looper、MessageQueue 各自的作用:

  • Handler:它把消息发送给 Looper 管理的 MessageQueue ,并负责处理Looper分给它的消息

  • MessageQueue:管理 Message,由 Looper 管理

  • Looper:每个线程只有一个Looper,比如UI线程中,系统会默认的初始化一个Looper对象,它负责管理 MessageQueue,不断的从MessageQueue中取消息,并将相对应的消息分给Handler处理。

Message

Message 属于被传递,被使用的角色。Message 是包含描述和任意数据对象的“消息”,能被发送给 。Message 包含 2 个 int 属性和一个额外的对象。 虽然构造器是公开的,但获取实例最好的办法是调用。这样可以从他们的可回收对象池中获取到消息实例。一般来说,每个Message实例持有一个Handler。

Message部分属性值

代码块
JavaScript
自动换行
复制代码
/*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产生的任务,避免内存泄露。

重置自身的方法,将属性全部重置

代码块
JavaScript
自动换行
复制代码
public void recycle()
void recycleUnchecked()
复制成功

获取 Message 实例的常用方法,得到的实例与传入的 Handler 绑定

代码块
JavaScript
自动换行
复制代码
/**
 * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
 * @param h  Handler to assign to the returned Message object's <em>target</em> member.
 * @return A Message object from the global pool.
 */
public static Message obtain(Handler h) {
    Message m = obtain();
    m.target = h;

    return m;
}
复制成功

将消息发送给 Handler

代码块
JavaScript
自动换行
复制代码
/**
 * 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

持有一列可以被 Looper 分发的 Message。一般来说由 Handler 将 Message 添加到 MessageQueue 中。 获取当前线程的 MessageQueue 方法是。通过  获取到主线程的 looper。

Looper 与 MessageQueue 紧密关联。在一个线程中运行的消息循环。线程默认情况下是没有与之管理的消息循环的。 要创建一个消息循环,在线程中调用 prepare,然后调用 loop。即开始处理消息,直到循环停止。大多数情况下通过Handler来与消息循环互动。

Handler 与 Looper 在线程中交互的典型例子

代码块
JavaScript
自动换行
复制代码
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 开始运行。当 looper 的 messageQueue 中没有消息时,相关的线程处于什么状态呢? 查看looper的源码,看到loop方法里面有一个死循环。方法是可能会阻塞线程的。如果从queue中获取到null,则表明此消息队列正在退出。此时looper的死循环也会被返回。

代码块
JavaScript
自动换行
复制代码
for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }
复制成功

调用 looper 的 quit 方法,实际上调用了 。消息队列退出后,looper 的 loop 死循环也被退出了。

进入 MessageQueue 的方法去看,发现里面也有一个死循环。没有消息时,这个死循环会阻塞在这个方法。

代码块
JavaScript
自动换行
复制代码
Message next() {
    // ...
    for (;;) {
        // ...
        nativePollOnce(ptr, nextPollTimeoutMillis);
        // 处理message对象
复制成功

我们知道 Thread 有 New(新建,未运行),RUNNABLE(可运行),BLOCKED,WAITING(线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll),TIMED_WAITING,TERMINATED(已经执行完毕)这几种状态。

消息队列中没有消息,在方法中“等待”。效果上大致等同于,但它们的实现完全不同。使用 , 而  使用 

“等待”时,相关线程则处于状态。

Looper中的属性

Looper 持有 MessageQueue;唯一的主线程 Looper ;Looper 当前线程 ; 存储 Looper 的 

代码块
JavaScript
自动换行
复制代码
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class

final MessageQueue mQueue; // Handler会获取这个消息队列实例(参考Handler构造器)
final Thread mThread; // Looper当前线程
复制成功

ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。

Looper方法

准备方法,将当前线程初始化为 Looper。退出时要调用 quit

代码块
JavaScript
自动换行
复制代码
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
}
复制成功

方法新建 Looper 并存入 sThreadLocal 

代码块
JavaScript
自动换行
复制代码
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 对象时,从  获取

代码块
JavaScript
自动换行
复制代码
// 获取与当前线程关联的Looper,返回可以为null
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
复制成功

在当前线程运行一个消息队列。结束后要调用退出方法

代码块
JavaScript
自动换行
复制代码
public static void loop()
复制成功

准备主线程 Looper。Android 环境会创建主线程 Looper,开发者不应该自己调用这个方法。 UI线程,它就是 ActivityThread,ActivityThread 被创建时就会初始化 Looper,这也是在主线程中默认可以使用 Handler 的原因。

代码块
JavaScript
自动换行
复制代码
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。我们开发者想操作主线程时,可调用此方法

代码块
JavaScript
自动换行
复制代码
public static Looper getMainLooper()
复制成功

同一个 Thread 的不同 Handler

与 UI 线程对应的 MainLooper,可以关联多个 Handler。 多个 Handler 之间的计划任务不会互相影响。比如有 2 个关联了 UI 线程的 handler。

代码块
JavaScript
自动换行
复制代码
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。

1. ⼦线程⼀定不能更新 UI 吗?

答:不⼀定。

  • Activity存在⼀种审计机制,这个机制会在Activity完全显示之后⼯作,如果⼦线程在Activity完全显示

之前更新UI是可⾏的;

  • SurfaceView:多媒体视频播放,也可以在⼦线程中更新UI

  • Progress:进度相关控件,也可以在⼦线程中更新UI

2. 给我说说 Handler 的原理

3. Handler 导致的内存泄露你是如何解决的?

4. 如何使⽤Handler让⼦线程和⼦线程通信?

  • 发送消息的⼦线程

代码块
JavaScript
自动换行
复制代码
  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);
    }
}
复制成功

  • 接收消息的⼦线程

代码块
JavaScript
自动换行
复制代码
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();
    }
}
复制成功

  • 调⽤

代码块
JavaScript
自动换行
复制代码
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

代码块
JavaScript
自动换行
复制代码
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 后消息队列有什么变化?