Handler 知识点梳理:HandlerLooper 以及 Message 三者之间的关系

Posted Android每日一讲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler 知识点梳理:HandlerLooper 以及 Message 三者之间的关系相关的知识,希望对你有一定的参考价值。

前言

Handler、Looper以及Message之间的关系,概括性来说,Looper负责的是创建一个MessageQueue对象,然后进入到一个无限循环体中不断取出消息,而这些消息都是由一个或者多个Handler进行创建处理

Messagequeue 的数据结构是什么

基础数据结构中 “先进先出” 的一种数据结构

Handler post 原理

消息是通过 MessageQueen 中的 enqueueMessage()方法加入消息队列中的,并且它在放入中就进行好排序,链表头的延迟时间小,尾部延迟时间最大

  • Looper.loop()通过 MessageQueue 中的 next()取消息
  • next()中如果当前链表头部消息是延迟消息,则根据延迟时间进行消息队列会 阻塞,不返回给 Looper message,知道时间到了,返回给 message
  • 如果在阻塞中有新的消息插入到链表头部则唤醒线程
  • Looper 将新消息交给回调给 handler 中的 handleMessage 后,继续调用 MessageQueen的next()方法,如果刚刚的延迟消息还是时间未到,则计算时间继续阻塞

handler.postDelay()的实现是通过MessageQueue中执行时间顺序排列,消息队列阻塞和唤醒的方式结合实现的;如果真的是通过延迟将消息放入到 MessageQueen中,那放入多个延迟消息就要维护多个定时器

android 消息机制的简介

在 Android 中使用消息机制,我们首先想到的就是 Handler;没错,Handler是 Android 消息机制的上层接口;Handler 的使用过程很简单,通过它可以轻松地将一个任务切换到Handler所在的线程中去执行。通常情况下,Handler的使用场景就是更新UI

在子线程中,进行耗时操作,执行完操作后,发送消息,通知主线程更新 UI;这便是消息机制的典型应用场景。我们通常只会接触到 Handler 和 Message 来完 成消息机制,其实内部还有两大助手来共同完成消息传递

