解读Handler源码

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方法。

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

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

推荐阅读更多精彩内容