闲聊Android中的Handler机制

作者: 冬日毛毛雨 | 来源:发表于2021-03-15 22:08 被阅读0次

前言

我们经常使用的Handler就是用来更新UI,我们知道,主线程不能执行耗时操作,这样会造成UI卡顿,所以需要在子线程进行耗时的I/0操作,文件下载或者是访问网络。我们又知道,子线程不能更新UI,当在完成子线程的耗时操作之后,就需要转换到主线程来更新UI。那么,系统是怎么样切换线程,完成子线程到主线程的转换的?

这就是本文要讲的重点,Android的消息机制。

Android的消息机制主要指的是Handler的运行机制,Handler是Android的一个上层接口,开发过程中只需要与Handler交互,通过它可以轻松地将一个任务切换到Handler所在的线程去执行。

Android为什么不允许在子线程更新UI

Android的UI控件不是线程安全的,在多线程并发访问的条件下可能会导致UI处于不可预期的状态。举个例子,假如允许子线程更新UI,在子线程A对一个TextView 更新 为 “hello world”,在子线程B对这个TextView 更新为 “你好,世界 ”,线程并发环境下,就有可能出现 “你好,world” 的UI界面,显然,这不是我们想要的。

那为什么系统不对UI控件加上锁机制?

  1. 加上锁机制会让UI逻辑变复杂
  2. 锁机制会降低UI访问的效率

综上,最简单高效的方法就是采用单线程模型来处理UI操作,即只允许在主线程更新UI

Handler的工作流程

Hander怎么样实现线程切换的?这是我们本文要分析的话题,要深入理解切换线程的原理,就得先知道Handler是怎么使用的。下面我通过一段代码,讲解Handler切换线程的流程,然后进行源码分析,理解原理实现。

    //在主线程中创建Handler实例
    //步骤2
    private Handler handler = new mHandler();

    //子线程
    //步骤3
    public void sonThread(){
        new Thread(){
            @Override
            public void run() {
                super.run();
                // ....
                // ....
                // 在子线程中执行耗时操作
                Message msg = Message.obtain();
                msg.what = 1; //消息标识
                msg.obj = "Message"; //消息内容
                //步骤4
                handler.sendMessage(msg); //发送消息
                //耗时操作完成,需要更新UI,通过Handler发送消息
            }
        }.start();
    }

    //主线程
    //步骤1
  static class mHandler extends  Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            //步骤5
            //收到消息。在此更新UI
            // ....
            // ....
        }
    }

