终于明白了Handler的运行机制

前言

Handler是一个Android SDK 提供给开发者方便进行异步消息处理的类。

我们都知道在UI线程中不能进行耗时操作,例如数据读写、网络请求。Android 4.0开始,在主线程中进行网络请求甚至会抛出Android.os.NetworkOnMainThreadException。这个时候,我们就会开始依赖Handler。我们在子线程进行耗时操作后,将请求结果通过Handler的sendMessge**() 方法发送出去,在主线程中通过Handler的handleMessage 方法处理请求结果,进行UI的更新。

后来随着AsyncTask、EventBus、Volley以及Retrofit 的出现,Handler的作用似乎被弱化,逐渐被大家遗忘。其实不然,AsyncTask其实是基于Handler进行了非常巧妙的封装,Handler的使用依然是其核心。Volley同样也是使用到了Handler。因此,我们有必要了解一下Handler的实现机制。

神奇的Handler

记得很久之前的一天,我在阅读别人的代码时,看到了这样一段:

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(mContext, "I'm new Handler !", Toast.LENGTH_SHORT).show();
            }
        }, 1000);

第一印象就是,这不是在子线程中进行UI操作吗?这代码有问题吧,于是乎立刻在自己电脑上写了个demo试了一下,结果发现真的没有问题。在一阵懵逼过后,我又写出下面的代码,测试一下子线程中到底能不能进行UI操作。

        new Thread(new Runnable() {
            @Override
            public void run() {
                
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();

            }
        }).start();

结果很明显,程序一启动立刻就奔溃了。并抛出异常java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
于是乎我又在try block 之前添加了Looper.prepare()这行代码。再次运行程序虽然没有奔溃,但也没有任何反应,Toast也没显示。

那么Handler到底是什么呢?他怎么就这么神奇。

实现机制解析

首先,我们从整体上了解一下,在整个Handler机制中所有使用到的类,主要包括Message,MessageQueue,Looper以及Handler。

Handler

好了,为了方便后面的叙述,我们就首先了解一下这个类图中使用到几个类,及其关键方法。

Message

首先看一下Message这个类的定义(截取部分)

 public final class Message implements Parcelable {

    public int what;
    public int arg1; 
    public int arg2;
    public Object obj;
    /*package*/ Handler target;
    /*package*/ Runnable callback;
    
    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
    
    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message() {
    }
}

看到这个类的前四个属性,大家应该很熟悉,就是我们使用Handler时经常用到的那几个属性。用来在传递我们特定的信息。其次我们还可以总结出以下信息:

  • Message 实现了Parcelable 接口,也就是说实现了序列化,这就说明Message可以在不同进程之间传递。
  • 包含一个名为target的Handler 对象
  • 包含一个名为callback的Runnable 对象
  • 使用obtain 方法可以从消息池中获取Message的实例,也是推荐大家使用的方法,而不是直接调用构造方法。

MessageQueue

MessageQueue顾名思义,就是上面所说的Message所组成的queue。

首先看一下构造方法:

MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

接收一个参数,决定当前队列是否允许被终止。同时调用 一个native方法,初始化了一个long类型的变量mPtr。

同时,在这个类当中,还定义了一个next 方法,用于返回一个Message 。

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
    ……
    }

由于这个方法中有一些native调用,未能完全理解,只知道会返回一个Message对象。

这个next方法相当于是队列出栈,有出栈必然有进栈,enqueueMessage 方法就是完成这个操作;这个我们后面再说。

Looper

上面说到了MessageQueue,那么这个Queue又是由谁创建的呢?其实就是Looper。关于Looper有两个关键方法:

prepare()loop()

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));
    }

可以看到,对于每一个线程只能有一个Looper。也就是说执行prepare方法时,必然执行最后一行代码
sThreadLocal.set(new Looper(quitAllowed));

我们再看Looper(quitAllowed)方法:

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

这样,MessageQueue 就被创建了。这里也可以看到,默认情况下,一个MessageQueue的quiteAllow=true。

