Android TipsAndroidAndroid开发

一张图带你深入了解Handler机制

2017-12-06  本文已影响21人  Micrason

前言


这两天正好闲着,遂回顾了一下以前觉得理解的很模糊的东西。Handler机制便是其中之一。
尽管我们经常用Handler进行线程之间的通信。但是对其中的过程,可能没太怎么去了解。即便有了解,可能也被复杂的逻辑给整蒙了。又是Looper又是MessageQueue的。我就是被整蒙圈的一员。但是今天,我将它重新梳理了一遍,在此记录一下。一方面是便于以后查看,另一方面加深自己的理解。

Handler机制思维导图


先上一张我画的思维导图,通过这张图,你就能清晰明了的了解HandlerLooperMessageMessageQueue它们之间的千丝袜缕了。

该图比较大,建议在电脑上观看效果会更好。https://github.com/KCrason/kcrason.github.io/blob/master/uploads/images/Handler%E6%9C%BA%E5%88%B6.png

Handler机制.png

Handler机制流程分析


流程概述

首先,我们知道,通常在Android Activity中使用Handler的流程是:创建Handler对象->在异步线程中使用Handler对象发送消息。然后就能在Handler的回调中收到消息。而事实上Handler机制的起始并不是从创建Handler对象开始,而是从Looper.preare()开始的。之后还要调用Looper.loop()整个过程才算真正完成。在Activity之所以看不到Looper相关的代码,是因为主线程已经做了。ActivityThread(主线程)的main方法源码

public static void main(String[] args) {  
        SamplingProfilerIntegration.start();  

        ...省略部分代码
  
        //初始化Looper
        Looper.prepareMainLooper();  
  
        ActivityThread thread = new ActivityThread();  
        thread.attach(false);  

        ...省略部分代码 
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  

        //开始循环取消息
        Looper.loop();  
  
        throw new RuntimeException("Main thread loop unexpectedly exited");  
    }  

而你在异步线程中使用Handler,它应该是下面这样的逻辑:

new Thread(new Runnable() {
                @Override
                public void run() {
                    //初始化Looper
                    Looper.prepare();
                    //创建Handler对象
                    Handler handler = new Handler(new Handler.Callback() {
                        @Override
                        public boolean handleMessage(Message msg) {
                            return false;
                        }
                    });
                    //开始循环取消息
                    Looper.loop();
                }
            }).start();

知道了使用规则,那么下面我们根据这个规则来分析它们之前的联系。

初始化Looper

调用Looper.preare()或Looper.prepareMainLooper方法开始,就是创建Looper对象的起点。那么在Looper.preare()中到底干了啥呢。我们看源码:

public static void prepare() {
     prepare(true);
}
//quitAllowed是否允许终止循环取消息。
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));
}

可以看到,很简单,就是 new了一个Looper。然后将Looper对象设置进sThreadLocalsThreadLocalLooper类的一个常量对象,其在在Looper类中的声明:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

我们接着看Looper的构造函数:

private Looper(boolean quitAllowed) {
   //创建一个MessageQueue赋值给mQueue 
   mQueue = new MessageQueue(quitAllowed);
   //获取当前线程赋值给mThread 
   mThread = Thread.currentThread();
}

至此,Looper的初始化工作完成。

神奇的ThreadLocal

之所以说ThreadLocal很神奇,是因为它确实很神(好像是废话~),它的神奇之处在于同一个ThreadLocal对象它可以在不同的线程间的赋值互不影响。什么意思呢?例子说明:

private void testTreadLocal() {
        //创建ThreadLocal对象
        final ThreadLocal<String> threadLocal = new ThreadLocal<>();

        Thread oneThread = new Thread(new Runnable() {
            @Override
            public void run() {
                //oneThread设置值OneThreadValue
                threadLocal.set("OneThreadValue");
                //打印oneThread下的结果
                Log.i("KCrason", "testTreadLocal: " + threadLocal.get());
            }
        });

        Thread twoThread = new Thread(new Runnable() {
            @Override
            public void run() {
                //twoThread设置值TwoThreadValue
                threadLocal.set("TwoThreadValue");
                //打印twoThread下结果
                Log.i("KCrason", "testTreadLocal: " + threadLocal.get());
            }
        });

        oneThread.start();
        twoThread.start();

        //主线程中设置值MainThreadValue
        threadLocal.set("MainThreadValue");

        //打印mainThread下结果
        Log.i("KCrason", "testTreadLocal: " + threadLocal.get());
    }

输出结果:

ThreadLocal测试结果
通过输出结果,我们知道,其实ThreadLocal就是对不同线程的赋值拷贝了一个副本进而保存了。到此,我相信对于ThreadLocal,你已经能够理解了,至于它具体是如何实现的,这里暂不分析,你也可以去查看源码进行深入了解。
Handler的创建

