Android 消息机制 Handler 详解

Handler

一些Handler子类

Handler 允许你发送和处理,与线程的 MessageQueue 关联的 Message 和 Runnable 对象。每个 Handler 实例都与单个线程和该线程的消息队列相关联。当你创建一个新的 Handler 的时候,它绑定到正在创建它的线程的消息队列。从这时开始,Handler 可以把消息或可执行的任务传递给消息队列,并当消息被读取的时候执行它们。

  • There are two main uses for a Handler
    Handler的两个主要用处:
    • to schedule messages and runnables to be executed as some point in the future;
      在将来的某个时候安排消息和子线程任务的执行。
    • to enqueue an action to be performed on a different thread than your own.
      添加在不同线程执行的事件。

Scheduling messages is accomplished with the

  • post(Runnable),
  • postAtTime(Runnable, long),
  • postDelayed(Runnable, long),
  • sendEmptyMessage(int),
  • sendMessage(Message),
  • sendMessageAtTime(Message, long),
  • sendMessageDelayed(Message, long) methods.

post 允许添加 Runnable 对象入队列,以便在收到消息队列时被消息队列调用;sendMessage 允许添加一个Message对象入队列,Message 对象包含一系列数据,它将由 Handler 的 handleMessage(Message) 方法处理。

当 post 或 sendMessage 到 handler 时,可以允许在消息队列准备就绪后立即处理该项目,或者在处理消息之前指定延迟或规定执行的时间。后两者允许你实现 timeout,ticks 和其他 timing-based (基于时间)的行为。

为应用程序创建一个进程时,其主线程将专门运行一个消息队列,负责管理顶层应用程序对象(活动,广播接收器等)以及它们创建的任何窗口。通过在子线程中调用 post 或 sendMessage 方法,并通过 Handler 与主线程进行通信。

Looper

Looper 是用于为线程运行消息循环的类。 线程默认情况下没有与它们相关的消息循环,因此必须创建一个,在要运行循环的线程中调用 prepare(),然后 loop() 使其处理消息,直到循环停止。 与消息循环的大多数交互是通过 Handler 类。

示例

这是执行 Looper 线程的一个典型例子,使用 prepare() 和 loop() 的分离来创建初始 Handler 来与 Looper 进行通信。

class LooperThread extends Thread {
      public Handler mHandler;

      @Override
      public void run() {
          Looper.prepare();

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }

MessageQueue

MessageQueue 类是持有由 Looper 发送的消息列表的低级别类。 消息不直接添加到 MessageQueue,而是通过与 Looper 关联的 Handler 对象添加。 可以使用 Looper.myQueue() 恢复到当前线程的 MessageQueue。

消息机制

Android 应用启动时,会默认有一个主线程(UI 线程),在这个线程中会关联一个消息队列,所有的操作都会被封装成消息然后交给主线程来处理。获取消息的操作放在一个死循环中,保证主线程不会主动退出。这样程序相当于一直执行死循环,因此不会退出。

UI 线程的消息循环是在 ActivityThread.main 方法中创建的:

    public static void main(String[] args) {
        ......
        Process.setArgV0("<pre-initialized>");

        //1.创建消息循环Looper
        Looper.prepareMainLooper();

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

        if (sMainThreadHandler == null) {
            //UI线程的Handler
            sMainThreadHandler = thread.getHandler();
        }
        ......
        //2.执行消息循环
        Looper.loop();

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

在子线程中完成耗时操作后,很多时候要更新 UI,常用的方式就是通过 Handler 将一个消息 Post 到 UI 线程中,然后在 Handler 中的 handleMessage() 方法中进行处理。有一点要注意,该 Handler 必须在主线程中创建。

一个简单的 Handler 示例:

public class MyHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
         //更新UI
    }
    
    MyHandler mMyHandler = new MyHandler();
    
    //开启新线程
     new Thread(){
        @Override
        public void run() {
            //耗时操作
            mMyHandler.handleMessage(123);
        }
    }.start();
}

一、Handler 如何关联消息队列以及线程呢?

每个 Handler 都会关联一个消息队列,消息队列被封装在 Looper 中,而每个 Looper 又会关联一个线程(Looper 通过 ThreadLocal 封装),于是每个消息队列关联一个线程。

    public Handler() {
        this(null, false);
    }
    
    public Handler(Callback callback, boolean async) {
       ......
        mLooper = Looper.myLooper(); //获取Looper
        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;
    }

从以上 Handler 的构造函数可以看出,Handler 在内部通过Looper.myLooper() 来获取 Looper 对象,并且与之关联,最重要还获取了消息队列。

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

myLooper() 方法中是通过 sThreadLocal.get() 来获取 Looper 对象。