这里使用到的sThreadLocal 是一个ThreadLocal对象。简单来说,使用它可以用来解决多线程程序的并发问题。使用set方法,将此线程局部变量的当前线程副本中的值设置为指定值;使用get方法,返回此线程局部变量的当前线程副本中的值。

Looper-loop()

再看一下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;
            }
            msg.target.dispatchMessage(msg);
            msg.recycleUnchecked();
        }
    }

首先看第一句代码执行的方法:

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

很明显,这样返回的Looper就是刚才prepare时set进去的那个,因为都是在同一线程。再明确一下,一个线程对应一个Looper。

这样就确保我们可以在不同的线程中创建各自的Handler,进行各自的通信而不会互相干扰

回到代码,后面逻辑就很简单了,在一个死循环中,通过队列出栈的形式,不断从MessageQueue 中取出新的Message,然后执行msg.target.dispatchMessage(msg) 方法,还记的前面Message类的定义吗,这个target属性其实就是一个Handler 对象,因此在这里就会不断去执行Handler 的dispatchMessage 方法。如果取出的Message对象为null,就会跳出死循环,一次Handler的工作整个就结束了。

Handler

上面说了这么多终于轮到Handler,那么就看看在Handler中到底发生了什么。回到我们一开始的代码。

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                String currentName=Thread.currentThread().getName();
                Toast.makeText(mContext, "I'm new Thread "+currentName, Toast.LENGTH_SHORT).show();
            }
        }, 4000);

这里我们用Toast弹出了当前线程的name,结果发现这个线程的名字居然是main,这也是必然结果

让我们一步一步看看,神奇的Handler到底是怎样工作的。就从这个代码开始解读。首先看一下Handler的构造方法。

    public Handler() {
        this(null, false);
    }

    ---------------

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        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;
    }

这里做的事情很简单,就是完成了一些初始化的工作,调用Looper.myLooper()赋值给当前mLooper,关联MessageQueue;这里由于代码中调用的是不带任何参数的构造函数,因此会创建一个mCallback=null且非异步执行的Handler 。

接下看postDelayed 方法。

public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

这里通过getPostMessage(Runnable r) 方法,把我们在Activity里写的Runnable 这个线程赋给了Message 的callback这个属性。

平时大家使用Handler也发现了,他为我们提供了很多方法

handler

因此,上面的postDelayed经过了各种辗转反侧,最终来到了这里:

    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);
    }

经过之前的构造方法,mQueue显然不为null,继续往下看

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

注意,注意,注意 这里进行了一次赋值:

msg.target = this;

