Android高工(三)——Handler消息机制

一、Handler的简介

官方文档解释如下:

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread.

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; 
and (2) to enqueue an action to be performed on a different thread than your own.

官方文档的介绍中,主要解释了Handler的意义以及它在实际开发中的作用,大体如下:

Handler作为Android的消息机制,设计它的主要意义在于:

(1)搭配与线程绑定的消息队列和Looper轮询器,可以发送消息或者Runnable对象到与线程绑定的消息队列,并在looper中取出的消息后执行这个消息对应的操作(简单来说就是发送消息,处理消息,例如主线程的启动广播、Service等都是通过它来实现的)

(2)可以进行线程切换,通过Handler来处理子线程到主线程的切换,这也是Android提供的用于线程之间通信的方式,尤其是子线程与主线程之间

二、Handler消息机制的应用

1、主线程的系统消息

 

对于主线程中,有许多Application、四大组件等相关的生命周期的系统消息

        public static final int BIND_APPLICATION        = 110;
        @UnsupportedAppUsage
        public static final int EXIT_APPLICATION        = 111;
        @UnsupportedAppUsage
        public static final int RECEIVER                = 113;
        @UnsupportedAppUsage
        public static final int CREATE_SERVICE          = 114;
        @UnsupportedAppUsage
        public static final int SERVICE_ARGS            = 115;
        @UnsupportedAppUsage
        public static final int STOP_SERVICE            = 116;

        public static final int CONFIGURATION_CHANGED   = 118;
        public static final int CLEAN_UP_CONTEXT        = 119;
        @UnsupportedAppUsage
        public static final int GC_WHEN_IDLE            = 120;
        @UnsupportedAppUsage
        public static final int BIND_SERVICE            = 121;
        @UnsupportedAppUsage
        public static final int UNBIND_SERVICE          = 122;
        public static final int DUMP_SERVICE            = 123;
        public static final int LOW_MEMORY              = 124;
        public static final int PROFILER_CONTROL        = 127;
        public static final int CREATE_BACKUP_AGENT     = 128;
        public static final int DESTROY_BACKUP_AGENT    = 129;
        public static final int SUICIDE                 = 130;
        @UnsupportedAppUsage
        public static final int REMOVE_PROVIDER         = 131;
        public static final int DISPATCH_PACKAGE_BROADCAST = 133;
        @UnsupportedAppUsage
        public static final int SCHEDULE_CRASH          = 134;
        public static final int DUMP_HEAP               = 135;
        public static final int DUMP_ACTIVITY           = 136;
        public static final int SLEEPING                = 137;
        public static final int SET_CORE_SETTINGS       = 138;
        public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
        @UnsupportedAppUsage
        public static final int DUMP_PROVIDER           = 141;
        public static final int UNSTABLE_PROVIDER_DIED  = 142;
        public static final int REQUEST_ASSIST_CONTEXT_EXTRAS = 143;
        public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;
        @UnsupportedAppUsage
        public static final int INSTALL_PROVIDER        = 145;
        public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
        @UnsupportedAppUsage
        public static final int ENTER_ANIMATION_COMPLETE = 149;
        public static final int START_BINDER_TRACKING = 150;
        public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
        public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
        public static final int ATTACH_AGENT = 155;
        public static final int APPLICATION_INFO_CHANGED = 156;
        public static final int RUN_ISOLATED_ENTRY_POINT = 158;
        public static final int EXECUTE_TRANSACTION = 159;
        public static final int RELAUNCH_ACTIVITY = 160;
        public static final int PURGE_RESOURCES = 161;
        public static final int ATTACH_STARTUP_AGENTS = 162;

3、子线程与主线程的通信/线程切换

例如Glide框架中,子线程加载图片后,在主线程上设置ImageView的bitmap

或者是我们创建子线程,创建一个Handler,传入主线程的消息队列,然后发送消息

三、Handler消息机制源码分析

 

1、Message消息

Message消息作为Handler通信的数据载体,实现了Parcelable接口,即可以执行序列化和反序列化操作