消息机制的模型 消息机制主要包含: MessageQueue,Handler和Looper这三大部分,以及Message,下面我们一一介绍

  • Message: 需要传递的消息,可以传递数据;
  • MessageQueue: 消息队列,但是它的内部实现并不是用的队列,实际上是通过 一个单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优 势。主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
  • Handler: 消息辅助类,主要功能向消息池发送各种消息事件 (Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
  • Looper: 不断循环执行(Looper.loop),从 MessageQueue中读取消息,按分发机制将消息分发给目标处理者

消息机制的架构

消息机制的运行流程: 在子线程执行完耗时操作,当 Handler 发送消息时,将会 调用MessageQueue.enqueueMessage,向消息队列中添加消息

当通过Looper.loop 开启循环后,会不断地从线程池中读取消息 ,即调用 MessageQueue.next,然后 调用目标 Handler(即发送该消息的 Handler)的 dispatchMessage 方法传递消息,然后返回到 Handler 所在线程,目标 Handler 收到消息,调用 handleMessage方法,接收消息,处理消息

MessageQueue,Handler 和 Looper 三者之间的关系: 每个线程中只能存在一个 Looper,Looper 是保存在 ThreadLocal 中的。主线程(UI 线程)已经创建了一个 Looper,所以在主线程中不需要再创建 Looper,但是在其他线程中需要创建 Looper

每个线程中可以有多个 Handler,即一个Looper 可以处理来自多个 Handler的消息 ;Looper 中维护一个MessageQueue,来维护消息队列,消息队列中的 Message可以来自不同的 Handler

  • Looper 有一个 MessageQueue 消息队列
  • MessageQueue 有一组待处理的 Message
  • Message中记录发送和处理消息的 Handler
  • Handler中有 Looper 和 MessageQueue

我们可以使用 Handler 发送并处理与一个线程关联的 Message 和 Runnable ;(注意:Runnable 会被封装进一个 Message,所以它本质上还是一个 Message ) 每个 Handler 都会跟一个线程绑定,并与该线程的 MessageQueue 关联在一起, 从而实现消息的管理以及线程间通信

  • Handler 的背后有着 Looper 以及 MessageQueue 的协助,三者通力合作,分工 明确;尝试小结一下它们的职责,如下:
  • Looper : 负责关联线程以及消息的分发在该线程下从 MessageQueue 获取 Message,分发给 Handler ;
  • MessageQueue : 是个队列,负责消息的存储与管理,负责管理由 Handler 发送过 来的 Message ;
  • Handler : 负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节
    Handler 发送的消息由 MessageQueue 存储管理,并由 Loopler 负责回调消息 到 handleMessage();线程的转换由 Looper 完成,handleMessage() 所在线程由 Looper.loop() 调用 者所在线程决定

Handler 引起的内存泄露原因以及最佳解决方案

Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露

这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会 持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露

解决该问题的最有效的方法是:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并及时移除所有消息

示例代码如下:

private static class SafeHandler extends Handler 
private WeakReference<HandlerActivity> ref;
public SafeHandler(HandlerActivity activity) 
this.ref = new WeakReference(activity);

@Override
public void handleMessage(final Message msg) 
HandlerActivity activity = ref.get();
if (activity != null) activity.handleMessage(msg);


并且再在 Activity.onDestroy() 前移除消息,加一层保障:

@Overrideprotected void onDestroy() 
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();

这样双重保障,就能完全避免内存泄露了。注意:单纯的在 onDestroy 移除消息并不保险,因为 onDestroy 并不一定执行

    1. Handler 的 背 后 有 Looper 、 MessageQueue 支 撑 , Looper 负 责 消 息 分 发 , MessageQueue 负责消息管理;
    1. 在创建 Handler 之前一定需要先创建 Looper;
    1. Looper 有退出的功能,但是主线程的 Looper 不允许退出;
    1. 异步线程的 Looper 需要自己调用 Looper.myLooper().quit(); 退出;
    1. Runnable 被封装进了 Message,可以说是一个特殊的 Message;
    1. Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,也可以说成 Looper所在的线程,并不是创建Handler的线程;
    1. 使用内部类的方式使用Handler可能会导致内存泄露,即便在 Activity.onDestroy 里移除延时消息,必须要写成静态内部类

Android UI 是线程不安全的,如果在子线程中尝试进行 UI 操作,程序就有可能 会崩溃

相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也 是早已烂熟于心,即创建一个 Message 对象,然后借助 Handler 发送出去,之 后在 Handler 的 handleMessage()方法中获得刚才发送的 Message 对象,然 后在这里进行 UI 操作就不会再出现崩溃了

在主线程中可 以直接创建 Handler 对象,而在子线程中需要先调用 Looper.prepare()才能创 建 Handler 对象

有关 Handler 的知识点就到这里了, 有需要学习更多关于 Handle 知识点的同学,可以 私信 发送 “进阶” 即可 获取一份 学习笔记 ,相信能够帮助大家 查漏补缺

资料部分内容展示如下:

《Handler 机制之 Thread》

  • 线程概念
  • Android线程的实现
  • 线程的阻塞
  • 关于线程上下文切换
  • 关于线程的安全问题
  • 守护线程
  • 线程的内存

《Handler机制之ThreadLocal》

  • Java中的ThreadLocal
  • ThreadLocal的前世今生
  • Android中的ThreadLocal
  • Android 面试中的关于ThreadLocal的问题
  • ThreadLocal的结构
  • ThreadLocal静态类ThreadLocal.Values
  • ThreadLocal的总结

完整版PDF文档获取方式: 私信 发送 “进阶” 即可 免费获取

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们

技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面

Android 架构师之路还很漫长,与君共勉

Android Handler 机制:Handler 运行机制完整梳理

做Android开发的都应该知道Handler的运行机制,这个问题属于老生常谈了。

这里再简单赘述一下:

  1. Handler 负责发送消息;
  2. Looper 负责接收 Handler 发送的消息,并在合适的时间将消息回传给Handler;
  3. MessageQueue是一个存储消息的队列容器。

本文我们会详细完整的将Handler的运行机制梳理一遍。

一、ActivityThread类和APP的启动过程

为什么要讲ActivityThread和App的启动过程,因为Handler、Looper都是在这个阶段进行创建和初始化的。

ActivityThread就是我们常说的主线程或UI线程,ActivityThread的main方法是一个APP的真正入口,MainLooper在它的main方法中被创建。

    //ActivityThread的main方法
    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        //在attach方法中会完成Application对象的初始化,然后调用Application的onCreate()方法
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ...
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

主线程的Handler作为ActivityThread的成员变量,是在ActivityThread的main方法被执行,ActivityThread被创建时进行初始化的。MessageQueue在Looper创建的时候作为成员变量被初始化创建。

二、Handler创建Message并发送给Looper

当我们创建一个Message并交给Handler发送的时候,内部调用的代码如下:

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

 最终调用到的是MessageQueue的enqueueMessage方法,enqueueMessage 是核心处理方法。下面是MessageQueue.enqueueMessage方法的代码:

    boolean enqueueMessage(Message msg, long when) {
        ...synchronized (this) {
            ...
            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;
                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;
    }

这段代码处理的事情就是将进入消息队列的Message插入到合适的位置,并通过needWake判断是否需要调用底层唤醒整个消息队列。

结合上述的代码,我们可以得出整体的逻辑如下所示:

          +-------+     +------------+   +------------------+   +--------------+                        
          |Handler|     |MessageQueue|   |NativeMessageQueue|   |Looper(Native)|                        
          +--+----+     +-----+------+   +---------+--------+   +-------+------+                        
             |                |                    |                    |                               
             |                |                    |                    |                               
sendMessage()|                |                    |                    |                               
+----------> |                |                    |                    |                               
             |                |                    |                    |                               
             |enqueueMessage()|                    |                    |                               
             +--------------> |                    |                    |                               
             |                |                    |                    |                               
             |                |                    |                    |                                             
             |                |  nativeWake()      |                    |                               
             |                |    wake()          |                    |                               
             |                +------------------> |                    |                               
             |                |                    |                    |                               
             |                |                    |    wake()          |                               
             |                |                    +------------------> |                               
             |                |                    |                    |                               
             |                |                    |                    |                               
             |                |                    |                    |write(mWakeWritePipeFd, "W", 1)
             |                |                    |                    |                                 
             |                |                    |                    |                                                             
             +                +                    +                    +                               

三、Looper循环处理MessageQueue的Message

我们知道Loop循环处理Message调用的方法是 Looper.loop()。

而Looper.loop执行的代码:

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        ...
        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);
            }
            ...
        }
    }

