Android 消息机制 —— Handler

Handler 消息机制是用于同一进程中的多个线程之间进行通信的。由于工作线程与主线程共享地址空间,即 Handler 实例对象位于线程间共享的内存堆上,所以工作线程与主线程都能直接使用该对象,只需要注意多线程的同步问题。工作线程通过 Handler 向其消息队列 MessageQueue 中添加新 Message,主线程一直处于 loop() 方法中,当收到新的 Message 时按照一定规则分发给相应的 handleMessage() 方法来处理。所以说,Handler 消息机制用于同进程的线程间通信的核心是线程间共享内存空间,而不同进程拥有不同的地址空间,也就不能用 Handler 来实现进程间通信。

Handler 消息机制是由 MessageMessageQueueHandlerLooper 共同完成的。

Handler 消息机制

既然是用来通信的消息机制,那用什么来通信呢?当然是 Message,下面就先介绍一下 Message。

Message

Message 默认提供了两个 int 属性和一个 object 属性,能够满足我们大多数情况的需求。

关于 Message 主要需要注意的是,怎样正确的创建一个 Message 对象?尽管 Message 的构造函数是公共的,但是最好的方法(效率高,避免重复创建对象)却是通过调用 Message.obtain() 或者 Handler.obtainMessage() 方法,从消息池中回收的 Message 中获取。

Message next;
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;

上面几个属性是 Message 中声明的,每个 Message 对象都可以通过为 next 属性赋值,添加一个后继元素,这样多个 Message 对象就组成了一个单链表;而 sPool 就是链表的表头位置的 Message 对象,也就是头指针。此外我们还可以了解到这个链表的最大长度为 50。

那么这个链表有什么用呢?

答案就是把已经用过的 Message 对象缓存起来,避免重复创建 Message 对象,这个缓存是通过 recyclerUnchecked() 方法实现的,而且这个机制是在 Looper 中消息被处理完成之后触发的。

现在明白了消息池中的消息是怎么来的,再来看 obtain() 这个方法是怎么从消息池中取出消息的。

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            // 如果消息池的第一个消息 sPool 不为null,也就意味着消息池中有消息,就将它从消息池中取出来。
            Message m = sPool;
            // 将 sPool 改为消息池的第二个消息
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    // 如果消息池中没有 Message,就创建新的 Message。
    return new Message();
}

现在已经通过正确的方式得到了一个 Message 对象实例,那么我们发送的消息在哪保存呢?下面就到了 MessageQueue 出场了。

MessageQueue

MessageQueue 的构造方法:

// True if the message queue can be quit.
private final boolean mQuitAllowed;

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

mQuitAllowed 用来标记这个消息队列是否可以退出,事实也证明这个变量只在 quit() 方法中用到了,主线程是不可以退出的。我们创建的 MessageQueue 都是可以退出的,严格来说是必需退出的。因为这个 MessageQueue 是在 Looper 中创建的,而且创建时也确实是传入的 true,后面在 Looper 中会介绍。

MessageQueue 的两大主要作用就是:

  • 保存消息:boolean enqueueMessage(Message msg, log when)
  • 取出消息:Message next()