源码如下:

public final class Message implements Parcelable {
    // 消息的code参数,作为消息标识
    public int what;
    // arg1和arg2用于传递int型参数
    public int arg1;
    public int arg2;
    // 传递object对象
    public Object obj;
    
    // 发送消息的时间
    @UnsupportedAppUsage
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public long when;

    // 传递bundle对象
    /*package*/ Bundle data;

    // 指定发送消息的target,用于后面分发消息时判断分发给哪个Handler
    @UnsupportedAppUsage
    /*package*/ Handler target;

    // 传递Runnable对象
    @UnsupportedAppUsage
    /*package*/ Runnable callback;
  	// 表示同步消息与异步消息的Flag  
  /*package*/ int flags;
    
    // Message对象池的最大容量
   private static final int MAX_POOL_SIZE = 50;
 	// 一个空的构造方法
    public Message() {
    }
    
   
  // 从对象池获取Message对象
 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();
    }
    
    // 是否为系统的异步消息,异步消息利用同步屏障来进行优先执行
    public boolean isAsynchronous() {
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }

(1)可传递的参数类型

可以传递Int型、object对象、Bundle对象、Runnable对象

(2)消息对象池

在Message中维护了一个Message对象池,消息队列本质上是一个单链表,在Message通过Message next来表示指向下个Message的指针

这个消息池最大消息个数为50个(以API 30为例)

   private static final int MAX_POOL_SIZE = 50;

我们日常使用中,建议从消息池中获取消息对象

Message msg = Message.obtain()

那么消息池的消息是如何维护的呢?

首先一个线程中只会有一个消息队列,而其他的多个线程可能都会向这个线程插入消息,那么在获取消息对象的时候就需要考虑线程同步问题

这里是定义一个Object对象作为同步对象

    public static final Object sPoolSync = new Object();

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

当spool对象为空时,表示此时Message对象池中没有对象,那么就new 一个Message对象

如果不为空,那么取出spool指向的Message对象,将spool指向取出对象的next对象上,同时对象池大小-1

 

那么spool是哪里赋值呢?

我们从Looper的loop方法中来查找,在执行消息结束后,会调用Message.recycleUnchecked()方法

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    @UnsupportedAppUsage
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            // 当前对象池小于小于最大对象池大小时
            if (sPoolSize < MAX_POOL_SIZE) {
                // 将spool的值赋值给next
                next = sPool;
                // 将当前对象赋值给spool
                sPool = this;
                sPoolSize++;
            }
        }
    }

即在消息执行完毕后,当前的spool指针会指向该对象,以供下一次使用

 

回答上面的问题,为什么要使用obtain而不是直接new对象

首先明确一点new对象后,必然会产生一个新的对象,而这个对象最终也会加入到对象池中;

而使用obtain方法, 如果对象池中存在可使用的对象,那么就可以直接使用

 

(3)消息类型

Message的消息类型主要有三类:

    • 同步消息 ,target为Handler对象,isAsynchronous为false
    • 异步消息(一般为系统的消息,例如view的绘制等,这类消息一般要搭配屏障消息,保证优先执行),异步消息target为null,isAsynchronous()为true
    • 同步屏障消息(本质上也是同步消息,只是用于搭配异步消息使用,队列中按时间排序的消息队列中异步消息优先执行),同步屏障消息target为null,isAsynchronous为false

2、消息队列MessageQueue

消息队列MessageQueue作为Looper对象的属性存在,它的作用就是作为一个消息队列(单链表),可以执行消息插入和取出Message对象

(1)插入消息enqueueMessage()

插入消息本质上就是将插入的消息添加到当前消息队列的末尾,并指定消息的执行时间

插入的消息包括同步消息和异步消息

插入同步消息,一般是我们向Handler中调用sendMessage插入