下面是MessageQueue的next方法的代码:

    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 (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

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

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

这段代码处理的事情就是不断从MessageQueue中取出消息,如果没有消息的时候会给nativePollOnce的nextPollTimeoutMillis设置为-1,这时消息队列就处于阻塞状态了。

结合上述代码,可以得出逻辑如下图所示:

          +------+    +------------+  +------------------+  +--------------+                    
          |Looper|    |MessageQueue|  |NativeMessageQueue|  |Looper(Native)|                    
          +--+---+    +------+-----+  +---------+--------+  +-------+------+                    
             |               |                  |                   |                                                 
+-------------------------------------------------------------------------------+               
|[msg loop]  |   next()      |                  |                   |           |               
|            +------------>  |                  |                   |           |               
|            |               |                  |                   |           |               
|            |               |                  |                   |           |               
|            |               | nativePollOnce() |                   |           |               
|            |               |    pollOnce()    |                   |           |               
|            |               +----------------> |                   |           |               
|            |               |                  |                   |           |                  
|            |               |                  |                   |           |               
|            |               |                  |                   |           |               
|            |               |                  |     pollOnce()    |           |               
|            |               |                  +-----------------> |           |               
|            |               |                  |                   |           |               
|            |               |                  |                   | epoll_wait()              
|            |               |                  |                   +--------+  |               
|            |               |                  |                   |        |  |               
|            |               |                  |                   |        |  |               
|            |               |                  |                   | <------+  |               
|            |               |                  |                   | awoken()  |               
|            +               +                  +                   +           |                 
+-------------------------------------------------------------------------------+               

四、总结

1. 相关知识点

HandlerThread、ThreadLocal、Linux Epoll 机制。

HandlerThread:

HandlerThread相比Thread最大的优势在于引入MessageQueue概念,可以进行多任务队列管理。HandlerThread背后只有一个线程,所以任务是串行依次执行的。串行相对于并行来说更安全,各任务之间不会存在多线程安全问题。HandlerThread所产生的线程会一直存活,Looper会在该线程中持续的检查MessageQueue,并开启消息处理的循环。这一点和Thread(),AsyncTask都不同,thread实例的重用可以避免线程相关的对象的频繁重建和销毁。 getLooper().quit();来退出这个线程,其实原理很简单,就是改变在消息循环里面标志位,退出整个while循环,使线程执行完毕。

注意:要想更新界面内容,还是需要使用主线程的Looper,不然的话还是会抛错误。

 

2. 推荐文章:

1. Android Handler机制 - MessageQueue如何处理消息:https://blog.csdn.net/lovelease/article/details/81988696

2. ActivityThread的理解和APP的启动过程:https://blog.csdn.net/hzwailll/article/details/85339714

 

以上是关于Handler 知识点梳理:HandlerLooper 以及 Message 三者之间的关系的主要内容,如果未能解决你的问题,请参考以下文章

Android Handler 机制:Handler 运行机制完整梳理

Android Handler 机制:Handler 运行机制完整梳理

Android7_安卓的知识体系梳理

Handler相关梳理

从Handler.post(Runnable r) ,Handler.sendEmptyMessage()梳理Android的消息机制(以及handler的内存泄露)

Android Handler 机制:Hander 机制深入探究问题梳理