Handler 消息机制

Handler 消息机制是由 Message MessageQueue Handler Looper 共同完成的。

你还在为开发中频繁切换环境打包而烦恼吗?快来试试 Environment Switcher 吧!使用它可以在app运行时一键切换环境,而且还支持其他贴心小功能,有了它妈妈再也不用担心频繁环境切换了。https://github.com/CodeXiaoMai/EnvironmentSwitcher

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

public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

void recycleUnchecked() {
    flags = FLAG_IN_USE;
    // 将所有数据清空
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;
    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            // 如果当前消息池未满就把 Message 插入到消息池的头部
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

现在明白了消息池中的消息是怎么来的,再来看 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 允许我们发送和处理与当前线程关联的 MessageQueue 中的 Message 对象。每个 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 会发出如下警告:

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 类型的消息(最终还是转换为 Message 类型的消息)

sendMessage

public final boolean sendEmptyMessage(int what){
    return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

public final boolean sendMessageAtFrontOfQueue(Message msg) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
            this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, 0);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

对比上面几个方法后可以发现无论是以何种方式发送消息,最终都是调用 enqueueMessage() 方法将 Message 保存到当前 Handler 的 MessageQueue 中。

那么 post() 方法呢?

post()

public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean postAtTime(Runnable r, long uptimeMillis){
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

public final boolean postAtTime(Runnable r, Object token, long uptimeMillis){
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}

public final boolean postDelayed(Runnable r, long delayMillis){
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

public final boolean postAtFrontOfQueue(Runnable r){
    return sendMessageAtFrontOfQueue(getPostMessage(r));
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

private static Message getPostMessage(Runnable r, Object token){
    Message m = Message.obtain();
    m.obj = token;
    m.callback = r;
    return m;
}

我们也可以看到,只是将 runnable 作为 message.callback,其实还是先调用与之对应的 senMessageXXX()方法,最终调用 enqueueMessage() 方法。

dispatchMessage

消息发送后,会进入当前 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) {
            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.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();
    }
}

从注释中也可以看出,这个方法是创建 Application 的主 Looper,由Android系统调用(ActivityThread.main()和SystemServer.run()),我们不能调用这个方法。

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 的值只能被当前线程访问,那么当然就存在特殊情况了,我们可以使用 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 的目的就是通过它的线程局部变量这个特性,保证数据不能被其他线程访问和修改。

这里既然关系到数据的访问与修改,那么必然就联系到了 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() 方法分发处理。

如果上面的内容都理解了,就通过下面这个问题检测一下吧!

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

答案是可以的。因为我们通过 Handler 发送的 Message,都会保存到与它相关联的 Looper 的 MessageQueue 中,Looper 的 loop() 方法会不断循环取出 MessageQueue 中的 Message 并调用 message.target.dispatchMessage() 方法进行分发处理。

还记得 Handler 怎么与 Looper 关联,Looper 又是怎样与线程关联的吗?再来回顾一下。

Handler 除了可以与创建它的线程相关联的 Looper 相关联外,还可以与指定的 Looper 相关联,因此我们可以直接指定子线程的Looper 与 Handler 关联。但是,因为每个线程默认情况下没有与它相关联的 Looper,所以需要在子线程中先调用 Looper.prepare() 方法将 Looper 与子线程关联,创建完成后就可以调用Lopper.loop()方法开始循环处理消息了。

下面是详细代码:

public class SampleActivity extends Activity {

    private static class MyThread extends Thread {

        private WeakReference<SampleActivity> activityWeakReference;
        private MyHandler mHandler;

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

        @Override
        public void run() {
            super.run();
            
            Looper.prepare();

            SampleActivity sampleActivity = activityWeakReference.get();
            if (sampleActivity != null) {
                mHandler = new MyHandler(sampleActivity);
            }

            Looper.loop();
        }
    }

    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 void handleMessage(Message message) {
        Log.e("handlerMessage", "currentThread:" + Thread.currentThread().getName() + "message.what:" + message.what);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.e("onCreate", "currentThread:" + Thread.currentThread().getName());
        // 创建子线程
        final MyThread myThread = new MyThread(this);
        // 开启子线程接收消息
        myThread.start();

        // 创建一个发送消息的线程
        new Thread() {
            @Override
            public void run() {
                super.run();

                while (true) {
                    SystemClock.sleep(3000);
                    myThread.mHandler.sendEmptyMessage(new Random().nextInt(10));
                }
            }
        }.start();
    }
}

打印结果:

E/onCreate: currentThread:main
E/handleMessage: currentThread:Thread-9430,message.what:9
E/handleMessage: currentThread:Thread-9430,message.what:2
E/handleMessage: currentThread:Thread-9430,message.what:7
E/handleMessage: currentThread:Thread-9430,message.what:7
E/handleMessage: currentThread:Thread-9430,message.what:9
E/handleMessage: currentThread:Thread-9430,message.what:3
E/handleMessage: currentThread:Thread-9430,message.what:5
E/handleMessage: currentThread:Thread-9430,message.what:2
E/handleMessage: currentThread:Thread-9430,message.what:3
E/handleMessage: currentThread:Thread-9430,message.what:6

很明显可以看出消息是在子线程中接收与处理的。

延伸

再来一个问题,Android 中什么情况下会在子线程中发送消息和处理消息呢?

当然是耗时操作,而耗时操作又使用什么来实现呢? Service,其实 IntentService 就是利用 Handler 机制实现的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • 版权声明: 本文来自 书生依旧 的简书,转载请注明出处。原文链接: http://www.jianshu.com/...
    书生依旧阅读 1,899评论 1 7
  • 找工作面试中这个问题被问到的概率至少是80%,跟面试官扯一扯Handler消息的原理,会让面试加分很多的。最近我也...
    ldlywt阅读 662评论 0 3
  • 消息机制主要包含三个元素:Handler、MessageQueue、Looper 工作原理 Hander被创建后,...
    Jdqm阅读 301评论 0 0
  • 加班太晚,难得入眠; 上边瞧瞧,下边转转; 北方摆手,风声太紧; 东莞摇头,她不方便; 索性起床,继续苦干!
    孤独乞丐阅读 133评论 0 0
  • 2017-05-16 感谢猫叔、助理与剽悍一只猫内容组分享的剽悍晨读:好运都是上班路上设计出来的 一个好的开始 早...
    一粟于海阅读 358评论 0 1