插入异步消息,则是Android源码中针对UI绘制等场景插入,例如:Choreographer中

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                // 标记异步
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }
 boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            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) {
                // New head, wake up the event queue if blocked.
                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;
                // 遍历找到当前消息队列的末尾元素,将末尾元素的next指向插入的消息msg,msg的next指向空
                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) {
                // 用于欢迎next方法取出消息的无限循环
                
                // 只有当当前next已经阻塞,且队列中没有其他消息时才会调用唤醒方法
                nativeWake(mPtr);
            }
        }
        return true;
    }

从插入消息的源码中我们可以看到,主要是根据消息的执行时间对单链表进行了调整,头节点总是执行时间最早的,从头节点向后遍历,执行时间点依次增加或者是相等

即,假设msg1基于当前时间点延时为0,msg2延时0,msg3延时3,msg4延时5,按照msg1,msg2,msg3,msg4依次插入,则消息队列中结构如下:

 

(2)插入同步屏障消息postSyncBarrier

该方法用于插入同步屏障消息,是不暴露给用户使用的

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

它的作用就是将同步屏障消息插入到消息队列的头部

(2)取出消息next()

next取出消息中,我们需要特别注意同步屏障消息、同步消息、异步消息的取出逻辑

  @UnsupportedAppUsage
    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // 等待取出下个消息的时间
        int nextPollTimeoutMillis = 0;
        for (;;) {
            // 如果下个消息的执行时间不等于0,表示暂时没有任务处理,那么当前线程将进入阻塞,此时需要将还未执行完的其他任务尽快处理完
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            // 如果nextpollTimeoutMillis不为0,则阻塞当前线程等待下个消息的执行
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                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;
                }
            
            .......
            }

在next方法中,比较重要的主要有两点:

1>无限for循环

for (;;) {

...
}

这里执行了一个无限的for循环,用于不断获取队列中是否有需要执行的消息

2>取出异步消息

当我们调用postSyncBarrier插入了同步屏障消息时,此时消息队列的头部就是同步屏障消息(当然插入多个同步屏障消息,它也是按照时间排序的),同步屏障消息的target为null,因此,当判断当前消息队列头部是同步屏障消息时,则遍历消息队列,查找异步消息,并返回

 // 如果是同步屏障消息,那么遍历消息队列,找出异步消息,优先执行
                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());
                }

(2)阻塞与唤醒

 

1> nativePollOnce

该方法 主要用于执行阻塞,其中支持传入超时时间,

具体如下:

    • 0 立即返回,没有阻塞
    • 负数 一直阻塞,直到事件发生,对于MessageQueue则是在enqueueMessage时调用nativewake唤醒,取消阻塞
    • 正数,最多等待时间

 

当队列中存在消息,如果当前消息还未到执行时间,那么调用nativePollOnce(time)阻塞对应的时间

如果当前消息到达了执行时间,那么调用nativePollOnce(0),不执行阻塞,立即返回

当队列中不存在消息,那么调用nativePollOnce(-1),一直阻塞next方法,等待后续消息到来唤醒

 

它的本质是linux中的epoll机制,即在当前线程中释放CPU资源进入休眠,当等到时间到来后或者是有读写(例如调用nativewake)任务被唤醒后才会继续工作

2> nativeWake

该方法主要用于唤醒阻塞的nativePollOnce方法,从而继续取出消息

它的本质是通过pipe管道写入字符,唤醒休眠等待中的事件

在MessageQueue中,使用nativeWake的来唤醒next方法中的阻塞,主要场景有:

当消息队列中没有任何消息并且IdleHandler个数也为空,那么此时通过nativePollOnce(-1)一直阻塞当前线程,当enqueue插入消息时,就会调用nativeWake取消阻塞,唤醒next方法执行

当消息队列中头部消息为延时消息,则nativePollOnce阻塞一段时间,这个时间就是当前时间与消息执行时间的差值,如果此时在头部插入一条立即执行的消息,则会调用调用nativeWake取消阻塞

 

例如,Hnadler退出时,电泳nativewake唤醒消息队列,此时消息队列上消息为空,直接return

 