Handler的使用分为4个步骤

  1. 自定义Handler子类(继承Handler类),复写handleMessage()方法
  2. 在主线中创建Handler实例
  3. 创建所需的消息对象
  4. 在子线程中发送消息到消息队列中
  5. handleMessage处理消息(更新UI

从代码层面上来看,大致可以分为这几个步骤,讲解了Handler的使用步骤。下面我们深入方法内部源码,看看这些步骤用了底层的哪些方法,从而掌握Handler底层实现的原理。

先说一下结论

子线程使用Handler发送消息到MessQueue中入列,Looper取出消息交给Hander,主线程使用Handler处理消息,更新UI

下面我们根据这句话来分析具体的细节。

Handler

Handler对象的创建

查看Handler源码,发现Handler共有七种构造方法

上面例子使用的是Handler(),handler()->handler(callback,async)

 public Handler(@Nullable Callback callback, boolean async) {
        ...
        ...
        //获取当前looper
        mLooper = Looper.myLooper();
        //如果当前线程没有looper,则说明没有调用looper.prepare(),抛出异常
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        //把Looper的Queue赋值给Handle的Queue
        mQueue = mLooper.mQueue;
        //mCallBack赋值
        mCallback = callback;
        //mAsynchronous赋值
        mAsynchronous = async;
 }

Callback:一个接口,内部有一个handleMessage方法,当实例化Handler时可以使用Callback接口,避免自己去实现Handler子类

async:是否是异步消息,true表示是异步消息,false表示是同步消息,默认是同步消息。

异步消息相对于同步消息的而言的,表示消息不会受到中断或者事件的影响其全局顺序。异步消息是不受到MessageQueue.enqueueSyncBarrier(long)的同步障碍影响。

Handler构造器完成了mQueuemCallBackmAsynchronous的赋值,mQueue是通过mLooper获取的,一个线程只有一个Looper,而mQueue又是通过Looper获取的,就样就实现了一个线程只有一个MessageQueue

Handler发消息给MessageQueue

从发消息开始,进入sendMessage方法内部,由sendMessage->...->enqueueMessage

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

进入enqueueMessage

 private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

enqueueMessage调用了MessageQueueenqueueMessage的方法,这个方法很重要,这是HanderMessageQueue关联的环节

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

return queue.enqueueMessage(msg, uptimeMillis);

现在来看看这个queue是什么,依次向上查找,发现了这一行代码

MessageQueue queue = mQueue;

mQueue则是Handler这个类中的一个成员变量

final MessageQueue mQueue;

Handler发送消息的方法sendMessage最终执行的是queue.enqueueMessage(msg, uptimeMillis),而queue是一个MessageQueue对象,所以可以这么理解,Handler发送消息,实际上就是执行MessageQueue的一个入队操作enqueueMessage

对应结合上面的例子,子线程使用Handler发送了一个Message对象, handler.sendMessage(msg)这行代码底层实现的就是MessageQueueenqueueMessage入列操作,接下来看MessageQueue的入列操作。

补充:MessageQueue本质上是一个单链表结构,翻译过来就是一个消息队列,用于存放Handler发送过来的消息,入列操作实际上就是链表的插入操作,我们可以通过源码来验证它。

Message

Message是上述例子中所说的消息,当我们在子线程完成耗时操作,发送消息更新UI。这里的消息指的就是Message对象,Message属性有

  • what 用户定义的Message的标识符用以分辨消息的内容。

当我们在主线程执行handleMessage方法时,需要判断接收的是哪个消息以便执行,这个what就是用来区分不同消息的属性。

  • arg1arg2 保存几个整形的数值
  • obj 用来保存序列化的对象
  • when 用于存储发送消息的时间点,以毫秒为单元

Message对象的创建

在上述例子中,有这么一段代码 Message msg = Message.obtain(),它的作用是获取一个Message实例,

为什么不是通过new获取呢?

为了复用对象,可避免避免重复创建实例对象,达到节约内存的目的。

    public static Message obtain() {
       // 保证线程安全
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                 // flags为移除使用标志
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

spool是一个Message对象,默认是null,当spoolnull时,调用obtain方法会重新new一个Message对象,既然官方不推荐使用new获取对象,那么大部分情况下spool是不为空的。

消息是怎么被放入消息对象池的?

查看recycleUnchecked方法

    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 = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;
        //拿到同步锁,以避免线程不安全
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

可以看出spool在这里面也有赋值,当一个Message被回收时,会把这个Message对象作为消息对象池中下一个被复用的对象,并且将消息对象池的数量+1。也就是说,当系统回收消息的时候,会优先把他放入消息池,等待下一次复用。

MessageQueue

MessageQueue对象的创建

MessageQueue的构造函数

   MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

MessageQueue只是有一个构造函数,该构造函数是包内可见的,其内部就两行代码,分别是设置了MessageQueue是否可以退出和native层代码的相关初始化

MessageQueueenqueueMessage入列操作

    //入队操作
    boolean enqueueMessage(Message msg, long when) {
        //判断target变量是否为null,能为null的只有障栅,而障栅入队则是通过postSyncBarrier()方法入队,所以msg的target一定有值,为null抛出异常
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        //加入同步锁
        synchronized (this) {
            //判断msg的标志位,因为此时的msg应该是要入队,意味着msg的标志位应该显示还未被使用。如果显示已使用,明显有问题,直接抛异常。
            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的标记为,msg标记位显示已使用
            msg.markInUse();
            //设置msg的when
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //p==null表示链表头部元素为null
            //when==0表示立即执行
            //when<p.when表示msg的执行时间早于链表中头部元素的时间
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //设置msg成消息队列中链表头部元素
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
                //如果上面三个条件都不满足则说明要把msg插入到中间的位置,不需要插入到头部
            } 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表示是否唤醒消息队列,如果头部元素不是障栅或者异步消息而且还是插入中间的位置,不需要唤醒消息队列
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //进入死循环
                for (;;) {
                    //p指向的是mMessage,prev=p,在下一次循环时,prev指向了第一个message,以此类推
                    prev = p;
                    //移向下一个元素
                    p = p.next;
                    //p==null说明没有下一个元素,消息队列到头了,跳出循环
                    //when<p.when则说明当前入队的这个message的执行时间是小于队列中这个任务的执行时间的,
                    // 也就是说入队的这个message比队列中的message先执行,说明这个位置刚好是适合这个messgae的,跳出循环
                    //如果下面的两个条件都不满足,则说明这个位置还不是放置这个需要入队的message,则继续跟链表中后面的元素,】
                    // 也就是继续跟消息队列中的下一个消息进行对比,直到满足条件或者到达队列的末尾。
                    if (p == null || when < p.when) {
                        break;
                    }
                    //因为没有满足条件,说明队列中还有消息,不需要唤醒。
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //将入队的这个消息的next指向循环中获取到的应该排在这个消息之后的message。
                msg.next = p; // invariant: p == prev.next
                //将masg前面的message.next指向了msg
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            //如果需要唤醒,则唤醒
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

源码的详细注释已经在上面给出来了,现在我们只分析enqueueMessage所做的主要功能,就是Handler在发送消息,执行sendMessage,进而执行MessageQueueenqueueMessage方法后,怎么实现在MessageQueue队列中插入消息。

MessageQueue翻译成队列,实际上它实现的是一个单链表结构,那为什么不使用数组结构?

单链表的插入删除效率比数组高,而数组的查找效率比单链表高,MessageQueue就是利用了单链表插入删除效率高的优点,当我们使用Handler发送消息的时候,实际上执行的是一个入列操作,而Looper在处理收到的消息的时候,MessageQueue实际上执行的是一个出列操作。Handler机制中频繁使用的是MessageQueue的入列出列操作,所以使用单链表结构来实现。

MessageQueue的入列操作,实际上就是单链表的插入操作,当然,不仅仅是单链表插入一个数据这么简单,这里还需要执行MessaeQueue这个消息队列一些特有的方法。

用一句话概括,enqueueMessage方法就是遍历链表,按照when属性的大小将插入的数据进行排序。

既然是单链表插入,那么插入消息msg的条件是什么?或者说根据什么来确定插入的位置?

Messagewhen属性的作用:用于存储发送消息的时间点,以毫秒为单位

  1. 当消息队列中没有消息,将消息队列的头结点指针指向msg
  2. 当消息Message的属性when==0(表示立即执行),或者当前头节点的when<此时插入消息的when时,将消息msg作为头结点插入在原有的消息前
  3. 不符合以上两点,遍历链表,找到符合when<当前遍历到的链表元素的when的位置,插入在当前遍历到的当前链表元素的前面
  4. 若都不符合,插入在链表末尾

Looper

Hander使用sendMessage发送消息,sendMessage又调用了MessageQueueenqueueMessage方法,enqueueMessage插入数据的原理就是链表的插入,在开头先说的结论中,是Looper取出消息交给Handler处理的,关于Looper就会有下面的问题,

Looper对象是怎么获取的 ?

查看Looper的构造器,里面保存了一个MessageQueue对象和一个Thread对象,实现了Looper与线程的关联。Looper是线程私有的,每个线程都有一个Looper对象。

并且构造器它是私有的,即Looper对象不能直接创建

 //Looper这个类的对象不能直接创建,必须通过Looper来的两个静态方法prepare()/prepareMainLooper()来间接创建
    private Looper(boolean quitAllowed) {
        //创建MessageQueue对象
        mQueue = new MessageQueue(quitAllowed); //实现了Looper与MessageQueue的关联
        //记录当前线程
        mThread = Thread.currentThread(); //实现了Thread与Looper的关联
    }

那么Looper对象是怎么创建的?查看prepare()方法

public static void prepare() {
        prepare(true);
    }

实际调用的是prepare(true),查看prepare(true)

  /**
     *
     * @param quitAllowed 表示Looper是否允许退出,true表示允许退出,fasle表示不允许退出
     */
    private static void prepare(boolean quitAllowed) {
        //每个线程只允许执行一次该方法,第二次执行的线程的TLS已有数据,则会抛出异常
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //创建Looper对象,并且保存到当前线程的TLS区域
        sThreadLocal.set(new Looper(quitAllowed));
    }

通过源码可以看出,Looper对象的创建就是执行了 sThreadLocal.set(new Looper(quitAllowed))这个语句,所以回到上面可以知道,Looper.prepare()可以创建Looper对象。并且保存在当前线程的TLS区域,确保不同的线程拥有一个不同的Looper对象,

上面分析中确保了不同线程有不同的Looper对象,那么怎么保证同一个线程只能有一个Looper对象?

代码中执行

 if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }

判断当前线程中是否已有Looper对象,如果有,还企图新建Looper对象的话,就会抛出一个"Only one Looper may be created per thread"异常,表示一个线程只能拥有一个Looper对象,这样就可以保证在正常运行的情况下,一个线程只有一个Looper对象。

主线程也是通过这种方法获取Looper对象吗?

Android主线程ActivityThread在启动时,会在入口方法main中通过Looper.prepareMainLooper()来创建主线程的Looper对象以及MessageQueue,这也是我们可以在主线程没使用Looper.prepare而能使用Handler传递消息的原因。

 public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        ...

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();//通过此方法创建Looper对象

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
        ...
    }

ActivityThread通过调用Looper.MainLooper()来创建Looper对象,查看Looper.MainLooper()

  @Deprecated
    public static void prepareMainLooper() {
        //设置不允许退出的Looper
        prepare(false);
        synchronized (Looper.class) {
            //将当前的Looper保存为looper,每个线程只允许执行一次
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper(); //给主线程的looper赋值
        }
    }

标记为废弃的原因:主线程的Looper是由系统自动创建的,无需用户自行调用。

Looper怎么与MessageQueue实现关联?

使用HandlerHandler的构造函数中,Handler里面MessageQueue就是LooperMessageQueueHandler默认采用的是当前线程TLS中的Looper对象,只要执行了Looper.prepare()方法,就可以获取到有效的Looper对象,同时又执行了 mQueue = mLooper.mQueue语句,把当前Handler的中MessageQueue对象mQueue的引用指向了Looper对象mLooperMessageQueue对象mQueue的引用,即当前Hander中,MessageQueue对象就是LooperMessageQueue中的对象。

    public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            //如果是匿名类、内部类、方法内部类,且没有声明为static,则存在内存泄漏风险,要打印日志
            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());
            }
        }

        //获取当前looper
        mLooper = Looper.myLooper();
        //如果当前线程没有looper,则说明没有调用looper.prepare(),抛出异常
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        //把Looper的Queue赋值给Handle的Queue
        mQueue = mLooper.mQueue;
        //mCallBack赋值
        mCallback = callback;
        //mAsynchronous赋值
        mAsynchronous = async;
    }
