Android开发Android技术知识Android开发经验谈

带着问题学习 Android Handler 消息机制

2019-02-27  本文已影响14人  Android开发架构

一、提出问题

面试时常被问到的问题:

这俩问题其实是一个问题,其实只要搞清楚了 Handler,Looper,MessageQueue,Message 的作用和联系,就理解了 Android 的 Handler 消息机制。那么再具体一点:

免费获取安卓开发架构的资料(包括Fultter、高级UI、性能优化、架构师课程、 NDK、Kotlin、混合式开发(ReactNative+Weex)和一线互联网公司关于android面试的题目汇总可以加:936332305 / 链接:点击链接加入【安卓开发架构】:https://jq.qq.com/?_wv=1027&k=515xp64

在这里插入图片描述

二、解决问题

那么,我们从主线程的消息机制开始分析:

2.1 主线程 Looper 的创建和循环

Android 应用程序的入口是 main 函数,主线程 Looper 的创建也是在这里完成的。

ActivityThread --> main() 函数

    public static void main(){
            // step1: 创建主线程Looper对象
            Looper.prepareMainLooper();
    
            ActivityThread thread = new ActivityThread();
            // 绑定应用进程,布尔标记是否为系统进程
            thread.attach(false);
            // 实例化主线程 Handler
            if(sMainThreadHandler == null){
               sMainThreadHandler = thread.getHandler();
            }
            // step2: 开始循环
            Loop.loop();
    
            throw new RuntimeException("Main thread loop unexpectedly exited");
    }

Looper.prepareMainLooper()用来创建主线程的 Looper 对象,接下来先看这个方法的实现。

2.1.1 创建主线程 Looper

Looper --> prepareMainLooper()

    private static Looper sMainLooper;  // guarded by Looper.class
    
    public static void prepareMainLooper(){
            // step1: 调用本类 prepare 方法
            prepare(false);
            // 线程同步,如果变量 sMainLooper 不为空抛出主线程 Looper 已经创建
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                // step2: 调用本类 myLooper 方法
                sMainLooper = myLooper();
            }
    }

prepareMainLooper() 方法主要是使用 prepare(false) 创建当前线程的 Looper 对象,再使用 myLooper() 方法来获取当前线程的 Looper 对象。

step1: Looper --> prepare()

    // ThreadLocal 为每个线程保存单独的变量
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    // Looper 类的 MessageQueue 变量
    final MessageQueue mQueue;
    // quitAllowed 是否允许退出,这里是主线程的 Looper 不可退出
    private static void prepare(boolean quitAllowed) {
            // 首先判定 Looper 是否存在
            if(sThreadLocal.get() != null){
                    throw new RuntimeException("Only one Looper may be created per thread");
            }
            // 保存线程的副本变量
            sThreadLoacal.set(new Looper(quitAllowed));
    }
    
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

更多关于 ThreadLocal 的原理:

深入剖析ThreadLocal实现原理以及内存泄漏问题

问题1:为什么在主线程可以直接使用 Handler?
因为主线程已经创建了 Looper 对象并开启了消息循环,通过上文的代码就可以看出来。

问题2:Looper 对象是如何绑定 MessageQueue 的?或者说 Looper 对象创建 MessageQueue 过程。
很简单,Looper 有个一成员变量 mQueue,它就是 Looper 对象默认保存的 MessageQueue。上面代码中 Looper 有一个构造器,新建 Looper 对象时会直接创建 MessageQueue 并赋值给 mQueue。
问题2解决:在 new Looper 时就创建了 MessageQueue 对象并赋值给 Looper 的成员变量 mQueue。

step2: Looper --> myLooper()

    // 也就是使用本类的ThreadLocal对象获取之前创建保存的Looper对象
    public static @Nullable Looper myLooper() {
         return sThreadLocal.get();
    }

这个方法就是通过 sThreadLocal 变量获取当前线程的 Looper 对象,比较常用的一个方法。上文主线程 Looper 对象创建后使用该方法获取了 Looper 对象。

2.1.2 开始循环处理消息

回到最开始的 main() 函数,在创建了 Looper 对象以后就调用了 Looper.loop() 来循环处理消息,贴一下大致代码:

    public static void main(){
            // step1: 创建主线程Looper对象
            Looper.prepareMainLooper();
            ...
            // step2: 开始循环
            Loop.loop();
    }

Looper --> loop()

    public static void loop() {
        // step1: 获取当前线程的 Looper 对象
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // step2: 获取 Looper 保存的 MessageQueue 对象
        final MessageQueue queue = me.mQueue;
    
        ...
        // step3: 循环读取消息,如果有则调用消息对象中储存的 handler 进行发送
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            try {
                // step4: 使用 Message 对象保存的 handler 对象处理消息
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
            msg.recycleUnchecked();
        }
    }

Android消息机制1-Handler(Java层)

获取到下一条消息,如果 MessageQueue 中没有消息,就会进行阻塞。那么如果存在消息,它又是怎么放入 MessageQueue 的呢?或者说MessageQueue 里的消息从哪里来?Handler是如何往MessageQueue中插入消息的? 先不说这个,把这个问题叫作问题3后面分析。

Handler --> dispatchMessage

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

可以看到该方法最后执行了 handleMessage() 方法,这是一个空方法也就是需要我们覆写并实现的。另外 dispatchMessage() 也体现出一个问题:

消息分发的优先级:

到这里 Looper 循环并通过 Handler 发送消息有一个整体的流程了,接下来分析 Handler 在消息机制中的主要作用以及和 Looper、Message 的关系。

2.2 Handler 的创建和作用

上面说到 loop() 方法在不断从消息队列 MessageQueue 中取出消息(queue.next() 方法),如果没有消息则阻塞,反之交给 Message 绑定的 Handler 处理。回顾一下没解决的两个问题:

- 问题3:MessageQueue 里的消息从哪里来?Handler 是如何往 MessageQueue 中插入消息的?
- 问题4:msg.target是何时被赋值的?,也就是说Message 是如何绑定 Handler 的? 既然要解决
Handler 插入消息的问题,就要看 Handler 发送消息的过程。

2.2.1 Handler 发送消息

Handler --> sendMessage(Message msg);

    final MessageQueue mQueue;
    
    public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
    }
    // 发送延时消息
    public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    // 指定时间发送消息
    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);
    }
    // 处理消息,赋值 Message 对象的 target,消息队列插入消息
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以看到调用 sendMessage(Message msg) 方法最终会调用到 enqueueMessage() 方法,这个方法主要有两个作用:赋值 Message 对象的 target、消息队列插入消息。

Android消息机制1-Handler(Java层)

要了解 Handler 的 MessageQueue 对象是怎么赋值的就要看 Handler 的构造函数了,Handler 创建的时候作了一些列操作比如获取当前线程的 Looper,绑定 MessageQueue 对象等。

2.2.2 Handler 的创建

下面是 Handler 无参构造器和主要的构造器,另外几个重载的构造器有些是通过传递不同参数调用包含两个参数的构造器。两个参数构造函数第一个参数为 callback 回调,第二个函数用来标记消息是否异步。

    // 无参构造器
    public Handler() {
         this(null, false);
    }
    
    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &amp;&amp;
                    (klass.getModifiers() &amp; Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                        klass.getCanonicalName());
            }
        }
        // step1:获取当前线程 Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
        }
        // step2:获取 Looper 对象绑定的 MessageQueue 对象并赋值给 Handler 的 mQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

step1:调用myLooper() 方法,该方法是使用 sThreadLocal 对象获取当前线程的 Looper 对象,回顾一下:

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

如果获取的 Looper 对象为 null,说明没有执行 Looper.prepare() 为当前线程保存 Looper 变量,就会抛出 RuntimeException。这里又说明了Handler 必须在有 Looper 的线程中使用,报错不说,没有 Looper 就无法绑定 MessageQueue 对象也就无法进行更多有关消息的操作。

既然说到了 Handler 的构造器,就想到一个问题:问题 6:关于 handler,在任何地方 new handler 都是什么线程下?这个问题要分是否传递 Looper 对象来看。

不传递 Looper 创建 Handler:Handler handler = new Handler();上文就是 Handler 无参创建的源码,可以看到是通过 Looper.myLooper() 来获取 Looper 对象,也就是说对于不传递 Looper 对象的情况下,在哪个线程创建 Handler 默认获取的就是该线程的 Looper 对象,那么 Handler 的一系列操作都是在该线程进行的。

传递 Looper 对象创建 Handler:Handler handler = new Handler(looper);那么看看传入 Looper 的构造函数:

    public Handler(Looper looper) {
        this(looper, null, false);
    }
    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }
    // 第一个参数是 looper 对象,第二个 callback 对象,第三个消息处理方式(是否异步)
    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

可以看出来传递 Looper 对象 Handler 就直接使用了。所以对于传递 Looper 对象创建 Handler 的情况下,传递的 Looper 是哪个线程的,Handler 绑定的就是该线程。

到这里 Looper 和 Handler 就有一个大概的流程了,接下来看一个简单的子线程 Handler 使用例子:

    new Thread() {
        @Override
        public void run() {
            // step1
            Looper.prepare();
             // step2
            Handler handler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    if(msg.what == 1){
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(MainActivity.this,"HandlerTest",Toast.LENGTH_SHORT).show();
                            }
                        });
                         // step5
                        Looper.myLooper().quit();
                    }
                }
            };
             // step3
            handler.sendEmptyMessage(1);
             // step4
            Looper.loop();
        }
    }.start();

三、总结和其它

3.1 Handler、Looper、MessageQueue、Message

3.2 Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

这是知乎上的问题,感觉问的挺有意思。平时可能不太会太深究这些问题,正好有大神回答那就记录一下吧。

 

       public static void main(){
                   
                    Looper.prepareMainLooper();
            
                    //创建ActivityThread对象
                    ActivityThread thread = new ActivityThread();
            
                    //建立Binder通道 (创建新线程)
                    thread.attach(false);
            
                    if(sMainThreadHandler == null){
                       sMainThreadHandler = thread.getHandler();
                    }
                    // step2: 开始循环
                    Loop.loop();
            
                    throw new RuntimeException("Main thread loop unexpectedly exited");
            }

thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程。

其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.nextAndroid消息机制1-Handler(Java层),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资。

    final H mH = new H();
    
    public static void main(){
            ...
            if(sMainThreadHandler == null){
               sMainThreadHandler = thread.getHandler();
            }
            ...
    }
    
    final Handler getHandler() {
        return mH;
    }
   

类 H 继承了 Handler,在主线程创建时就创建了这个 Handler 用于处理 Binder 线程发送来的消息。

Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:

在H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。
比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;
再比如收到msg=H.PAUSE_ACTIVITY,则调用ActivityThread.handlePauseActivity()方法,最终会执行Activity.onPause()等方法。 上述过程,我只挑核心逻辑讲,真正该过程远比这复杂。

3.3 Handler 使用造成内存泄露

免费获取安卓开发架构的资料(包括Fultter、高级UI、性能优化、架构师课程、 NDK、Kotlin、混合式开发(ReactNative+Weex)和一线互联网公司关于android面试的题目汇总可以加:936332305 / 链接:点击链接加入【安卓开发架构】:https://jq.qq.com/?_wv=1027&k=515xp64

在这里插入图片描述
上一篇下一篇

猜你喜欢

热点阅读