enqueueuMessage()

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;
        // mMessages 代表链表头结点的消息
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // 如果 p 为 null 表示 MessageQueue 中没有消息,如果 when = 0 或者触发时间比链表头结点的消息时间还早,就直接插入链表头部 
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                // 要插入 msg 的前驱元素
                prev = p;
                // 要插入 msg 的后继元素
                p = p.next;
                if (p == null || when < p.when) {
                    // p 为 null 表示到达链表末尾,when < p.when 表示新 msg 的触发时间比 p 的早,插入到它的前面就行了。
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

可以看出,MessageQueue 是将每个 Message 按照 when(触发时间)进行排列,并存储到链表中,当有新的 Message 需要添加时,从链表的第一个 Message 开始循环遍历,直到新 Message 的 when 比当前遍历到的 Message 的 when 要早,就把新 Message 插入到当前遍历到的 Message 之前,如果没有遍历到,就将新的 Message 插入到链表尾部。

next()

主要是从 MessageQueue 中取出头部的消息,具体细节都是涉及到数据结构的操作。

removeMessages()

Handler 中提供了几个 removeMessages() 和 removeCallbacks() 以及 removeCallbacksAndMessages() 方法,最终都是调用内部的 messageQueue 的 removeMessages() 方法,将消息从 MessageQueue 中移除。

现在 Message 和保存 Message 的 MessageQueue 都了解了,可谓是万事具备只欠东风了,下面开始发射。。。

Handler

Handler 允许我们发送和处理与当前线程关联的消息队列中的消息对象。每个 Handler 实例都与单个线程以及该线程的消息队列相关联。当创建一个新的 Handler 时,它绑定到创建它的线程和线程的消息队列上。这样,它就可以将消息传递到消息队列,并在消息从队列出来时执行它们。

当应用程序创建一个进程时,其主线程将专门运行一个消息队列,负责管理顶层应用程序对象(activities, broadcast,receivers 等)以及它们创建的任何窗口。我们可以创建自己的线程,并通过 Handler 的 post 或 sendMessage 方法与应用程序主线程进行通信,然后 Message 将在 Handler 的 MessageQueue 中进行调度,并在适当的时候进行处理。

要使用 Handler,首先要创建一个 Handler 的实例,Handler 提供了很多构造方法,主要分为两大类:

第一类构造方法

public Handler() {
    this(null, false);
}

public Handler(Callback callback) {
    this(callback, false);
}

public Handler(boolean async) {
    this(null, async);
}

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        // 匿名类、内部类或本地类都必须申明为static,否则会警告可能出现内存泄露
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

第一类构造方法,都是默认使用当前线程的 Looper 与 Handler 相关联,并且可以设置回调和是否为异步。Ok,既然有了构造方法,我们就可以使用它了。于是我们就这样创建一个 Handler 对象。

public class SampleActivity extends Activity {

  private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ... 
    }
  }
}

上述代码没问题吧,但是会发现 Android Studio 会发出如下警告:

Warning

This Handler class should be static or leaks might occur (anonymous android.os.Handler).
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

巴拉巴拉一大堆,其实就是告诉我们这样创建 Handler 可能会发生内存泄漏。因为在 Java 中,非静态内部类或匿名类会持有其外部类的引用,导致即使外部类不再使用也不能被垃圾回收机制回收。如果 Handler 是在非主线程中使用 Looper 或 MessageQueue,则没有问题(不明白为什么,虽然非主线程的 Loop 在 MessageQueue 中没有消息后就会退出 loop() 方法,但消息没有处理的时候还是会持有 Activity 的引用啊)。如果 Handler 在使用主线程(主线程的 loop() 方法不会退出)的 Looper 或 MessageQueue,则需要将 Handler 声明为静态类; 在外部类中,实例化 WeakReference 到外部类,并在实例化 Handler 时将此对象传递给Handler; 使用WeakReference对象来引用外部类的所有成员。

我们再来捋一遍发生内存泄漏的原因:我们发送的 Message 对象持有 Activity 中的 Handler 的引用,Handler 又隐式的持有它的外部类(也就是 Activity )的引用。而这个引用会一直存在,直到这个消息被处理,所以垃圾回收机制就没法回收这个 Activity,内存泄露就发生了。

因此创建 Handler 的正确姿势如下:

public class SampleActivity extends Activity {

    private static class MyHandler extends Handler {

        private WeakReference<SampleActivity> activityWeakReference;

        private MyHandler(SampleActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            SampleActivity sampleActivity = activityWeakReference.get();
            if (sampleActivity != null) {
                sampleActivity.handleMessage(msg);
            }
        }
    }

    private MyHandler mHandler = new MyHandler(SampleActivity.this);

    private void handlerMessage(Message msg) {
        // 处理消息
    }
}

第二类构造方法

public Handler(Looper looper) {
    this(looper, null, false);
}

public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

第二类构造方法可以设置指定的 Looper 与 Handler 相关联,当然同样可以设置回调和是否为异步。

发送消息