Looper是怎么取出消息的?

在子线程使用Handler发送消息,消息插入主线程的MessageQueue消息队列中,Looper又在消息队列中取出消息,分发给主线程处理。在这个过程中,主线程里面的MessageQueueLooper是怎么实现关联的?看看Looperlooper方法

public static void loop() {
         //第1步
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //第2步
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

         //第3步
        for (;;) {
            //第四步
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                // 第5步
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
            // 第6步
            msg.recycleUnchecked();
        }
    }

looper方法内的具体操作有:

第1步 获取Looper对象

第2步 获取MessageQueue消息队列对象

第3步 while()死循环遍历

第4步 通过queue.next()来从MessageQueue的消息队列中获取一个Message msg对象

第5步 通过msg.target. dispatchMessage(msg)来处理消息

第6步 通过msg.recycleUnchecked()方来回收Message到消息对象池中

分析第四步,进入MessageQueuenext方法,这是整个handler机制的核心方法

Message next() {
        // 如果消息循环已经退出了。则直接在这里return。因为调用disposed()方法后mPtr=0
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        //记录空闲时处理的IdlerHandler的数量
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // native层用到的变量 ,如果消息尚未到达处理时间,则表示为距离该消息处理事件的总时长,
        // 表明Native Looper只需要block到消息需要处理的时间就行了。 所以nextPollTimeoutMillis>0表示还有消息待处理
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                //刷新下Binder命令,一般在阻塞前调用
                Binder.flushPendingCommands();
            }
            // 调用native层进行消息标示,nextPollTimeoutMillis 为0立即返回,为-1则阻塞等待。
            nativePollOnce(ptr, nextPollTimeoutMillis);
            //加上同步锁
            synchronized (this) {
                // 获取开机到现在的时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                // 获取MessageQueue的链表表头的第一个元素
                Message msg = mMessages;
                 // 判断Message是否是障栅,如果是则执行循环,拦截所有同步消息,直到取到第一个异步消息为止
                if (msg != null && msg.target == null) {
                     // 如果能进入这个if,则表面MessageQueue的第一个元素就是障栅(barrier)
                    // 循环遍历出第一个异步消息,这段代码可以看出障栅会拦截所有同步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                       //如果msg==null或者msg是异步消息则退出循环,msg==null则意味着已经循环结束
                    } while (msg != null && !msg.isAsynchronous());
                }
                 // 判断是否有可执行的Message
                if (msg != null) {  
                    // 判断该Mesage是否到了被执行的时间。
                    if (now < msg.when) {
                        // 当Message还没有到被执行时间的时候,记录下一次要执行的Message的时间点
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Message的被执行时间已到
                        // 从队列中取出该Message,并重新构建原来队列的链接
                        // 此时说明说有消息,所以不能阻塞
                        mBlocked = false;
                        // 如果还有上一个元素
                        if (prevMsg != null) {
                            //上一个元素的next(越过自己)直接指向下一个元素
                            prevMsg.next = msg.next;
                        } else {
                           //如果没有上一个元素,则说明是消息队列中的头元素,直接让第二个元素变成头元素
                            mMessages = msg.next;
                        }
                        // 因为要取出msg,所以msg的next不能指向链表的任何元素,所以next要置为null
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        // 标记该Message为正处于使用状态,然后返回Message
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 没有任何可执行的Message,重置时间
                    nextPollTimeoutMillis = -1;
                }
                // 关闭消息队列,返回null,通知Looper停止循环
                if (mQuitting) {
                    dispose();
                    return null;
                }
                // 当第一次循环的时候才会在空闲的时候去执行IdleHandler,从代码可以看出所谓的空闲状态
                // 指的就是当队列中没有任何可执行的Message,这里的可执行有两要求,
                // 即该Message不会被障栅拦截,且Message.when到达了执行时间点
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }

                // 这里是消息队列阻塞( 死循环) 的重点,消息队列在阻塞的标示是消息队列中没有任何消息,
                // 并且所有的 IdleHandler 都已经执行过一次了
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                // 初始化要被执行的IdleHandler,最少4个
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            // 开始循环执行所有的IdleHandler,并且根据返回值判断是否保留IdleHandler
            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);
                    }
                }
            }
            // 重点代码,IdleHandler只会在消息队列阻塞之前执行一次,执行之后改标示设置为0,
            // 之后就不会再执行,一直到下一次调用MessageQueue.next() 方法。
            pendingIdleHandlerCount = 0;

            // 当执行了IdleHandler 的 处理之后,会消耗一段时间,这时候消息队列里的可能有消息已经到达 
            // 可执行时间,所以重置该变量回去重新检查消息队列。
            nextPollTimeoutMillis = 0;
        }
    }