前面提到,这个target就是一个Handler对象,因此这里Message就和当前Handler关联起来了。enqueueMessage,哈哈,这就是我们之前在MessageQueue中提到的进栈操作的方法,我们看一下:

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 (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;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

这个方法就是典型的队列入队操作,只不过会根据Message这个对象特有的一些属性,以及当前的状态是否inUse,是否已经被quit等进行一些额外的判断。

这样,我们就完成消息入队的操作。还记得我们在Looper中说过,在loop方法中,会从MessageQueue中取出Message 并执行他的dispatchMessage 方法。

**dispatchMessage **

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

到这里,就很明确了,在之前的postDelayed 方法中,已经通过getPostMessage,实现了 m.callback = r;这样这里就会执行第一个if语句:

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

这样,就会执行我们在Activity 的Runnable 中的run 方法了,也就是显示Toast。

到了这里,我们终于明白了,使用Handler 的postDelay 方法时,其Runnable中的run方法并不是在子线程中执行,而是把这个Runnable赋值给了一个Message对象的callback属性,而这个Message会被传递到创建Handler所在的线程,也就是这里的主线程,所以这个Toast的显示依旧是在主线程中。这也和postDelay API 中所声明的内容是一致的。

/**
* Causes the Runnable r to be added to the message queue, to be run
* after the specified amount of time elapses.
* The runnable will be run on the thread to which this handler
* is attached.
*/

到这里,一开始所说的第一个代码块所执行的逻辑已经理清楚了,但是还是有一点疑问,我们并没有在Handler的构造方法中看到Looper 的prepare()方法和loop() 方法被执行,那么他们到底是在哪里执行的呢?这个问题我也是疑惑了很久,最终才明白是在
ActivityThread的main方法中执行。简单来说,ActivityThread是Java层面一个Android程序真正的入口。关于ActivityThread更多的内容可以看看这篇文章

ActivityThread-main方法(截取主要部分)

public static void main(String[] args) {
      
        
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

这个类藏的比较深,你可以在Android-SDK\sources\android-24\android\app 这个目录中找到。

也就是说,在一个Android 程序启动之初,系统会帮我们为这个主线程创建好Looper。只不过这个方法名字比较特殊,叫做prepareMainLooper。

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

注意这里调用prepare时传递的参数值为false,和我们之前创建普通Looper时是不同的,这也 可以理解,因为这是主线程,怎么可以被允许被外部代码终止呢。

到这里,我们终于完整的理解了开头我们提到的第一个代码块的内容了。

至于第二种使用写法出错的原因也在明显不过了,主线程会在程序启动时在main方法中帮我们主动创建Looper,调用loop方法;而我们自己创建的线程就得我们主动去调用Looper.prepare(),这样才能保证MessageQueue被创建,程序不会奔溃;但是我们所期望的Toast依然没有显示出来,这是为什么呢?因为,我们没有调用loop方法。消息被加入队列了,但是没有办法弹出。因此我们将代码修改如下:

        new Thread(new Runnable() {
            @Override
            public void run() {

                Looper.prepare();

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();

                Looper.loop();

            }
        }).start();

这样就没问题了,Toast就可以显示出来了。实际上,平时写代码肯定不会这么写,这里只是为了说明问题。

handleMessage

回想一下,我们使用Handler最常见的场景:

handler = new MyHandler();

private class MyCallback implements Callback {

        @Override
        public void onFailure(Call call, IOException e) {
            Message msg = new Message();
            msg.what = 100;
            msg.obj = e;
            handler.sendMessage(msg);
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            Message msg = new Message();
            msg.what = 200;
            msg.obj = response.body().string();
            handler.sendMessage(msg);
        }
    }

上面的代码是OKHttp的回调方法,由于其回调方法不处于UI 线程,因此需要我们通过Handler将结果发送到主线中取执行。
那么这又是怎样实现的呢?

前面我们截图说过,Handler为我们提供许多sendMessage 相关的方法,因此这里我们在onResponse 中执行的sendMessage 经过层层传递,殊途同归依然会回到MessageQueue的enqueueMessage方法,也就是说所有的sendMessageXXX方法完成的工作无非就是队列入栈的工作,就是将包含特定信息的Message加入到MessageQueue中。而我们也知道,通过loop方法,会从MessageQueue中取出Message,执行每一个Message 所对应Handler的dispatchMessage方法,我们再看一次这个方法:



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

这一次,我们创建的Message很简单,其callback属性必然是空的,而且在实例化Handler时,调用的是其无参构造函数 ,因此这个时候,就会执行最后一行代码handleMessage(msg) ;

/**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

空的 ! 没错,这个方法就是空的,因为这是需要我们在Handler的继承类中自己实现的方法呀。比如下面这样;

class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            loading.setVisibility(View.GONE);
            switch (msg.what) {
                case 100:
                    Object e = msg.obj;
                    Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();
                    break;
                case 200:
                    String response = (String) msg.obj;
                    tv.setText(response);
                    break;
                default:
                    break;
            }
        }
    }

我们在handleMessage方法中,实现了自己的处理逻辑。

总结

好了,这就是Handler的实现机制,这里再做一次总结称述。

  • 通过Looper的prepare方法创建MessageQueue
  • 通过loop方法找到和当前线程匹配的Looper对象me
  • 从me中取出消息队列对象mQueue
  • 在一个死循环中,从mQueue中取出Message对象
  • 调用每个Message对象的msg.target.dispatchMesssge方法
  • 也就是Handler的dispatchMessage 方法
  • 在dispatchMessage 根据Message对象的特点执行特定的方法。

至此,终于弄明白了Handler的运行机制。

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

推荐阅读更多精彩内容