Handler机制详解

1. Handler简介

Handler机制,说的也是Android中的消息传递机制。也就是将工作线程(子线程)中创建的消息,传递到主线程,以进行UI更新操作的过程。简单的可理解为,子线程和主线程之间的通讯。采用这样的机制是为了防止并发更新UI所导致的线程安全问题,而不使用线程锁是为了提高系统的运行效率。

2.Handler简单使用

2.1 使用sendMessage()方法。

Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = 0x11;
                        message.obj = "异步创建字符串";
                        handler.sendMessage(message);
                    }
                });
                thread.start();

2.2 使用post方法

Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("异步创建字符串");
                    }
                });
            }
        });
        thread.start();

2.3 接收、处理消息

private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0x11:
                    String text = (String) msg.obj;
                    textView.setText(text);
                    break;
            }
        }
    };

3.Handler相关方法

3.1 sendMessage系列

sendMessage()方法主要是用于发送一条消息。一般用法是通过重写handler的handler消息是通过Message进行封装。如:

 Message message = new Message();
 message.what = 0x11;
 message.obj = "异步创建字符串";
 message.arg1 = 1;
 message.arg2 = 2;
 handler.sendMessage(message);
  • what字段一般用来表示事件类型,用int类型表示。
  • arg1和arg2字段可以传递两个int类型的值。
  • obj字段可以传递一个对象类型。

除了sendMessage方法可以调用外,还有如下方法:


image.png

sendEmptyMessage用户发送一条空消息,参数what用于表示一个消息类型。

  • 后缀为Delayed的方法,表示延迟发送,delayMillis参数表示延迟的毫秒数。
  • 后缀为AtTime的方法,表示指定时间发送,uptimeMillis参数指定发送的时间,也是毫秒级。
  • 其实sendMessage()、sendMessageDelayed()方法,最终调用的还是sendMessageAtTime()方法,源码中的调用为:
    public final boolean sendMessage(Message msg)    {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    public final boolean sendEmptyMessage(int what)   {
        return sendEmptyMessageDelayed(what, 0);
    }

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis)    {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

我们可以发现,sendEmptyMessage()方法,最终调用的是sendMessageDelayed()方法。是在sendEmptyMessageDelayed()方法中为我们创建了一个Message对象。

3.2 post系列

post方法主要是post一个Runnable对象,在Runnable的run方法中进行消息的处理,这种方法在使用方面不用自己去创建Message对象,但内部其实还是通过Message实现的。这种方法可以实现在子线程中方便的更新UI而不用去通过重写Handler的handleMessage()方法来更新UI。如:

 Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("异步创建字符串");
                    }
                });
            }
        });
thread.start();

同样,post也有延迟发送等方法:


image.png

4.消息机制

接下来通过源码来分析Handler的消息机制。

4.1 相关的类

  • Handler 用于产生和处理消息
  • Message 用于消息的封装
  • MessageQueue 消息队列,用户存储消息。这个MessageQueue不是一个真正的队列,他并没有实现java的Queue接口,他实际上只是封装了一个单向链表,向外界提供了enqueueMessage()、next()等方法。
  • Looper 轮询器,以轮询的方式从MessageQueue中查询消息,并将消息发送给Handler做处理。
  • ThreadLocal 用于在线程中存储Looper,可以说实现了线程和Looper的绑定。

4.2 流程

下面一张图理解Handler、Message和MessageQueue之间的关系。


image.png

大概的流程为:handler发送message到MessageQueue中。Looper轮询MessageQueue,将消息发送给Handler进行处理。

4.3 Handler的创建过程

4.3.1 主线程中创建Handler

handle的创建只需要通过new Handler()就可以创建一个Handler。在创建handler的时候需要指定Looper,主线程的Looper是在ActivityThread的main方法中创建的,通过Looper.prepareMainLooper()方法进行创建。

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