MessageQueuenext中,执行各种语句的最终目的都是返回一个Message对象,这个Message可能为空

接下来分析一下,

  • MessageQueue会先判断队列中是否有障栅存在,如果有,只会返回异步消息(同步屏障),如果没有,就逐个返回
  • MessageQueue没有任何消息可以处理的时候,它会进入阻塞状态等待消息到来(无限循环),在阻塞之前它会执行一遍IdleHandler,阻塞其实就是不断循环查看是否有先消息进入队列
  • MessageQueue被关闭的时候,成员变量mQuitting会被标记为true,然后Looper尝试从消息队列中取出Message的时候返回null,而Message==null就是通知Looper消息队列已经关闭,应该停止循环。
  • Handler里面有两个无限循环体LooperMessageQueue,真正阻塞的地方是MessageQuuenext方法

障栅:障栅指的就是同步屏障,前面在Handler构造器中说过Message分为同步消息和异步消息,障栅实际上也是一个Message对象,target=null是它与其他Message对象的区别,next方法当中,如果设置了障栅,开启同步屏障(MessageQueue#postSyncBarrier())则会优先处理异步消息,其他同步消息需要移除障栅(removeSyncBarrier())才能处理。

IdleHandler:IdleHandler在处理业务逻辑方面和Handler一样,不过它只会在线程空闲的时候才执行业务逻辑的处理,这些业务经常是哪些不是很紧要或者不可预期的,比如GC

Handler是怎么处理消息的?

Handler发送消息 ---> MessageQueue插入消息 ---> Looper接收消息 ---> Handler处理消息

Looper通过MessageQueuequeue.next()拣出消息后,调用msg.target.dispatchMessage**(**msg**)** 把消息分发给对应的Handler,跟到:Handler -> dispatchMessage

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            //当Message存在回调方法,回调msg.callback.run()方法;
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                //当Handler存在Callback成员变量时,回调方法handleMessage();
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //Handler自身的回调方法handleMessage()
            handleMessage(msg);
        }
    }
  • 如果msg.callback 不为空,则执行handleCallback(Message),而handleCallback(Message)的内部最终调用的是message.callback.run();,所以最终是msg.callback.run()
  • 如果msg.callback 为空,且mCallback不为空,则执行mCallback.handleMessage(msg)
  • 如果msg.callback 为空,且mCallback也为空,则执行handleMessage()方法