Looper一样,我们来照样从构造函数开始分析Handler的创建,看它到底干了啥?首先,Handler有多个构造函数,但它们无一例外的都会回到下面两个构造函数中来。

public Handler(Callback callback, boolean async) {
        ...省略部分代码
        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;
}
public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
}

事实上,通过比较,我们发现他们俩干的事其实都是一样的。那都干啥了呢,分为两种情况:

1、如果有传递Looper,那么直接将传递的Looper对象赋值给mLooper
2、如果没有传递Looper,则通过Looper.myLooper()获取Looper对象。然后赋值给mLooper

前面我们提到初始化Looper时会将创建的Looper对象保存在LoopersThreadLocal中,而Looper.myLooper()正是通过sThreadLocal去获取了在该线程下的Looper对象。

Looper中的myLooper方法源码
public static @Nullable Looper myLooper() {
   //通过sThreadLocal的get()返回创建的Looper对象。
   return sThreadLocal.get();
}

与此同时,通过获取到的mLooper 对象去获取在Looper中创建的MessageQueue对象并赋值给Handler的变量mQueue,最后将Callback对象进行赋值。Handler的初始化完成。

Looper.loop()开启循环获取消息

事实上,在初始化Looper之后,就可以直接调用Looper.loop()进行循环取消息进行处理。只不过那时MessageQueue中还没有消息,循环就处于block状态罢了。我们看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);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...省略部分代码
            msg.recycleUnchecked();
        }
    }

其流程简单来讲,就是通过一个死循环使用MessageQueuenext()方法不断的从MessageQueue中取出消息,如果为null,则block。否则,通过msg.target.dispatchMessage(msg)将消息分发。你可能会有所疑问,msg.target又是个什么鬼?没关系,我们接着往下分析。

使用Handler发送消息

首先,在使用Handler发送消息时,不管是哪种方式,最终都会调用sendMessageAtTime()方法,其中mHandler.post(Runnable runnable)也是先将runnable封装成一个Message最后调用sendMessageAtTime(),根据源码分析:

//将runable封装到Message中
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        //将Runnable对象r赋值给Message的callback变量。
        m.callback = r;
        return m;
}
//发送消息
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);
}

sendMessageAtTime()中,我们注意到,其最后调用了enqueueMessage(),下面我们看enqueueMessage()又干了什么。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //将Handler对象赋值给msg.target
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //将消息加入到MessageQueue(消息队列)
        return queue.enqueueMessage(msg, uptimeMillis);
}

enqueueMessage()中,我们知道了上面那个msg.target是个啥了,它其实就是当前的Handler对象。queue.enqueueMessage()即真正意义上的将Message加入到消息队列中。最后通过Handler对象的dispatchMessage()方法,最终将消息回调出来。最后我们再看看dispatchMessage()中干了啥:

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

显示判断msg.callback是否为null,而msg.callback实际上就是使用mHandler.post(Runnable r)发送的Runnable的对象。

1、如果msg.callback不为null,调用handleCallback,而handleCallback就是调用了msg.callback.run()

2、如果msg.callbacknull,再判断是否设置了Callback接口,设置了回调Callback接口中的handleMessage(),没有设置直接回调Handler的空方法handleMessage()

至此,整个流程结束。

一些疑问

尽管,通过上面的分析,我相信你对Handler的机制已经有了更深的理解。但是一直以来,有两个问题依然深深的困扰着我。

1、Handler究竟是如何进行不同线程切换的?

上面我们提到,通常而言,我们会使用Handler在异步线程中发送消息,之后都会调用enqueueMessage()方法将消息加入到消息队列中,而我们所使用的MessageQueue对象却是来源MainThread(主线程)Looper所创建的。也就是说,Handler发送消息时,MessageQueue的对象是运行在异步线程中的。而使用Looper.loop()开启循环后,MessageQueue的对象是运行在MainThread(主线程)的。这也是为什么在enqueueMessage()中,将消息加入到消息队列需要增加synchronized关键字,因为我们需要确保MessageQueue对象在同一时刻只能有一个线程访问。这一点,在MessageQueue#enqueueMessage()的源码得到验证:

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字段的作用。
        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;
            }

            ...具体的加入到队列的逻辑我们不分析,省略该部分代码
        }
        return true;
    }

通过上面的分析,我们可以得出结论:Handler的不同线程数据传输其实就是通过共享变量来实现的,而他们之间的介质便是Message

2、view.post(Runnable runable)mHandler.post(Runnable runnable)之间的关联是什么?为何在ActivityonCreate()中调用view.gitHeight()返回0,而使用view.post(Runnable runable)之后可以正确的返回View的高度?

关于这个问题,我将会用另一篇文章来单独分析~

Welcome to KCrason's blog

上一篇下一篇

猜你喜欢

热点阅读