现在我们已经创建了 Handler 的实例,接下来就可以用它来发送消息了。

Handler 发送消息主要分为两大类:

  • Message 类型的消息
  • Runnable 类型的消息(只是将 runnable 作为 message.callback,最终还是转换为 Message 类型的消息)

消息分发

消息发送后,会进入当前 Handler 的 MessageQueue 中,而 Handler 持有的 MessageQueue 其实就是与当前线程相关联的 Looper 持有的 MessageQueue,Looper 的 loop() 方法,会不断的从 MessageQueue 中取出消息进行分发,这个分发机制就是通过调用 Message 中的 target (其实就是Handler) 的 dispatchMessage() 方法实现的。

Looper.java

public static void loop() {
    ...
    msg.target.dispatchMessage(msg);
    ...
}

Message.java

/*package*/ Handler target;

Handler.java

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    ...
}

通过上面几段代码可以看出,msg.target 就是一个 Handler 对象,而这个 target 是在 enqueueMessage() 方法中被赋值的,而且这个值就是 Handler 实例本身。下面就可以看 Handler 的 dispatchMessage() 方法了。

Handler.java

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        // 如果 message 设置了 callback,也就是 runnable 消息,调用 callback.run()方法。
        // 源代码为 handleCallback(msg);实际上 handleCallback(msg) 的具体实现就是下面这行代码。
        msg.callback.run();
    } else {
        // 如果 handler 设置了 callback,执行 callback 回调。
        if (mCallback != null) {
            // 如果 callback 消费了该消息则不再有后续操作
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 这个方法默认是空的,需要重写该方法来处理消息。
        handleMessage(msg);
    }
}

从 dispatchMessage 发放中,还可以看出消息分发是有三个级别的:

  1. Message 的回调方法 callback 不为空时,则回调方法 msg.callback.run()
  2. Handler 的 mCallback 成员变量不为空时,则回调方法 mCallback.handleMessage(msg)
  3. Handler 自身的回调方法 handleMessage(),该方法默认为空,我们可以通过重写该方法来完成具体的逻辑。

现在消息也已经发送了,剩下的就交给 Looper 来处理了。

Looper

Looper 是在一个线程中进行消息循环的类。每个线程默认情况下没有与它相关联的 Looper,可以通过调用 Looper.prepare() 方法创建,创建完成后调用Lopper.loop()方法开始循环处理消息。

Looper.prepare()

上面已经提过了,可以通过调用 Looper.prepare() 方法创建 Looper 的实例与一个线程相关联,那就先看看这个方法。

Looper提供了两个prepare()方法:

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));
}

我们只能调用无参的 prepare()方法(有参的方法是私有的),而且无参的方法内部也是调用有参的方法,并传入参数 true,表示允许退出,false 表示不允许退出。而直接调用这个私有的构造方法,并传入 false 的地方只有一个,那主是 prepareMainLooper() 方法:

/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

ThreadLocal

再回到prepare()方法,第一行中的 sThreadLocal 是什么?

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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));
}

可以看到 sThreadLocal 是一个 ThreadLocal 类型的静态变量。

什么是 ThreadLocal 呢?

ThreadLocal 是一个用于创建线程局部变量的类。

线程局部变量又是什么呢?

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用 ThreadLocal 创建的变量只能被当前线程访问,其他线程则无法访问和修改,这就是线程局部变量。

ThreadLocal的特点:

  • Global:在当前线程中,任何地方都可以访问到 ThreadLocal 的值。
  • Local:该线程的 ThreadLocal 的值只能被该线程自己访问和修改,一般情况下 其他线程访问不到。

下面是通过在主线程中创建 ThreadLocal 实例并为其赋值,然后测试子线程能否成功访问的示例:

public static void main(String[] args) {
    final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    threadLocal.set("hello");
    String s = threadLocal.get();
    System.out.printf("当前线程 %s %s\n", Thread.currentThread().getName(), s);
    new Thread() {
        @Override
        public void run() {
            super.run();
            String s = threadLocal.get();
            System.out.printf("当前线程 %s %s\n", Thread.currentThread().getName(), s);
        }
    }.start();
}

