Android:关于 Handler 消息传递机制

本文总结了Android消息机制,详细解释了Handler、Looper和MessageQueue的作用及交互方式。并通过三种常见用法对比,帮助理解如何在多线程环境中正确使用Handler。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面

这一篇主要是对Android 的消息机制做一个总结。

在消息传递机制里,Android提供了 Handler 来作为对线程里的消息队列里信息进行传递和处理的方法,所以并不是说 Handler 就是用来更新主线程里的 UI,只是我们大多时候是用来在这么做。

而且在 Handler 的介绍里,并没有说作用就是用来更新主线程 UI:

A Handler allows you to send and process {@link Message} and Runnable objects associated with a thread’s {@link MessageQueue}.

它主要的工作就是分发Message 和 Runnable (实际还是封装成 Message) 到消息队列里,然后当它们从消息队列里出来的时候执行它们。

一说到这个就离不开三个关键词:Handler,MessageQueue 和 Looper。关于这三者的内容和关系不少,但我们先从用法开始。


用法

常见的用法有几种:

第一种方法

Handler handler = new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {

                    tv.setText(msg.what + "");
                    return false;
                }
            });

private Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            Message message = new Message();
            message.what = 1;           
            handler.sendMessage(message);
        }
    });   

第二种方法

    private Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
           Handler handler = new Handler(Looper.getMainLooper() , 
           new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {

                    tv.setText(msg.what + "");
                    return false;
                }
            });

            Message message = new Message();
            message.what = 1;
            handler.sendMessage(message);
        }
    });

第三种方法


 private Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            Handler handler = new Handler( 
            new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {

                    //做操作
                    return false;
                }
            });

            Message message = new Message();
            message.what = 1;
            handler.sendMessage(message);
            
            Looper.loop();
        }
    });

用法说明

为什么第一种方法和第二种方法是一样的?

第一种和第二种方法是一样的,主要的区别是在于 Handler 初始化时的位置不同而导致的不同。

因为 Handler 的构造方法里会需要指定一个 Looper,默认是当前线程里的 Looper:

public Handler(Callback callback, boolean async) {
      //省略其它代码
      //关注这里
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
      //省略其它代码
    }

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

在第一种方法里,像有时我们在 Activity 里,之所以不用指定 Looper,是因为我们这样写就表示是默认使用主线程ActivityThread的 Looper (在这里被称为 MainLooper)了,这个可以在 ActivityThread 的源码里看到:

//ActivityThread.java

 public static void main(String[] args) {
        //省略其它代码...

        Looper.prepareMainLooper();

        //省略其它代码...
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

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

而在第二种方法里,你不写Looper.getMainLooper()的话,就默认是当前子线程,那么运行起来你其实是更新不了 UI 的。

第三种是标准写法

第三种是最原本的标准用法,要在用法上完整的使用 Handler,那么就需要

  1. 有一个线程
  2. 准备Looper.prepare()
  3. 初始化 Handler,(顺便把对消息回调的方法也实现了)
  4. Looper.loop();
  5. Handler 发送消息

第3点就是前面说的,Handler 是用来针对线程里的消息队列的,在初始化的时候需要有 Looper 参数,而这个 Looper 参数初始化的时候是获取当前线程,也就是说要想知道 Handler 是针对哪个线程的,就看 Looper 是哪里的。


消息传递机制

Handler 会投递(send or post方法 )信息(Messasge or Runnable)到消息队列(MessageQueue)里,Looper 会从消息队列里取出消息,然后再将消息发给 Handler的回调(Callback)或是执行 Runnable 的 run 方法。


Handler

关于 post 和 send

这里有个地方我们要注意,就是 Handler 的 send 和 post 方法,通过源码的分析,我们可以知道,post 一个 Runnable 和 send一个 Message最后调用的方法都是一样的,即sendMessageAtTime(Message , long)

当使用 post 方法时,它会把 Runnable 封装进 Message 里,因为 Message 里有个 Callback 的成员变量,就是 Runnable 类型,所以最后都可以当做 Message 来看待。

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

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

那么 send 一个 Message 的时候,我们知道有这样的回调Handler.Callback:

 Handler handler = new Handler( 
            new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {

                    //做操作
                    return false;
                }
            });

而 post 一个 Runnable 的时候,就是执行 Runnable里 的 run 方法了,所以它是先 post进队列,然后等队列里出来后,才 run,相当于上面的回调。

//这个相当于在子线程里进行个计时器的操作
handler.postDelayed(new Runnable() {
                @Override
                public void run() {

                }
            } , 100);

从最后Handler 对消息的分发方法dispatchMessage(Message)也可以看出来。

 public void dispatchMessage(Message msg) {
        //使用 post 方法时,就执行 Runnable 里的 run
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //使用 send 方法,就回调
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

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

另外由于 post 和 send 两个方法的返回值是 boolean 类型,所以也可以让我们来判断是否成功加入队列。

Looper

Looper 就是对消息队列进行循环,MessageQueue 是它的一个成员变量,所以它可以在自己的loop()方法里,对 MessageQueue 进行遍历操作。

loop()方法是开启循环的操作,然后我们可以调用quit()或是quitSafety()方法来退出Looper。这两个方法最后都是调用了MessageQueue 的quit(boolean safe)方法。

区别在于quit()是直接清空消息队列,而quitSafety()是只清空那些延时的信息(即 postDelay),然后分发那些即时的消息。

我们可以看下最后调用的MessageQueue 的quit(boolean safe)方法:

void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                //关注这个
                removeAllFutureMessagesLocked();
            } else {
                //关注这个
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

 private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            //如果超过了当前时间,即表示是要延时发送的消息,那就都清掉
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

MessageQueue

一个用链表实现的消息队列,主要包含插入(enqueueMessage)和读取(next)两个操作,在读取的时候,还包含将该消息进行删除。

ThreadLocal

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说则无法获取到数据。

在新的版本里,ThreadLocal 的 get 和 set 方法从原先的数组索引里取值,变成了从一个自定义的 hashmap ——ThreadLocalMap里取值。

ThreadLocal 的 set 方法会获取当前线程的 value 值,没有的话则对其初始化。然后会使用 ThreadLocalMap (一个自定义的 hashmap)来保存(在以前的版本里是用了一个数组,然后 i 的值为 key,i+1的值为 value。现在改成了用 Entry 类型的数组来保存,即一个 Entry 包含了 key 和 value)。

小插曲

经常看到 Thread 和 Runnable 在一起的操作,但现在我们在 Handler,Message 也看到Runnable 的出现。

那么 Runnable 究竟是什么?

Runnable 是个接口,按照规定,当一个类要想实现这个接口,就代表着它的实例是想在线程里被执行,所以 Runnable 的接口里会有个 run 方法。

它被设计于用来说当这个对象想在活跃状态时运行自己那部分的代码,例如Thread 这个类就是这样,活跃状态的意思就是已经开始但还没结束。但其实它不是仅限于在 Thread 里的。

参考

《Android 开发艺术探索》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值