(3)阻塞for循环是否会导致ANR?

对于这个疑问,需要从两个角度来分析

1> ANR产生的原因

首先ANR产生的原因是我们在主线程执行了耗时操作,而为了保护主线程的系统消息的正常执行,因此对于Activity、Service、BroadCast中都增加了超时消息的机制,判断主线程可以在正常范围内执行完,如果没有执行完则触发ANR

这里以Service的ANR来分析

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    if (proc.executingServices.size() == 0 || proc.thread == null) {
        return;
    }
    Message msg = mAm.mHandler.obtainMessage(
            ActivityManagerService.SERVICE_TIMEOUT_MSG);
    msg.obj = proc;
    mAm.mHandler.sendMessageDelayed(msg,
            proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}

// ActiveServices
// 前台服务的ANR时间
static final int SERVICE_TIMEOUT = 20*1000;
// 后台服务的ANR时间
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;

Service启动后,ASM的HandlerThread会发送定时消息,前台的延时为20s,后台的延时200s,以前台为例,它会计算Service的执行时间,如果Service的执行时间超过了20s,那么这个延时消息就会发送出去从而产生ANR

2> 主线程的消息驱动

对于主线程而言,它负责绘制UI和各种事件,例如输入响应、帧渲染回调等,都会发送消息到主线程的MessageQueue,这类型的系统消息会唤醒阻塞,执行绘制等事件处理

假设一直处于阻塞状态,那么说明主线程实际上没有任何任务处理,在等待下一个事件处理,因此无需担心ANR

 

这个问题的核心在与MessageQueue的enqueue方法会根据消息执行时间以及是否同步异步消息来唤醒nativePollOnce,并不是发送一个延时20s的消息就会导致Activity等无法正常执行绘制等任务的执行

同步屏障与异步消息的存在,会保护我们的系统消息及时执行

3、Looper

Looper,翻译过来为轮询器,主要作用就是创建对应线程的消息队列,并启动轮询

(1)初始化prepare()

Looper的构造方法是私有权限,意味着我们无法使用它的构造方法创建对应,只能调用prepare方法

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

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    • quitAllowed: 表示当前消息队列是否可以退出,例如主线程的消息队列则是不可以退出的
    • 创建的Looper对象保存在ThreadLocal中,作为线程的独有的对象

(2)启动轮询loop()

    public static void loop() {
        // 启动无限循环,从消息队列中获取消息
         for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
             
             // 将消息分发到msg对应的Handler对象
                try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
         }

loop方法主要是启动一个无限循环,从消息队列中取出消息,通过msg.target.dispatchMessage分发给对应的Handler

4、Handler

Handler是系统提供对外操作的核心类,我们主要关心的是消息的发送和处理

(1)发送消息(send/post)

发送消息,主要有两种方式,send和post

 

1>即时消息

 

 

    public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }

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


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

2>延时消息

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

public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

(2)发送同步屏障消息

该API是不暴露给外部使用的,需要反射调用

主要是系统内部用于发送处理UI绘制相关的,为了优先处理UI绘制等相关任务,通常会先插入同步屏障,然后发送异步消息,插入异步消息后,移除屏障,保证异步消息优先执行

例如,Chroeographer中的与绘制相关的异步消息

    private static final int MSG_DO_FRAME = 0;
    private static final int MSG_DO_SCHEDULE_VSYNC = 1;
    private static final int MSG_DO_SCHEDULE_CALLBACK = 2;

 

例如:ViewRootImpl中的scheduleTraversals方法

mHandler.getLooper().getQueue().postSyncBarrier()
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

同步屏障消息的发送是通过postSyncBarrier来实现的

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;
			// 遍历当前消息队列,将同步屏障消息插入到队列头部
            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

(3)处理消息(dispatchMessage)

消息的处理,是在Looper的loop方法中

loop(){  
    .......
msg.target.dispatchMessage(msg);
    .....
}