在prepareMainLooper方法中,sMainLooper通过myLooper()方法创建,而myLooper方法是通过调用ThreadLocal的get方法得到Looper对象。

    /**
     * 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();
    }

这里,先不具体讲ThreadLocal,简单的可理解为讲数据和线程做一个绑定,在当前线程中使用ThreadLocal.get()方法获取对象,则获取到的对象是当前线程所独立拥有的对象,和其他线程中的对象互不干扰。ThreadLocal中有一个Map,用来保存线程和数据的映射关系。

通过ThreadLocal.get()方法获取数据,首先要调用ThreadLocal.set()方法设置数据。所以刚才在myLooper方法中使用ThreadLocal.get()方法获取Looper对象,那是在什么时候设置进去的呢?答案就是prepareMainLooper方法中调用的prepare方法。

     /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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));
    }

我们再看prepare(boolean)方法。这里首先调用了ThreadLocal.get()方法检查当前线程是否已经创建了Looper对象,如果创建了,就抛出一个RuntimeException。然后调用ThreadLocal.set()方法设置了一个新的Looper,quitAllowed参数是表示是否可以退出消息队列。
由于Looper的构造方法是私有的,所以我们需要通过Looper.prepare()方法来创建Looper。在Looper的构造方法中,创建了一个MessageQueue,并且获取了当前线程信息。

此时主线程中的Looper已经创建完成。再看ActivityThread的main方法,在main方法的最后,调用了Looper.loop()方法,之后抛出一个RuntimeException。此时我们是不是有疑问,为啥要抛出一个RuntimeException?我们知道java程序的入口是main方法,当main方法执行结束,也表示应用程序已经执行结束。所以我们应该会想到,作为一个Android应用程序,怎么可以刚打开就结束了呢。所以说正常情况下只不会执行结束的。关键就在于Looper.loop()方法。我们查看Looper.loop()方法:

     /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop()   {
        //此处省略...........
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null)   {
                // No message indicates that the message queue is quitting.
                return;
            }
            //此处省略..........
        }
        //此处省略..........
    }

在loop方法中有一个死循环,这个死循环就防止了应用程序执行结束。我们看到通过queue.next()方法拿到一个Message,queue的定义在loop方法中,并且在for循环之前:

        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;

通过myLooper方法拿到当前线程的Looper对象me,并通过me.mQueue方法拿到了当前Looper对象中的MessageQueue,这个MessageQueue的创建是在前面讲到的Looper的构造方法中。通过MessageQueue.next()方法从MessageQueue中取得一个Message。

此时Handler、Looper、MessageQueue已经创建完成,并且Looper循环已经开启。只需要handler开始发送消息,并处理消息。

4.3.2 子线程中创建Handler

上面是从主线程的角度开始讲解Handler的创建过程,那子线程呢?
在子线程中使用Handler,首先需要new一个handler,此时handler中是没有Looper对象的。上面我们分析源码得出。Looper的构造方法是私有的,只能通过Looper.prepare()方法来创建当前线程的Handler,并且每个线程最多只能存在一个Looper对象,如果多次创建,会抛出RuntimeException。
所以我们在子线程中要使用Handler的时候,必须指定Looper。为什么说是指定而不是创建呢,因为我们可以通过Looper.getMainLooper()方法拿到主线程的Handler。当Looper指定完成以后,就可以通过Looper.loop方法开启循环。所以说,消息处于哪个线程的Looper中,则消息回调处理就在哪个线程中。

4.4 消息发送

4.4.1 使用Send方法发送Message

上面说到了sendMessage()方法最终都是调用了sendMessageAtTime()方法。我们看看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);
    }

在sendMessageAtTime()方法中,首先检查了当前Handler是否指定了MessageQueue。如果没有指定,则返回false,如果已经指定,则执行了enqueueMessage()方法,我们再看enqueueMessage()方法。

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

在这里我们就可以看明白,是在这个方法中将消息加入到MessageQueue中。并且在加入message之前,将msg的target字段赋值为this,这个this也就是当前的Handler对象了。mAsynchronous表示是否异步,这个参数是在handler创建的时候初始化的,默认为false。

4.4.2 使用Post方法发送Runnable

我们首先看Post方法中执行了什么操作。

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

我们发现post()方法直接调用的是sendMessageDelayed()方法。但是message的获取是通过getPostMessage()方法。我们看看getPostMessage()方法。

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

在getPostMessage()方法中通过Message.obtain()得到了一个Message对象,并且将runnable对象设置给message的callback,之后就返回了这个message对象。Message.obtain()方法简单理解是获取一个可复用的Message对象,降低了内存开销,提高效率。
所以说,我们使用post()方法最终还是通过Message,并且是将这个回调Runnable设置给Message对象中的callback属性。

4.5 消息的处理

我们回到Loop的loop()方法。

public static void loop() {
   //... 省略 
   for (;;) {
        //... 省略
          try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
        //... 省略
   }
  //... 省略
}

在loop()方法的for循环中,有一行代码。msg.target.dispatchMessage()。调用了message的target的dispatchMessage()方法。之前我们看过,message的target实际上是保存的当前Handler的实例,所以这里调用的是handler对象的dispatchMessage()方法进行消息的封发处理。我们查看dispatchMessage()方法。

    /**
     * Handle system messages here.
     */
    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,这里的callback就是使用post方法传入的Runnable参数。

  • 当callback不为null时,调用了handleCallback()方法
 private static void handleCallback(Message message) {
        message.callback.run();
    }

handleCallback()方法直接调用了message.callback.run(),也就是调用了传入的Runnable的run方法。

  • 当callback为null时,首先是判断了mCallback是否为null,不为null。则执行mCallback的handleMessage()方法,并且return。如果mCallback为null,则会调用handler的handleMessage方法。这个方法大家就很熟悉了,不就是我们重写的handler的handleMessage()用来处理消息的方法吗。
    我们可以查看mCallback的初始化,其实是在Handler的构造方法中初始化的,可以传入一个Callback,实现Callback的handleMessage()方法。所以我们使用callback的handleMessage方法去处理消息,和重写handler的handleMessaeg()方法,都是可以实现消息回调处理的,但是callback的优先级在前。
    从上面可以看出,我们可以重写dispatchMessage()方法进行消息的拦截处理。

写在最后

首先,谢谢大家的阅读。由于小编也是初次学习写技术文章,都是按照自己的理解来写的,可能会有错误的地方欢迎大家留言指出。文章中语言的组织也不是很好,希望大家理解。写这篇文章一是为了能让自己进一步理解Handler机制,并加强记忆。二是能和大家交流技术,共同进步。也希望能提起大家阅读源码的兴趣。最后,还是感谢大家阅读。

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

推荐阅读更多精彩内容