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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

友情链接更多精彩内容