打印结果:

当前线程 main hello
当前线程 Thread-0 null

上面说一般情况下 ThreadLocal 的值只能被当前线程访问,那么当然就存在特殊情况了,我们可以使用 ThreadLocal 的子类 InheritableThreadLocal 实现在子线程中访问主线程中创建的值。

final ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("hello");
System.out.printf("当前线程 %s %s\n", Thread.currentThread().getName(), threadLocal.get());
new Thread() {
    @Override
    public void run() {
        super.run();
        System.out.printf("当前线程 %s %s\n", Thread.currentThread().getName(), threadLocal.get());
    }
}.start();
当前线程 main hello
当前线程 Thread-0 hello

结果确实是主线程和子线程都可以访问。

我们使用 ThreadLocal 的目的就是通过它的线程局部变量这个特性,保证数据不能被其他线程访问和修改。

这里既然关系到数据的访问与修改,那么必然就联系到了 ThreadLocal 内部的 set() 和 get() 方法了。

ThreadLocal.java

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
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();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

Thread.java

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. 
 */
ThreadLocal.ThreadLocalMap threadLocals = null;

通过 set() 和 get() 方法的前两行代码,可以看到它们都是首先获取当前线程的实例,然后再获取当前线程的 threadLocals 属性,最后才去对这个 threadLocals 进行赋值或取值操作。这样就保证了每个线程操作的只是它自己的 threadLocals,从而实现线程局部变量的效果

Looper 的构造方法

现在已经明白了 ThreadLocal 的实现原理,再次回到 prepare() 方法。

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));
}

当我们第一次调用 sThreadLocal.get() 方法时,得到的肯定是 null,所以就向 sThreadLocal 中赋值一个 Looper 对象。

下面看一下 Looper 的构造方法:

final MessageQueue mQueue;
final Thread mThread;

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

在构造方法中可以看到,分别为 mQueue 和 mThread 赋值,而且 quitAllowed 也传递给 MessageQueue 的构造方法,还记得前面在 MessageQueue 中已经介绍过了吗?而且我们创建的 MessageQueue 都是必须退出的,只有主线程才不可以也不能退出。

其实 Looper 只有这一个私有的构造方法,这再一次证明我们不能直接创建 Looper 的实例,而是应该通过调用 Looper.prepare() 方法创建。

到此为止,我们通过调用 Looper.prepare() 方法已经创建了一个与当前线程绑定,并通过 ThreadLocal 保证每个线程只有一个的 Looper 实例,同时这个 Looper 实例持有一个 MessageQueue 对象实例。

Looper.loop()

现在有了 MessageQueue 的实例,我们就可以调用 Looper.loop() 循环处理消息了。

下面是精简过的 loop() 方法:

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        msg.recycleUnchecked();
    }
}

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

首先从 sThreadLocal 中获取当前线程唯一的 Looper 实例 me,然后得到这个 Looper 实例的 MessageQueue 实例,接着就开始无限循环处理消息了。每当得到一个 Message 实例,只要不为空就调用 msg.target.dispatchMessage(msg) 开始分发消息。

总结

  • 创建一个 Message 的正确方式是:Message.obtain() 或 Handler.obtain()
  • 创建 Handler 时要注意避免内存泄漏
  • Looper 的 prepare() 方法,将 Looper 实例与当前线程绑定,通过 ThreadLocal 保证每个线程只有一个 Looper 实例,同时一个 Looper 实例也只有一个 MessageQueue 实例.
  • Looper 的 loop() 方法,不断从 MessageQueue 中取出 message 对象,并调用 message.target.dispatchMessage() 方法分发处理。

消息机制面试题

1. 我们平时都是使用 Handler 在子线程发送消息、主线程中接收与处理消息,那么 Handler 可以在主线程中发送消息,在子线程中接收与处理消息吗?如果可以怎么实现呢?

本文隐藏内容 登陆 后才可以浏览

2. 为什么主线程不会因为 Looper.loop() 里的死循环卡死

本文隐藏内容 登陆 后才可以浏览

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注