我们主要分析Handler的dispatchMessage方法

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        // 当Message消息的Runnable不为空,则调用handleCallback方法
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            // 在创建Handler对象时传入回调,在Handler对应的线程处理
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 由Handler处理,需要覆写Handler的handleMessage方法
            handleMessage(msg);
        }
    }


    private static void handleCallback(Message message) {
        // 调用Message中的Runnable的run方法
        message.callback.run();
    }

消息的处理主要过程如下:

 

 

五、IdleHandler

IdleHandler本质上是一个接口,如下:

    /**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }

它是定义在MessageQueue中的,作用是当MessageQueue空闲时,这里的空闲具体是指:

    • 对于可退出的Handler,此时内部必然有消息,且消息的执行时间还未到达
    • 对于不可退出的Handler,则分为两种情况

当最后一个消息return交付Looper后,执行下一次循环时,此时阻塞值还不是-1,但是当前消息队列中又不存在待执行的消息,那么就执行idleHandler的queueIdle

内部消息队列有消息,但是消息的执行时间还未到达,那么调用IdleHandler的queuIdle

具体看MessageQueue中的next方法

 

 

六、Handler需要注意的问题

1、休眠对Handler延时消息的影响

Handler延时消息使用了System.updateMillils()函数,该函数在系统休眠时不会进行计算,那么如果系统进入休眠,例如应用处于后台且锁屏时未连接USB时

只有当再次唤醒时才会执行

 

2、Handler的内存泄漏问题

例如组件销毁时,Handler仍然有延时消息没有发送

为了避免这个情况,我们需要在组件销毁的方法中,调用Handler的removeCallbackAndMessage方法,而该方法本质上是调动了MessageQueue的removeCallbackAndMessages方法

该方法的主要作用是遍历当前消息队列中的消息,依次回收到对象池中,并消息队列的头节点置为空,保证消息队列中没有需要执行的消息

 void removeCallbacksAndMessages(Handler h, Object object) {
        if (h == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;

            // Remove all messages at front.
            while (p != null && p.target == h
                    && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                // 回收消息
                p.recycleUnchecked();
                p = n;
            }

            // Remove all messages after front.
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }

3、子线程创建Handler问题

资料

1、nativePollOnce与nativeWake解析

https://blog.csdn.net/chewbee/article/details/78108201

https://www.cnblogs.com/jiy-for-you/archive/2019/10/20/11707356.html

2、同步屏障

https://www.jianshu.com/p/086462f061b7

https://blog.csdn.net/start_mao/article/details/98963744

https://blog.csdn.net/asdgbc/article/details/79148180

3、Handler的扩展

https://blog.csdn.net/weixin_44339238/article/details/109780367?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-8.pc_relevant_default&spm=1001.2101.3001.4242.5&utm_relevant_index=11

面试问题

1、Handler原理

(1)Looper、Message与Thread有什么对应关系?

(2)Looper轮询为什么不会产生ANR

 

(3)延时消息如何实现的?系统休眠、切换到后台对于延时消息是否有影响?

延时消息的发送,首先我们使用时传入了延时时间,单位毫秒,发送消息时Handler内部通过System.updateMillis()方法计算当前时间戳,用这个时间戳加上我们加入的延时,就是消息最后发送的时间戳

(4)Looper中的无限循环是否会消耗资源

2、子线程问题

(1)子线程中实例化 Handler会有什么问题?

在Handler构造方法中存在Looper非空的判断,如果为空,则抛出RuntimeException异常

 if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }

(2)子线程创建Handler中使用Looper.prepare和loop方法的作用是什么?

UI线程默认会创建Looper和MessageQueue,并启动loop轮询

子线程需要我们手动处理这些逻辑

3、定时任务

(1)是否可以用Handler实现定时任务?

不可以,休眠时会停止计时

建议使用workManager或者AlarmManager或者Timer来执行

4、同步屏障与异步消息

(1)讲讲同步屏障与异步消息

 

 

posted @ 2022-02-21 19:24  大喵爱吃鱼1027  阅读(128)  评论(0编辑  收藏  举报