解读Handler源码

作者: cgzysan | 来源:发表于2016-10-29 19:00 被阅读0次

    Handler概述

    Handler是Android消息机制的上层接口,最常见的应用就是通过Handler在子线程更新UI。

    从创建Handler对象实例入手

    • 创建Handler对象
    private Handler mHandler = new Handler(new Handler.Callback() {
    @Override
        public boolean handleMessage(Message msg) {
              return false;
            }
    });    
    

    在这里覆写了 handleMessage()方法。

    • 点进去看Handler的构造函数,发现以上代码调用的一个参数的Handler构造方法,在内部调用了两个参数的构造方法。
    • mLooper = Looper.myLooper(); Handler构造函数的第一行有效代码,调用了Looper类的静态方法获取Looper实例。点进去的具体代码如下:
    public static @Nullable Looper myLooper() {
          return sThreadLocal.get(); 
    }
    
    > Return the Looper object associated with the current thread.  Returns null if the calling thread is not associated with a Looper.
    
    从注释可以看出,这个返回的Looper是每一个不同线程的Looper对象,也就是说不同的线程获得的Looper都是相互没有干扰的副本。
    那么是如何实现同一数据类型的不同线程副本,具体分析`sThreadLocal.get()`这行代码
    #### ThreadLocal ####
    * 首先sThreadLocal是一个ThreadLocal的对象,先来一小demo:
    
        public static void main(String[] args) {
            final ThreadLocal<Integer> test = new ThreadLocal<>();
            
            test.set(1);
            System.out.println(Thread.currentThread().getName() + "当前值 = " +test.get());
            
            new Thread(){
                public void run() {
                    test.set(2);
                    System.out.println(Thread.currentThread().getName() + "当前值 = " +test.get());
                };
            }.start();
            
            new Thread(){
                public void run() {
                    test.set(8);
                    System.out.println(Thread.currentThread().getName() + "当前值 = " +test.get());
                };
            }.start();
            
            new Thread(){
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "当前值 = " +test.get());
                };
            }.start();
        }
    
        打印结果:</br>
        main当前值 = 1</br>
        Thread-0当前值 = 2</br>
        Thread-1当前值 = 8</br>
        Thread-2当前值 = null</br>
    
        从代码和结果中可以看出,ThreadLocal类中有set/get方法,不同的线程获取的数据都与其自己设置的相同,同时如果没有调用set方法设置,返回的结果就是null,也就是说不同的线程拥有互不干扰的对象副本。
    

    ThreadLocal工作原理

        现在来看ThreadLocal的源码,分析它到底是如何实现的功能。先看set方法:
    
    public void set(T value) {
            Thread currentThread = Thread.currentThread();
            Values values = values(currentThread);
            if (values == null) {
                values = initializeValues(currentThread);
            }
            values.put(this, value);
        }
    
    • Values values = values(currentThread)返回了一个localValues,这里涉及到ThreadLocal中的一个静态类ThreadLocal.Values。
    • 接下来进行判断,如果localValues为null,则调用initializeValues方法进行初始化,否则会调用put方法将value存进去。
    void put(ThreadLocal<?> key, Object value) {
            cleanUp();
    
            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;
    
            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];
    
                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }
    
                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }
    
                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }
    
                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }
    

    可以看出,ThreadLocal的值就保存在LocalValues内部名为table的Object数组中,Threadlocal的reference字段后面紧跟着value,在这里也就是我们传进来的Looper。接下来就看看get方法:

     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];
                }
            } else {
                values = initializeValues(currentThread);
            }
    
            return (T) values.getAfterMiss(this);
        }
    

    get方法先根据当前线程获取其中的ThreadLocal.Values,从values中得到Object数组table,最后从table中找到存储的对象。

    • 接下来继续分析Handler的构造方法,获取Looper对象之后会判断,获取的Looper对象是否为null,如果为null,则抛出异常。
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    

    从异常的内容不难看出,在创建handler对象之前,必须要调用Looper.prepare()方法对Looper对象初始化。

    • mQueue = mLooper.mQueue;接着又从上一步获取的当前线程的Looper实例中获取了其保存的MessageQueue(消息队列),这样就保证了handler的实例与我们Looper实例中MessageQueue关联上了。
    • mCallback = callback;保存了创建Handler实例时覆写的callback回调方法。
    • mAsynchronous = async;保存了创建Handler实例时对异步的设置。
    • 以上就创建handler实例的代码,显然已经成功了。其实具体实现又要看looper内部的实现,以下来分析looper类。

    looper

    • 在以上创建Handler实例的时候,提到必须要先调用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));
    }
    

    在代码中,sThreadLocal是一个ThreadLocal对象,也就是对Looper对象进行了保存,同时进行了一次判断,判断sThreadLocal是否为null,否则抛出异常,这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程只有一个Looper实例。

    • 先看new Looper(quitAllowed)Looper的构造方法:
     private Looper(boolean quitAllowed) {
                mQueue = new MessageQueue(quitAllowed);
                mThread = Thread.currentThread();
    }
    
    1). 创建了一个消息队列`new MessageQueue(quitAllowed);`</br>
    2). 获取了当前线程实例。
    

    核心方法loop()

    • 现在让我们才看看Looper中的核心方法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;
    
        // 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;
            }
    
            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
    
            msg.target.dispatchMessage(msg);
    
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
    
            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
    
            msg.recycleUnchecked();
        }
    }
    

    在代码开头,调用Looper类的静态方法直接返回了sThreadLocal存储的Looper实例,如果me为null则抛出异常,也就是说looper()方法必须在prepare()之后方能运行。紧接着获取到了Looper实例中的mQueue(消息队列),之后就进入了无限循环。从消息队列中取出一条消息,如果没有消息就阻塞。
    关键代码->msg.target.dispatchMessage(msg);把消息交给了msg的target的dispatchMessage()方法去处理。点开target,可以发现target其实就是handler对象,最后释放消息占据的资源msg.recycleUnchecked();。</br>

    加入消息队列

    好了说了这么多,那么消息是怎么加入到消息队列MessageQueue中的呢

    • 那么现在具体看看平时使用的sendMessage(Message msg)方法具体是如何实现的。
    public final boolean sendMessage(Message msg)
    {
           return sendMessageDelayed(msg, 0);
    }
    
    • 其中的sendMessageDelayed方法
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    • 最后还是调用了sendMessageAtTime方法
    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);
    }
    

    enqueueMessage方法中

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

    在进入方法的第一行,enqueueMessage中首先为meg.target赋值为this,也就是把当前的handler作为msg的target属性。最终会调用queue的enqueueMessage的方法,同时可以看出最终调用了queue.enqueueMessage方法,那么queue又是什么呢,其实就是MessageQueue的对象实例,也就是说handler发出的消息,最终会保存到消息队列中去。

    • 接下来再来看dispatchMessage方法:
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    

    就会发现其中的handleMessage(msg);方法中是空的,其实就是需要我们覆写的最终消息处理方法。

    在这里还有一个问题,在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,结果Handler还是成功的创建了呢,其实这个因为在Activity的启动代码中,已经在当前的UI线程调用了Looper.prepare()和Looper.loop()方法。

    最后对handler机制做一个总结:</br>

    • 首先Looper.prepare()在本线程保存一个Looper实例,然后在该实例中创建并保存了一个MessageQueue对象,同时因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程只会存在一个。
    • Looper.loop()方法会让当前线程进入一个无限循环,不断的从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。
    • 而target就是Handler的实例,那么再看Handler的构造函数,其首先得到了当前线程保存的Looper实例,进而又从Looper实例中获取了保存的MessageQueue实例,与其相关联。
    • 接下来Handler的sendMessage方法,会给msg的target赋值为handler本身,然后加入到MessageQueue中。
    • 那么回过头来看Looper.loop()获取到了消息调用的dispatchMessage(msg)方法其实最终调用了我们覆写的handleMessage方法。

    学习的时间不长,有什么不对还请指出,谢谢指教。

    相关文章

      网友评论

        本文标题:解读Handler源码

        本文链接:https://www.haomeiwen.com/subject/xmeyuttx.html