开头的例子就是调用到handleMessage方法,至此,例子中的消息传递分析完毕。一开始分析了Handler的创建,又分析了Handler发送消息sendMessage方法,再分析了MessageQueueenqueueMessage方法,由enqueueMessage方法分析到了Looper中的looper,期间也分析了MessageQueueLooper对象的获取,最后分析了HandlerdispatchMessage方法。

从消息的发送到消息的处理,经历了Handler-->MessageQueue-->Looper->Handler分析完毕。

小结

Looper在主线程中死循环,为啥不会ANR

Looper通过MessageQueuenext获取消息队列消息当队列为空,会阻塞。

此时主线程也堵塞在这里,好处是:main函数无法退出,APP不会一启动就结束!

主线程阻塞怎么响应用户操作?

application启动时,不止一个main线程,还有其他两个Binder线程:ApplicationThread 和 **ActivityManagerProxy,用来和系统进程进行通信操作,接收系统进程发送的通知。

当系统受到因用户操作产生的通知时,会通过 Binder 方式跨进程通知 ApplicationThread;

它通过Handler机制,往 ActivityThreadMessageQueue 中插入消息,唤醒了主线程;

queue.next() 能拿到消息了,然后 dispatchMessage 完成事件分发;

死循环不会ANR,但是 dispatchMessage 中又可能会ANR!如果你在此执行一些耗时操作,导致这个消息一直没处理完,后面又接收到了很多消息,堆积太多,就会引起ANR异常。

