zoukankan      html  css  js  c++  java
  • Handler、Looper、MessageQueue原理分析

    一、概述

      Android的消息机制是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。我们在开发中使用Handler一般是切换线程更新UI等操作。因为Android规范规定Android只能在主线程中更新UI,这一点可以在ViewRootIml的checkThread方法中得到证明。代码如下:

    void checkThread() {
       //mThread是主线程,此处判断如果当前线程不是主线程,而此时又想要更新UI就会抛出下面这个异常。 if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }

      有些时候大家可能会看到如下的代码:

         new Thread(){
               @Override
               public void run() {
                   //在子线程中更新UI
                 activity.runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
                         textView.setText("您好Handler");
                     }
                 });  
               }
           }.start();

      上面这段代码运行后的确不会报错,TextView组件是哪个也会成功的显示“您好Handler”。这是为什么呢?答案就是runOnUiThread方法内部会首先判断当前线程是不是主线程,如果是主线程就直接调用runnable.run方法执行,如果再子线程,则调用主线程mHandler.post(r)把消息发送到主线程Handler执行。所以上面的代码之所以可以成功运行是因为把更新UI的动作切换到了主线程,下面这段代码就是证据

     public final void runOnUiThread(Runnable action) {
         //判断当前线程是否是主线程,如果是就直接调用action.run.如果不是就把消息发送到主线程Handler中执行。 if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } }

      先简单的说这么多下面开始分析Looper、MessageQueue、Handler的源代码,在分析完源代码后最后会有一个“灵魂拷问”对面试非常有帮助,如果最近要去面试,一定要看一下,基本上囊括了Handler的所有考点。

    二、源码分析

      2.1.MessageQueue是一个消息队列,其实说它是一个消息队列并不准确,因为它的内部是一个单链表的结构,叫它链表更为合适。为什么要是用链表形式呢?这就涉及到数据结构了,我们通过分析MessageQueue会发现其使用最多的方法就是插入(添加)和删除(删除伴随着取出)。而单链表结构恰恰对插入和删除的处理效率比较高,所以就选择了单链表

      我们先来看看其构造方法:

    private static final String TAG = "MessageQueue";
        
        private final boolean mQuitAllowed;
      ...省略了一些代码
        MessageQueue(boolean quitAllowed) {
            mQuitAllowed = quitAllowed;
            mPtr = nativeInit();
        }
    

      其构造方法非常简单,只需要传递一个quitAllowed即可。quitAllowed代表是否允许退出。true允许,false代表不允许,一般在主线程会设置为false,在子线程会设置为true。

      ps:native方法和本节业务关系不大,不做说明。

      2.1.1.MessageQueue.enqueueMessage(Message msg,long when),向消息队列中添加一条数据 

    boolean enqueueMessage(Message msg, long when) {
            ...省略了一些异常抛出
            synchronized (this) {
                if (mQuitting) {
                    IllegalStateException e = new IllegalStateException(
                            msg.target + " sending message to a Handler on a dead thread");
                    Log.w(TAG, e.getMessage(), e);
                    msg.recycle();
                    return false;
                }
    
                msg.markInUse();
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                    // Inserted within the middle of the queue.  Usually we don't have to wake
                    // up the event queue unless there is a barrier at the head of the queue
                    // and the message is the earliest asynchronous message in the queue.
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    for (;;) {
                        prev = p;
                        p = p.next;
                        if (p == null || when < p.when) {
                            break;
                        }
                        if (needWake && p.isAsynchronous()) {
                            needWake = false;
                        }
                    }
                    msg.next = p; // invariant: p == prev.next
                    prev.next = msg;
                }
    
                // We can assume mPtr != 0 because mQuitting is false.
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

      enqueueMessage的逻辑比较清晰,向MessageQueue中添加一条Message,如果添加成功则返回true

      2.1.2.MessageQueue.next() 从队列中获取一条Message。

     Message next() {
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
                nativePollOnce(ptr, nextPollTimeoutMillis);
                synchronized (this) {
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    if (msg != null && msg.target == null) {
                        // Stalled by a barrier.  Find the next asynchronous message in the queue.
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    if (msg != null) {
                        if (now < msg.when) {
                            // Next message is not ready.  Set a timeout to wake up when it is ready.
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // Got a message.
                            mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();
                            return msg;
                        }
                    } else {
                        // No more messages.
                        nextPollTimeoutMillis = -1;
                    }
    
                    // Process the quit message now that all pending messages have been handled.
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
    
                    // If first time idle, then get the number of idlers to run.
                    // Idle handles only run if the queue is empty or if the first message
                    // in the queue (possibly a barrier) is due to be handled in the future.
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    if (pendingIdleHandlerCount <= 0) {
                        // No idle handlers to run.  Loop and wait some more.
                        mBlocked = true;
                        continue;
                    }
    
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
    
                // Run the idle handlers.
                // We only ever reach this code block during the first iteration.
                for (int i = 0; i < pendingIdleHandlerCount; i++) {
                    final IdleHandler idler = mPendingIdleHandlers[i];
                    mPendingIdleHandlers[i] = null; // release the reference to the handler
    
                    boolean keep = false;
                    try {
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
    
                // Reset the idle handler count to 0 so we do not run them again.
                pendingIdleHandlerCount = 0;
    
                // While calling an idle handler, a new message could have been delivered
                // so go back and look again for a pending message without waiting.
                nextPollTimeoutMillis = 0;
            }
        }
    

      next()方法从队列中取出一个Message并返回,取出伴随着删除。如果队列中没有Message则返回一个null。

      2.2.Looper消息循环,只有创建了Looper.prepare()和Looper.loop()方法消息循环系统才能正式的启动。 

      //ThreadLocal用于存取当前线程的Looper
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; // 主线程Looper
      //消息队列 final MessageQueue mQueue;
       //当前线程 final Thread mThread;   ...省略了一些代码 private Looper(boolean quitAllowed) {
      //初始化消息队列 mQueue = new MessageQueue(quitAllowed);
        //获取当前线程的实例对象 mThread = Thread.currentThread(); }

      从上面的代码我们可以看出,Looper中包含了一个ThreadLocal用来存放和获取当前线程的Looper对象,而在构造方法中会初始化MessageQueue和当前线程实例,同样消息队列是否允许退出的标记也是在这里设置的。

      2.2.1.Looper.prepare(),初始化Looper 

      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方法内部会首先会收取当前线程的Looper对象(sThreadLocal.get())然后判断Looper是否为空,如果为空则抛出异常,如果不为空则实例化一个Looper并将其放在当前线程的sThreadLocal中。

      2.2.2.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) {
                   //退出条件是msg=null
                    return;
                }
    
                ...省略了一些方法
                try {
                    msg.target.dispatchMessage(msg);
                } 
          ...省略了一些方法 } }

      我们把Loop方法的结构简化一下,如上所示。其实逻辑非常清晰了,首先通过myLooper()方法获取当前线程的Looper对象,然后判断Looper对象如果为空,则抛出异常。然后从Looper中取出消息队列MessageQueue。之后创建一个死循环来不停的通过MessageQueue.next()(next是阻塞方法)方法中取出消息。然后通过msg.target.dispatchMessage(msg)把消息分发出去。这个死循环的退出条件是msg==null.

      下面看下上面代码比较重要的几个点:

      1.myLooper()方法

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

      从sThreadLocal中取出当前线程的Looper对象。

      2.msg.target.dispatchMessage(msg)

    public final class Message implements Parcelable {
    
        /*package*/ Handler target;
    
        /*package*/ Runnable callback; 

      msg指的是Message。而target指的是Handler在Message中的引用。所以msg.target.dispatchMessage(msg)进行消息分发的时候其实是执行在Handler所在的线程的,如果Handler在主线程中进行创建,其分发过程就会从其他线程切换到主线程中执行。因为Handler创建的时候默认会使用当前线程的Looper进行构造。

      msg.target代码

      2.3.Handler,Android中的消息机制其实指的就是Handler的运行机制以及Handler所附带的MessageQueue、Looper的循环机制。

      2.3.1.Handler的构造函数分析

      public Handler() {
            this(null, false);
        }
    
       //创建一个Handler并传入CallBack,Callback是Handler的内部接口
        public Handler(Callback callback) {
            this(callback, false);
        }
    
        //通过传入一个外部的Looper构造一个Handler  
        public Handler(Looper looper) {
            this(looper, null, false);
        }
    
      //传入外部Looper和Callback构造Handler
        public Handler(Looper looper, Callback callback) {
            this(looper, callback, false);
        }
    
      
        public Handler(boolean async) {
            this(null, async);
        }
    
     
        public Handler(Callback callback, boolean async) {
            if (FIND_POTENTIAL_LEAKS) {
                final Class<? extends Handler> klass = getClass();
                if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                        (klass.getModifiers() & Modifier.STATIC) == 0) {
                    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                        klass.getCanonicalName());
                }
            }
    
            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;
            mCallback = callback;
            mAsynchronous = async;
        }
    
       
        public Handler(Looper looper, Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

      通过以上代码可知,我们有7中形式可以构造一个Handler,并在构造函数中初始化MessageQueue。唯一需要介绍的是Callback接口,其是一个Handler的内部接口,定义了handleMessage方法。

      2.3.1.Handler通过post和sendMessage发送消息

        1.Handler.post(runnable)方法    

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

       post方法接着会调用sendMessageDelayed方法,并调用getPostMessage(r)传入一个Runnable生成一个Mesage返回

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

      在getPostMessage方法中会通过Message.obtain()方法创建一个Message并把runnable赋值给Message的callback成员变量,然后把这个Message返回

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

      直接调用了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方法中会生成一个MessageQueue并调用Handler的enqueue方法把queue和Message传递进去。

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

        在enqueueMessage方法内部会把当前Handler赋值给Message.target ,然后把这条消息通过queue.enqueueMessage放入消息队列,然后消息就发送完成了

        2.Handler.sendMessage(msg)   

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

       在sendMessage内部会调用sendMessageDelayed方法把msg向下传递,其他的和上面的post发送情况相同。直到调用enqueueMessage中消息队列的queue.enqueueMessage(msg,updatetime)把消息放入队列,然后发送消息的过程也完成了  

      2.3.2.Handler通过dispatchMessage(msg)对消息进行分发   

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

      消息的分发过程是在Handler的dispatchMessage(msg)方法中的。代码量不大,逻辑也比较清晰,首先判断msg.callback是否为空,如果不为空就调用handleCallback处理消息,如果为空就判断mCallback是否为空,如果mCallback不为空就调用mCallback.handleMessage处理消息,如果mCallback为空就调用Handler的handleMessage处理消息。

      上面的的逻辑我们分三步分析下:

      1.msg.callback!=null的情况。

      这种情况是handler发送post消息的时候走的一条路,因为post消息的时候会给msg.callback=runnable。所以这种情况下msg.callback不为空。走着个逻辑会调用msg.callback.run()方法执行逻辑。

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

      

      2.mCallback!=null。此时的mCallback就是Handler的内部接口Callback。通过创建Handler的时候实例化来实现的。  

      new Handler(Looper.getMainLooper(), new Handler.Callback() {
               @Override
               public boolean handleMessage(Message msg) {
                   return false;
               }
           });
    

      3.mCallback=null。此时使我们继承Handler时重写其handleMessage所执行的方法。

       我们使用Handler.sendMessage发送的消息都会走这个逻辑。

      Handler handler = new Handler(){
           @Override
           public void handleMessage(Message msg) {
               super.handleMessage(msg);
           }
       };
    

      到此Handler我们也分析完毕了。下面做一个Handler、Looper、MessageQueue的总结。

      1.MessageQueue存放Message,其提供next方法获取并删除队列数据,enqueueMessage向队列中加入Message。

      2.Looper包含MessageQueue

      3.Handler包含Looper,并可以通过Looper拿到MessageQueue

      4.首先Handler调用sendMessage或者post方法向消息队列中插入一条Message,Looper的loop()方法不断的从MessageQueue中拿消息,取出消息后调用消的Message.target.dispatchMessage方法对消息进行分发。其实Handler的消息机制也就这么多东西

     

    三、灵魂拷问

      问题1:说说Handler的post和send方法有有什么区别?

      答:post和send方法其实都是向MessageQueue中添加消息,只是post方法会给Message.callback赋值,而send方法不会。所在在Handler.dispatchMessage的执行逻辑也不同。post方法发送的msg由于有callback,所以在dispatchMessage的执行优先级比较高。send发送的msg由于没有封装msg.callback所以在优先级上要比post低两级。大家看一下Hadler的dispatchMessage就明白了。

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

      

      问题2:可不可以在子线程中更新UI?

      答:形式上可以通过Handler.post 或者runUiThread方法在子线程中更新UI,但是他们最终都是通过向主线程Handler发送消息来实现的,本质上还是在主线程中更新UI。

      问题3:Looper.loop()方法是个死循环为什么不会造成Ui线程卡顿?

      答:使用nativePollOne进行阻塞,使用nativeWake方法进行线程的唤醒

      问题4:使用Message.obtain()创建一个Message有什么好处?

      答:obtain方法在某些方面可以节省内存,但是基于性能上来说还是直接new Message更划算,毕竟obtain方法内部加锁了。 

       public static Message obtain() {
            synchronized (sPoolSync) {
                if (sPool != null) {
                    Message m = sPool;
                    sPool = m.next;
                    m.next = null;
                    m.flags = 0; // clear in-use flag
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();
        }  

      问题5:Handler在什么情况下会造成内存泄露?如何优化?

      答:在Activity中创建一个非静态内部类的时候会出现这个问题,因为内部类默认持有外部类的引用。而当Activity销毁的时候如果消息队列中还有消息则消息队列必须要所有的消息处理完才能够退出,在这段时间内内部类仍然持有外部类的引用到只Activity没法被销毁从而造成内存泄露(不过这种内存泄露是临时性的)。1.创建一个静态内部类,并将外部类对象用WeakReference引用,因为静态内部类不持有外部类对象的引用,使用WeakReference包裹外部类可以在GC的时候直接回收外部类。2.在Activity finish之前调用handler.removeCallbacksAndMessages移除还剩余的消息,既然内部类持有外部类引用是因为消息未移除干净,则直接移除消息来解决这个问题就行了。

      问题6:在一个线程中是否可以创建多个Looper,为什么?

      答:不可以,因为在调用Looper.prepare()方法内部会调用sThreadLocal.get()方法判断是否已经创建过Looper,如果创建了就抛出异常。如下代码所示:

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

      问题7:handler.postDealy的实现原理

      答:在MessageQueue的next方法的时候,如果发现Message有延时则将消息插图尾部,如果有多个延时消息则进行延时排序。

      问题8:MessageQueue的数据结构是什么?

      答:MessageQueue的数据结构是单链表数据结构,因为MessageQueue最主要的方法是next()和enqueueMessage,而单链表恰恰在插入和删除方面效率比较高。

      问题9:Looper怎样区分目标Message属于哪个Handler?

      答:通过ThreadLocal来区分,ThreadLocal可以不同的线程中保存不同的数据(ThreadLocalMap),而且每个线程的数据副本独立。例如:我们在主线程中的Looper可以通过主线程的ThreadLocal.get()方法获取,在主线程中可以利用主线程的ThreadLocal.get()方法获取。而Handler创建的时候默认是依靠当前线程的Looper创建的,Looper和Handler绑定之后,Handler发的消息只会发送给Handler绑定的Looper出来,所以可以区分清楚。

      问题10:说说ThreadLocal的实现原理

      答:请看上一篇文章。

      问题11:为何我们在主线程中可以直接使用Handler,而不必手动创建Looper

      答:系统在创建App的时候会创建ActivityThread实例,并调用其中的main方法,在main方法的内部会创建Looper.prepareMainLooper(),Looper.loop()方法开启消息循环。系统启动的时候已经帮我们创建好了,所以我们在主线程中创建Handler的时候实际上时使用的主线程所在的Looper进行创建的。所以不必再创建Looper了。

  • 相关阅读:
    页面进度条插件pace.js
    cropper.js头像剪裁
    数组每一项与之前项累加
    css实现波浪纹,水波纹动画
    在echart的map上把symbol替换成gif通过convertToPixel坐标转换成px定位到页面,没有做echart的缩放情况下
    vue折叠面板的收缩动画效果,实现点击按钮展开和收起,并有个高度过渡的动画效果
    vue项目中应用自定义的字体
    vue中监听滚动条事件,通过事件控制其他div滚动,固定表头和列
    好久没写博客了
    在nuxt中使用富文本编辑器quill
  • 原文地址:https://www.cnblogs.com/tony-yang-flutter/p/12507800.html
Copyright © 2011-2022 走看看