美文网首页
Android Handler消息机制

Android Handler消息机制

作者: 彭空空 | 来源:发表于2019-09-28 12:44 被阅读0次

    导读

    由于我更新优化本篇文章中的笔误之处,导致文章莫名被删除,故此重新发布!

    Android Handler消息机制

    为了避免ANR,我们通常会把耗时操作放在子线程里面去执行,因为子线程不能更新UI,所以当子线程需要更新UI的时候就需要借助到Andriod的消息机制,也就是Handler机制了。那么其原理是什么呢?下面我们根据平时的使用一步一步来解读源码。
    以上内容可能会产生的问题:

    • 什么是ANR?

      在Android上,如果你的应用程序有一段时间无法响应,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择让程序继续运行,但是,他们在使用你的应用程序时,并不希望每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样,系统不会显示ANR给用户。

    • 耗时操作为什么不能再主线程?
    • 为什么子线程不能更新UI?
    • Handler机制是什么?

    一. Handler的常见用法。

    1. 创建handler对象并重写handleMessage方法,以便于处理自己需要的逻辑。
    private Handler handler = new Handler(){
      @Override
      public void handleMessage(Message msg){
        ...
      }
    }
    
    1. 使用handler 对象发送消息(对象、载体)。
    private void sendMsg(){
      Mssages msg = handler.obtainMessage();
      msg.what=what;
      msg.age1=age1;
      handler.sendMessageDelayed(msg,1000);
    }
    ...
    

    二. 代码分析

    1. 以上代码可以看出,在主线程创建了一个handler对象,并重构了handleMessage方法,然后通过handler发送消息对象,中间经过一系列处理后,handleMessage方法接收到传递的数据,就可以处理具体的逻辑了(如更新UI)。
    2. 以上代码可以看出,发送消息、最后处理数据时都使用的是Message对象,这是在源码设计层面时约定的媒介(载体、中间件、快递),这里先不进行展开,只做一个简单的介绍:

    what字段作为标识,
    arg1/arg2字段作为简单类型的参数,
    obj字段作为复杂对象,以及Bundle常见参数
    target字段作为handler对象标识

    1. 再来看发送消息 sendMessageDelayed,通过源码可以发现所有的sendMessage方法执行后,最终都会走sendMessageAtTime()方法(这里就不贴上具体源码了)。
       public boolean sendMessageAtTime(@NonNull 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方法里会拿到一个MessageQueue的实例对象,并触发enqueueMessage(queue, msg, uptimeMillis)方法。那我们就先看看enqueueMessage方法,然后在去看mQueue怎么来的。

          boolean enqueueMessage(Message msg, long when) {
            ...
            Message p = mMessages;
            synchronized (this) {
             if (p == null || when == 0 || when < p.when) {
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                ...  
                Message p = mMessages;
                ...
                msg.next = p; // invariant: p == prev.next
                ...
            }
          }
            return true;
        }
    

    首先这是一个被synchronized修饰的同步代码块,确保了并发问题;核心逻辑是对Message的实例对象p进行非空判断,最终两个两个逻辑里都会把msg放到Message实例对象p的next方法中进行排队。

    4.现在回来研究这个mQueue对象是怎么来的呢,通过command+f(ctrl+f)搜索,最终发现是Handler构造方法中获取的(通过代码可以看出mQueue的获取是通过looper.mQueue,而我们前面在创建handler的时候使用的是无参构造的,那么这个Looper对象的实例额mLooper是怎么来的?

     @UnsupportedAppUsage
        public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    4.1 观察Handler的其他重载的构造方法发现,我们的无参构造最后会走如下构造,而这个mLooper实例对象是通过Looper.myLooper()方法获取的而且还不能为空!由此可以看出Looper、MessageQueue都是非常重要的对象。

       public Handler(@Nullable 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()");
            }
            mQueue = mLooper.mQueue;
        }
    

    4.2 继续跟踪源码,进入Looper类发现myLooper()的内部是通过sThreadLocal.get()获取的,那么有get()自然是有set()的,所以我们command+f(ctrl+f)搜索找到了prepare()方法,而这个方法会被prepareMainLooper的方法执行,prepare翻译过来是准备的意思,prepareMainLooper就是准备主Looper的意思了,这里要注意有synchronized修饰噢。

       ...
       public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
       ...
       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));
        }
       ...
       public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    

    4.3 到这里,我们知道了Handler无参构造的时候其实是使用了一个叫MainLooper及MessageQueue的对象,那这个MainLooper具体又是在哪里创建的呢,那就查查这个prepareMainLooper()方法在哪里被调用的使用command+shift+f进行全局搜索:


    WX20200201-115959@2x.png

    搜索出来4个结果,除了Looper.java外,SystemServer.java是系统级别的这里不做深入,后续新增该类的解读; Bridge.java是桥梁类,注释中写着“Main entry point of the LayoutLib Bridge.”,这里也不做深入;最后是ActivityThread.java类,也是这里需要重点关注的类:

    public final class ActivityThread extends ClientTransactionHandler {
      ...
       public static void main(String[] args) {
            ...
            Looper.prepareMainLooper();
            ...
            Looper.loop();
            ...
        }
      ...
    }
    

    有两个重大发现:
    1、Looper.prepareMainLooper()是在一个叫ActivityThread 的 final 类型的Java类,的main(String[] args) 方法中执行的。
    2、还执行了Looper.loop();
    main是Java的入口方法,说明什么呢,在整个(应用)代码里Looper.prepareMainLooper()是最先执行的代码之一,也就是说Handler所使用的Looper早就初始化好了,而且是同步唯一的(如果忘了请回翻 )。

    接下来看看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();
            }
        }
    

    这里可以看出几个重要消息
    1、Looper、MessageQueue是不可缺少的对象
    2、使用到了for (;;) ,就是说无限循环,一直去MessageQueue的next()去查有没有Message对象,
    3、有Message对象时会执行msg.target.dispatchMessage(msg);方法
    当然也会有2个困惑的问题
    3.1、for (;;) 这个无限循环不会让APP卡死吗?
    3.2、 有一行代码if (msg == null) return 其注释是的意思是是阻塞,return之后不是继续for循环吗?

    4.4 继续跟踪 msg.target.dispatchMessage(msg)方法,前面有提到target其实就是Handler(为什么呢,大家有兴趣可以看看源码)也就是说dispatchMessage方法是Handler类下的

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

    发现这里调用了一个很眼熟的方法名handleMessage 没错,这就是一开始我们重写的的handleMessage方法,至此,脑海中是否已经有了一个Handle整体轮廓图?是否还有一个疑问,Handler啥时候把线程给切换了?

    总结

    1.APP在启动的时候,最先启动ActivityThread类的main方法,其中Looper的初始化工作和准备工作就是这时候执行的(获得Looper对象、MessageQueue)
    2.Looper开启loop()方法(永动机)去死循环遍历MessageQueue的消息队列(至于为什么没有卡死前面有提到?有提到吗,为什么我修改的时候发现没有,捂脸)
    3.在通常用法中创建Handler对象时,构造方法会拿到looper、mQueue对象
    4.使用Handler对象发送消息(sendXxx())
    5.把消息加入队列(mQueue.enqueueMessage)
    6.第2步的永动机读取MessageQueue的消息
    7.执行dispatchMessage方法
    8.执行代码中创建Handler方法时重写的handleMessage方法完成Handler机制的整个过程。

    最后,让我们带着文章中的问题,进行继续深入:Android中为什么主线程不会因为Looper.loop里的无限循环ANR?

    相关文章

      网友评论

          本文标题:Android Handler消息机制

          本文链接:https://www.haomeiwen.com/subject/znisuctx.html