Handler为什么会发生内存泄漏,怎么解决?

在Java中,非静态内部类会持有一个外部类的隐式引用,可能会造成外部类无法被GC; 当Handler内部类没有声明为静态时,它会持有Activity的引用从而导致Activity无法正常释放。

而单单使用静态内部类,Handler就不能调用Activity里的非静态方法了,所以加上「弱引用」持有外部Activity。

还有一种情况可能会引起内存泄漏:延时消息,Activity关闭消息还没处理完,可以在Activity的onDestroy()中调用:handler.removeCallbacksAndMessages(null) 移除Message/Runnable

Hanlder怎么处理加急信息,类型UI绘制等消息?

使用同步屏障机制,同步屏障机制可以优先处理异步消息。在MessageQueuenext代码中,会优先判断是否设置了同步屏障,如果设置了同步屏障(通过MessageQueuepostSyncBarrier 函数来开启同步屏障)会不断循环消息队列直到找到异步消息并进行处理,类似UI绘制这种需要快速响应的消息,系统会优先执行。在同步屏障没移除前,只会处理异步消息,处理完所有的异步消息后,就会处于堵塞。如果想恢复处理同步消息,需要调用 removeSyncBarrier() 移除同步屏障。

子线程维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?

主线程无消息的时候,休眠阻塞 子线程中必须要调用Looper.quit()退出 loop是一个死循环的操作,要让loop退出,需要msg==null,这个时候就需要返回一个空的消息 quit()的作用 唤醒线程 将mquitting=true,让messagequeuenue==ll,退出loop()

发消息时各个Handler可能处于不同线程,那么它内部是如何保证线程安全的?取消息呢?

加锁 enqueueMessage方法中使用 synchronized(this) 同步锁(内置锁),锁定了该对象

线程是如何进行切换的?

调用Looperloop()方法无限循环查询MessageQueue队列是否有消息保存了。有消息就取出来调用dispatchMessage()方法处理。 这个方法最终调用了我们自己重写了消息处理方法handleMessage(msg);这样就完成消息从子线程到主线程的切换

Handler用了什么设计模式?用它的好处是什么?

生产者-消费者模式 内存共享的设计 好处:可以保证数据生产消费的顺序(MessageQueue先进先出)不管是生产者(子线程)还是消费者(主线程)都只依赖缓冲区(Handler),不会相互持有,没有任何耦合

最后

我这里总结了一些BAT大厂关于Handler 的超过 100+ 高频面试题,现已经整理成了高清的PDF学习文档,需要的朋友可以直接去我 GitHub地址:https://github.com/733gh/Android-T3 中查阅;基本涵盖了各个角度,大家可以拿来自测一下。在面试前也可以刷一刷,毕竟 Handler 面试题虽高频出现,但是遇到还是不用慌张的。

相关文章

网友评论

    本文标题:闲聊Android中的Handler机制

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