专栏首页AnRFDevAndroid Handler,Looper 与 MessageQueue 使用与分析

Android Handler,Looper 与 MessageQueue 使用与分析

Handler 简介

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

Handler主要有2种用法:

  • 做出计划,在未来某个时间点执行消息和Runnable
  • 线程调度,在其他线程规划并执行任务

要使用好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) {
    // 处理异常
    mLooper = Looper.myLooper();
    // 处理特殊情况...
    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各自的作用:

  • 1.Handler:它把消息发送给Looper管理的MessageQueue,并负责处理Looper分给它的消息
  • 2.MessageQueue:管理Message,由Looper管理
  • 3.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 <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

/**
 * 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方法中“等待”。相关线程则处于RUNNABLE状态。

参考:https://stackoverflow.com/questions/38818642/android-what-is-message-queue-native-poll-once-in-android

Looper中的属性

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

// 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

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<T>

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的演示任务已执行");
        }
    }, 1500);
    mMainHandler2.removeCallbacksAndMessages(null);
}

mMainHandler2取消它的任务并不会影响mMainHandler1。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android开发之漫漫长途 Ⅶ——Android消息机制(Looper Handler MessageQueue Message)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索...

    LoveWFan
  • Android 源码分析 —— Handler、Looper 和 MessageQueue

    在分析 Toast 源码的过程中我们涉及到了 Handler,这个在 Android 开发里经常用到的类——线程切换、顺序执行、延时执行等等逻辑里往往少不了它的...

    mzlogin
  • Handler都没搞懂,拿什么去跳槽啊?!

    做 Android 开发肯定离不开跟 Handler 打交道,它通常被我们用来做主线程与子线程之间的通信工具,而 Handler 作为 Android 中消息机...

    程序亦非猿
  • Android Handler异步通信:深入详解Handler机制源码

    在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理

    Carson.Ho
  • Android Handler机制4之Looper与Handler简介

    要理解Handler的消息机制,就不得不说Handler/Looper/Message/MessageQueue/Message这四4个类,下面我们先大概了解下...

    隔壁老李头
  • 音视频直播--深度理解Handler 与 HandlerThread

    大家好,今天我为大家讲解一下Android系统下的Handler机制。做过Android系统开发的人都清楚,App应用程序的主线程是决对不能被阻塞的,因为它的主...

    音视频_李超
  • Android Handler与Looper原理浅析

    本文分析下Android的消息处理机制,主要是针对Handler、Looper、MessageQueue组成的异步消息处理模型,先主观想一下这个模型需要的材料:

    看书的小蜗牛
  • Handler面试必问八大题:如何深挖原理进大厂?1万+字带你详细剖析!

    Handler一直是面试过程中的常客,我们今天来看看围绕Handler究竟能玩出那些花儿来。

    Android技术干货分享
  • android 开发Handler源码剖析

    Android的消息机制主要是Handler的运行机制,而讲Handler的机制,又需要和MessageQueue和Looper结合。MessageQueue中...

    xiangzhihong
  • Handler与Looper方法源码解析

    在线程的run方法中调用Looper的prepare()方法进行准备工作,准备之后就可以通过Looper.myLooper获取到当前的线程的Looper了。使用...

    Android架构
  • Android 异步任务 设置 超时使用handler更新通知功能

    一个Handler允许你发送和处理消息(Message)以及与一个线程的消息队列相关的Runnable对象。每个Handler实例都和单个线程以及该线程的消息队...

    砸漏
  • android入门 — 多线程(一)

    Mister24
  • 深入Android Handler,MessageQueue与Looper关系

    一说到Android的消息机制,自然就会联想到Handler,我们知道Handler是Android消息机制的上层接口,因此我们在开发过程中也只需要和Handl...

    砸漏
  • 每个 Android 开发者必须知道的消息机制问题总结

    不能,一个线程对应一个Looper对象,通过ThreadLocal保证一个线程只有一个Looper与之对应,如果多次调用Looper.prepare();则会抛...

    哲洛不闹
  • Android多线程:一步步带你源码解析HandlerThread

    总结 1. 为当前工作线程(即步骤1创建的线程)创建1个Looper对象 & MessageQueue对象 2. 通过持有锁机制来获得当前线程的Loop...

    Carson.Ho
  • 笔记——安卓消息机制Handler(十六)

    1、定义:Android的消息机制主要是指Handler的运行机制,Handler并不是专门用于更新UI的,它只是常被开发者用来更新UI,是同一个进程中线程间的...

    紫兮木溪
  • Android开发之Handler的前世今生

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

    陈宇明
  • Android开发:详解Handler的内存泄露

    上面提到,在Java里,非静态内部类和匿名类都会潜在的引用它们所属的外部类。 但是,静态内部类不会。

    Carson.Ho
  • 「细品源码」 Android 系统的血液:Handler

    作为 Android 开发者,相信对于 Handler 的使用早已烂熟于心。Handler 对于 Android 非常重要,可以说,没有它,Android Ap...

    开发的猫

扫码关注云+社区

领取腾讯云代金券