Android的Handler消息处理机制

这篇文章借鉴了任玉刚老师的《Android开发艺术探索》和Android6.0的源码。因为这只是我个人的一个学习记录,所以理解有错的地方希望大家可以指出来共同学习。(有个小建议就是希望大家可以在阅读源码的情况下学习这篇文章,互相比较会事半功倍

消息处理机制的本质:一个线程开启循环模式持续监听并依次处理其他线程给它发送的消息。

其中开启循环模式使用的是Looper结构;为了保证每个线程都有唯一的Looper,因此使用了ThreadLocal类存储Looper;读取的消息对象类是Message;线程一般需要处理多个消息,所以需要MessageQueue结构存储未处理的消息。

它们的类图关系如下:


Handler.png

首先从Handler的创建开始,创建一个Handler最后都会调用下面的两个构造函数之一,它们之间的区别在于是否指定Looper。(下面所有源码中的 ‘...’表示省略的部分,详细的大家可以去看源码。)

public Handler(Callback callback, boolean async) {
    ...
    mLooper = Looper.myLooper();  // 获取当前线程的Looper
    ...
    mQueue = mLooper.mQueue;  // 指定跟Looper绑定的消息队列
    mCallback = callback;  // 处理消息的回调接口,可为null
    mAsynchronous = async;
}

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;  // 设置指定的Looper
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

Looper的工作原理

之前Handler的构造函数中使用 Looper.myLooper() 方法获取默认的Looper,源码如下

public static @Nullable Looper myLooper() {
    // 从sThreadLocal变量中获取当前线程的Looper对象
    return sThreadLocal.get();  
}

知道Looper怎么获取了,那么又是什么时候存储的呢?首先看下Looper类的构造函数,为Looper指定了消息队列和线程对象。消息队列和线程是一一对应的关系,多个Handler共享一个消息队列,从而实现消息共享。

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

其实如果是你自己创建的线程要使用消息循环,那么需要做一些准备工作,否则会报异常,最简单的示例代码如下

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();  // 为当前线程创建一个Looper
        // 该handler没有指定Looper,所以默认持有此线程的Looper,也就是通过该handler发送的消息都会由此线程的Looper处理。
        Handler handler = new Handler();
        Looper.loop();  // 开启消息循环
    };
}).start();

然后看到 Looper.perpare() 方法,发现该方法做的事情就是新建一个当前线程的Looper并存储在sThreadLocal变量中,关于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));  // 
}

这时候你可能会发现,在主线程使用Handler的时候好像并没有开启消息循环呀,还不是一样能用Handler进行消息通信。这是因为主线程的消息循环在应用开始时就会调用 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);  // 存储当前线程的Looper变量
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        // 将当前线程的Looper设置为主线程的Looper ,方便后续调用
        sMainLooper = myLooper();         }
}

你也可以通过getMainLooper()方法在任意线程获取主线程的Looper。

准备工作完成后,就可以使用Looper.loop()方法开启消息循环,这是一个无限循环,唯一的退出条件是消息队列的next()方法返回为null,下面是关键代码

public static void loop() {
    final Looper me = myLooper();
    ...
    final MessageQueue queue = me.mQueue;
    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    for (;;) {
        // 在没有消息时,一般会在此处阻塞线程
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        // 调用Message对应Handler的方法进行消息分发
        msg.target.dispatchMessage(msg);
        ...
        // Message的回收,将该对象的相关信息置空后放入池中
        msg.recycleUnchecked();          
    }
}

MessageQueue的工作原理

说完了消息循环方式,接下来就是消息队列中消息的存储和取出实现了。

MessageQueue类是一个链表队列结构,消息的存储通过enqueueMessage()方法实现(插入链表头部),消息的取出通过next()方法实现(从链表尾部删除并返回)。下面只展示 next()方法的实现,其它细节代码大家可以自行查看源码。

nativePollOnce()是一个native函数,内部涉及epoll机制的处理(在没有消息处理时阻塞在管道的读端)。

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; // 第一次迭代
    int nextPollTimeoutMillis = 0;  // 等待时间
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 队列没有消息时阻塞,两种情况该方法会返回,一是等到有消息就返回;
        // 第二种时等待了nextPollTimeoutMillis时间之后,nativePollOnce方法也会返回。
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                do {  // 循环找到一条不是异步而且target(Handler)不为空的message
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // 下一条消息还没到时间,设置一个时间间隔,到时后唤醒
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 或取消息
                    mBlocked = false;
                    if (prevMsg != null) {  
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;  // 返回拿到的消息
                }
            } else {
                // 没有消息,对该值复位
                nextPollTimeoutMillis = -1;
            }
            ...
    }
}

Message的工作原理

Message的默认获取方法,一般通过obtain()方法获取Message对象

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {  // 消息池不为null时,取出一个消息对象返回
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();  // 否则创建新的消息
}

其它重载的obtain()方法的区别在于指定了某些参数,Message的重要参数如下

  • public int what;识别同一Handler的不同Message
  • public int arg1; public int arg2;传递整型数值时使用
  • public Object obj;传递对象数据时使用,该对象类需要实现Parcelable接口
  • Bundle data;传递Bundle类型数据时使用,通过setData()getData()方法进行修改和访问
  • Handler target;指定处理消息的Handler,通过setTarget()getTarget()方法进行修改和访问,也可以通过Handler.obtainMessage()方法默认指定。

Handler的工作原理

Looper的工作原理一节中有一个消息的分发函数,源码如下

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);  // callback是一个Runnable对象
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) return;
        }
        handleMessage(msg);  // 子类需要重写的消息处理逻辑实现
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}

ThreadLocal类的工作原理

刚刚在上面提到Looper变量的存储是通过ThreadLocal类执行的,而ThreadLocal类是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只有在指定线程内可以获取到存储的数据,对于其它线程来说则无法获取到数据。

这里我只简述下ThreadLocal的存储和获取原理,更详细的内部类Values的各种方法大家可以去查看源码。

public void set(T value) {
    Thread currentThread = Thread.currentThread();  // 获取当前线程对象
    Values values = values(currentThread);  // 获取指定线程的Values对象
    if (values == null) {
        values = initializeValues(currentThread);  // 不存在时进行初始化
    }
    values.put(this, value);  // 存储
}

public T get() {
    // Optimized for the fast path.
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values != null) {
        Object[] table = values.table;  // 存储的值就是存储在该对象数组中
        int index = hash & values.mask;
        if (this.reference == table[index]) {
            return (T) table[index + 1];  // 存储位置是在索引的下一位,原因可以去查看put方法
        }
    } else {
        values = initializeValues(currentThread);
    }
    return (T) values.getAfterMiss(this);
}

最终一般调用Handler子类重写的 handleMessage() 方法进行消息处理。

Handler中还有一个很重要的方法是 sendMessage(),最后调用的是 sendMessageAtTime() 方法,主要逻辑就是将消息入队到该Handler相关的Looper中的消息队列中。

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);  // 进行消息的入队
}

参考

《Android开发艺术探索》 - 任玉刚

你可能感兴趣的:(Android的Handler消息处理机制)