Handler

ThreadLocal名为线程局部变量,它的功能主要是为线程提供一个变量值的副本,从线程的角度看就好像是线程完全拥有该变量。在其get与set方法中,数据的存储并不是ThreadLocal的成员变量,而是Thread类的成员变量ThreadLocalMap。所以当线程结束时,该数据同时也会被回收。

ThreadLocalMap以ThreadLocal对象为key,所以每个线程可以保存多个线程局部变量(set方法),由于每个Thread都会有自己独立的ThreadLocalMap实例,因此在并发环境下,对ThreadLocal的set或get,不会有任何问题。

在不同版本上ThreadLocal的数据保存形式可能不同,请注意之。(ThreadLocal.Values存储)

理解UI线程的Looper初始化:在Framwork层,ActivityThread类与Activity的启动紧密相关,ActivityThread是一个含有main方法的Java类,main方法部分代码如下:

public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        AsyncTask.init();
        Looper.loop();
        ...
}

Looper.prepareMainLooper中,初始化UI线程的Looper对象,所以我们在应用程序中可以直接实例化Handler对象,并向这个初始化的Looper对象中的消息队列发送消息。在上述的最后一句代码可以看出,程序进入死循环,即从MessageQueue中取消息进行处理(MessageQueue.next()),你可能会问,为什么这个时候主线程还能执行其他代码?其实这和AMS相关,属于跨进程通信,有机会将会进行讲解!

ThreadLocal解说完毕后,下面分析一下Handler、Looper、MessageQueue之间的关系。

在主线程中的Looper.prepare与Looper.loop方法在ActivityThread main方法处调用,所以在应用中可以直接实例化一个Handler,如果是在子线程中创建Handler则需要先调用Looper.prepare方法,否者在Handler构造方法的以下代码中将抛出异常:

mLooper = Looper.myLooper();
if (mLooper == null) {
        throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}

在上述的第一行代码中,会通过ThreadLocal对象去寻找和当前线程相关的Looper对象。在接下来的代码块中,将获取到的Looper对象中的MessageQueue对象赋值给Handler的mQueue变量,如下:

mQueue = mLooper.mQueue;

在使用Handler对象发送消息时最终会执行以下方法:

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

该方法的第一个参数就是Handler的mQueue变量值,即Handler对象发送消息是发送在该线程作用域下的Looper对象中的MessageQueue,在方法中将Message的target设置成自身,即发送该消息的Handler对象,然后调用MessageQueue的enqueueMessage方法,在enqueueMessage方法中,首先将msg赋值给mMessages,调用nativeWake(mPtr)方法,这是一个JNI方法,其内部会将mMessage消息添加到C环境中的消息队列,并且如果消息线程正处于挂起(wait)状态,则唤醒该线程。

以上是发送消息,由于在异步机制中,会有一个死循环(Looper.loop),如果此时消息不为空,那么将会执行取消息操作,查看MessageQueue的next方法。

源码太长,请直接查看MessageQueue中的next方法。

该方法内部流程分为三步:

  • 调用nativePollOnce本地方法,从消息队列中取出一个消息,由于MessageQueue并没有保存消息队列,真正的消息队列数据保存在JNI中的C代码中也就是说在C环境中创建了一个NativeMessageQueue数据对象,这就是nativePollOnce的第一个参数的意义,它是一个int型变量,在C环境中,该变量被强制转换为一个NativeMessageQueue对象,如果消息队列中没有消息,那么当前线程被挂起;如果消息队列中有消息,则C代码中将该消息赋值给Java环境中的mMessages变量。
  • 在接下来的synchronized(this)中,this是被用作取消息和发消息的锁,发送消息的同步在enqueueMessage函数中的synchronized(this)代码块。在取消息的同步块中仅仅是判断所指定的执行时间是否到了。如果到了就返回该消息;如果时间没到就尝试取出下一个消息。
  • 如果mMessages为空,则说明C环境中的消息队列没有可执行的消息了,因此执行mPendingIdleHandlers列表中空闲回调函数。可以在MessageQueue中添加一些空闲回调函数,当线程中没有消息可处理时可以去执行这些代码。

在上面三步中,假如存在消息,那么在Looper.loop方法中将会执行。

msg.target.dispatchMessage(msg);

由于msg.target是Handler对象本身,所以最终会进入一下方法:

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
}

如果msg没有设置callback,那么该方法最终会执行handleMessage方法,所以我们在使用Handler时,重写此方法就可以完成相应回调工作。在查看Small的代码时,发现其为Handler设置了mCallback值,并且在handleMessage中返回false,这是一个技巧,即插入了自己想要执行的代码,也保证了原来的逻辑得以继续执行。因此,可以利用Handler的mCallback回调做一些其他工作。

在理解Handler时最好站在面向对象的角度来进行理解,想象一下现实中的对应模型。

results matching ""

    No results matching ""