那么 Looper 对象是什么时候存储在 sThreadLocal 中呢?

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

    public static void prepare() {
        prepare(true);
    }
    
    //为当前线程设置一个 Looper,存储在 sThreadLocal 中
    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));
    }

答案就是在 prepareMainLooper() 方法中,这个方法又调用了 prepare() 方法,在 prepare() 中创建了一个 Looper 对象,并设置给 sThreadLocal。这样 Looper(消息队列)和线程就关联上了。所以,不同的线程不能访问对方的消息队列。

消息队列通过 Looper 与线程关联上了,Handler 与 Looper 关联上,因此三者就彼此关联了。就是因为 Handler 要与主线程的消息队列关联上,这样 handleMessage 才会在 UI 线程中执行,此时更新 UI 才是线程安全的。

二、创建 Looper 后,如何进行消息循环?

消息循环的建立就是通过 Looper.loop() 方法。

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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; //1.获取消息队列

      ......
        for (;;) {  //2.死循环,即消息循环
            Message msg = queue.next(); // 3.获取消息 (might block) 
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

          ......
            try {
                msg.target.dispatchMessage(msg);  //4.处理消息
             ......
            msg.recycleUnchecked();  //5.回收消息
        }
    }

通过 Looper.prepare() 来创建 Looper 对象,消息队列封装在 Looper 对象中,Looper 对象保存在 sThreadLocal 中,通过 Looper.loop() 来执行消息循环,这两步通常成对出现。

三、消息如何被处理?

可以看到,Looper.loop() 中是通过第4步
msg.target.dispatchMessage(msg) 来处理消息的。

下面来看看 Message 的源码。

public final class Message implements Parcelable {
 
    ......

    /*package*/ Handler target;   //target处理

    /*package*/ Runnable callback;   //Runnable类型的callback

    // sometimes we store linked lists of these things
    /*package*/ Message next;   //下一条消息,消息队列是链式存储的

    ......

可以看到,target 是 Handler 类型,实际就是转了一圈,Handler 将消息投递给消息队列,消息队列又把消息分发给 Handler 来处理。

下面是 Handler 实际处理消息的源码

/**
     * Subclasses must implement this to receive messages.
     * 消息处理函数,子类必须覆写
     */
    public void handleMessage(Message msg) {
    }
    
    /**
     * 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);
        }
    }

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

可以看到,dispatchMessage 只是一个分发方法,如果 Runnable 类型的 callback 为空,则执行 handleMessage() 方法来处理消息,通常我们需要覆写该方法,将更新 UI 的代码写在里面。如果 callback 不为空,则执行 handleCallback() 方法,该方法会调用 callback 的 run 方法。

其实就是 Handler 分发的两种类型,一般我们 post(Runnable callback) 则 callback 就不为空,当使用 sendMessage 时一般不会设置 callback,因此就执行 handleMessage 这个分支。

下面来看下 post() 方法的具体实现

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

    //将Runnable对象包装成Message对象
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

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

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

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;    //设置消息的target为当前的Handler对象
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);  //将消息插入到消息队列
    }

在 post(Runnable r) 时,会将 Runnable 对象包装成 Message 对象,并将 Runnable 对象设置给 Message 对象的 callback 字段,最后将该 Message 对象插入消息队列。

sendMessage 方法的实现也是类似。

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

无论是 post 一个 Runnable 还是 sendMessage,都会调用 sendMessageDelayed(msg, time) 方法。

Handler 不断将消息追加到 MessageQueue 中,而 Looper 不断从 MessageQueue 中读取消息,并调用 Handler 的 dispatchMessage 消息,这样 Android 应用就运转起来。

四、在子线程中创建 Handler 为何会抛出异常?

先来看看下面这个代码

        new Thread() {
            Handler mHandler = null;

            @Override
            public void run() {
                mHandler = new Handler();
            }
        }.start();

这个代码是有问题的,会抛出 "Can't create handler inside thread that has not called Looper.prepare()" 异常,原因就出在 Handler 构造函数中。

    public Handler() {
        this(null, false);
    }
    
    public Handler(Callback callback, boolean async) {
       ......
        mLooper = Looper.myLooper(); //获取Looper
        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 对象还没有创建,因此,sThreadLocal.get() 会返回 null。没有 Looper 就没有 MessageQueue,而 Handler 的原理就是要与 MessageQueue 建立关联。因此,创建 Handler 时 Looper 一定不能为空。

解决方案如下:
        new Thread() {
            Handler mHandler = null;

            @Override
            public void run() {
                //1.为该线程创建Looper,并且会绑定到ThreadLocal中
                Looper.prepare();
                mHandler = new Handler();
                //2.启动消息循环
                Looper.loop();
            }
        }.start();

顺带一提,一个 Looper 可以被多个 Handler 共同使用,在 Handler 的构造方法里可以指定 Looper。

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

推荐阅读更多精彩内容