一、消息机制流程简介

在应用启动的时候,会执行程序的入口函数main(),main()里面会创建一个Looper对象,然后通过这个Looper对象开启一个死循环,这个循环的工作是,不断的从消息队列MessageQueue里面取出消息即Message对象,并处理。然后看下面两个问题:
循环拿到一个消息之后,如何处理?
是通过在Looper的循环里调用Handler的dispatchMessage()方法去处理的,而dispatchMessage()方法里面会调用handleMessage()方法,handleMessage()就是平时使用Handler时重写的方法,所以最终如何处理消息由使用Handler的开发者决定。
MessageQueue里的消息从哪来?
使用Handler的开发者通过调用sendMessage()方法将消息加入到MessageQueue里面。

上面就是Android中消息机制的一个整体流程,也是 “Android中Handler,Looper,MessageQueue,Message有什么关系?” 的答案。通过上面的流程可以发现Handler在消息机制中的地位,是作为辅助类或者工具类存在的,用来供开发者使用。

对于这个流程有两个疑问:

  • Looper中是如何能调用到Handler的方法的?
  • Handler是如何能往MessageQueue中插入消息的?

这两个问题会在后面给出答案,下面先来通过源码,分析一下这个过程的具体细节:

二、消息机制的源码分析

首先main()方法位于ActivityThread.java类里面,这是一个隐藏类,源码位置:frameworks/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) {
    ......
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper的创建可以通过Looper.prepare()来完成,上面的代码中prepareMainLooper()是给主线程创建Looper使用的,本质也是调用的prepare()方法。创建Looper以后就可以调用Looper.loop()开启循环了。main方法很简单,不多说了,下面看看Looper被创建的时候做了什么,下面是Looper的prepare()方法和变量sThreadLocal:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
}

很简单,new了一个Looper,并把new出来的Looper保存到ThreadLocal里面。ThreadLocal是什么?它是一个用来存储数据的类,类似HashMap、ArrayList等集合类。它的特点是可以在指定的线程中存储数据,然后取数据只能取到当前线程的数据,比如下面的代码:

ThreadLocal<Integer> mThreadLocal = new ThreadLocal<>();
private void testMethod() {

    mThreadLocal.set(0);
    Log.d(TAG, "main  mThreadLocal=" + mThreadLocal.get());

    new Thread("Thread1") {
        @Override
        public void run() {
            mThreadLocal.set(1);
            Log.d(TAG, "Thread1  mThreadLocal=" + mThreadLocal.get());
        }
    }.start();

    new Thread("Thread2") {
        @Override
        public void run() {
            mThreadLocal.set(2);
            Log.d(TAG, "Thread1  mThreadLocal=" + mThreadLocal.get());
        }
    }.start();

    Log.d(TAG, "main  mThreadLocal=" + mThreadLocal.get());
}

输出的log是

main  mThreadLocal=0
Thread1  mThreadLocal=1
Thread2  mThreadLocal=2
main  mThreadLocal=0

通过上面的例子可以清晰的看到ThreadLocal存取数据的特点,只能取到当前所在线程存的数据,如果所在线程没存数据,取出来的就是null。其实这个效果可以通过HashMap<Thread, Object>来实现,考虑线程安全的话使用ConcurrentMap<Thread, Object>,不过使用Map会有一些麻烦的事要处理,比如当一个线程结束的时候我们如何删除这个线程的对象副本呢?如果使用ThreadLocal就不用有这个担心了,ThreadLocal保证每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。更多ThreadLocal的讲解参考:Android线程管理之ThreadLocal理解及应用场景

好了回到正题,prepare()创建Looper的时候同时把创建的Looper存储到了ThreadLocal中,通过对ThreadLocal的介绍,获取Looper对象就很简单了,sThreadLocal.get()即可,源码提供了一个public的静态方法可以在主线程的任何地方获取这个主线程的Looper(注意一下方法名myLooper(),多个地方会用到):

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

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) {
            // No message indicates that the message queue is quitting.
            return;
        }

        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        msg.recycleUnchecked();
    }
}

上面的代码,首先获取主线程的Looper对象,然后取得Looper中的消息队列final MessageQueue queue = me.mQueue;,然后下面是一个死循环,不断的从消息队列里取消息Message msg = queue.next();,可以看到取出的消息是一个Message对象,如果消息队列里没有消息,就会阻塞在这行代码,等到有消息来的时候会被唤醒。取到消息以后,通过msg.target.dispatchMessage(msg);来处理消息,msg.target 是一个Handler对象,所以这个时候就调用到我们重写的Hander的handleMessage()方法了。
msg.target 是在什么时候被赋值的呢?要找到这个答案很容易,msg.target是被封装在消息里面的,肯定要从发送消息那里开始找,看看Message是如何封装的。那么就从Handler的sendMessage(msg)方法开始,过程如下:

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

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

可以看到最后的enqueueMessage()方法中msg.target = this;,这里就把发送消息的handler封装到了消息中。同时可以看到,发送消息其实就是往MessageQueue里面插入了一条消息,然后Looper里面的循环就可以处理消息了。Handler里面的消息队列是怎么来的呢?从上面的代码可以看到enqueueMessage()里面的queue是从sendMessageAtTime传来的,也就是mQueue。然后看mQueue是在哪初始化的,看Handler的构造方法如下:

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 that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

mQueue的初始化很简单,首先取得Handler所在线程的Looper,然后取出Looper中的mQueue。这也是Handler为什么必须在有Looper的线程中才能使用的原因,拿到mQueue就可以很容易的往Looper的消息队列里插入消息了(配合Looper的循环+阻塞就实现了发送接收消息的效果)。

以上就是主线程中消息机制的原理。

那么,在任何线程下使用handler的如下做法的原因、原理、内部流程等就非常清晰了:

new Thread() {
    @Override
    public void run() {
        Looper.prepare();
        Handler handler = new Handler();
        Looper.loop();
    }
}.start();
  1. 首先Looper.prepare()创建Looper并初始化Looper持有的消息队列MessageQueue,创建好后将Looper保存到ThreadLocal中方便Handler直接获取。
  2. 然后Looper.loop()开启循环,从MessageQueue里面取消息并调用handler的 dispatchMessage(msg) 方法处理消息。如果MessageQueue里没有消息,循环就会阻塞进入休眠状态,等有消息的时候被唤醒处理消息。
  3. 再然后我们new Handler()的时候,Handler构造方法中获取Looper并且拿到Looper的MessageQueue对象。然后Handler内部就可以直接往MessageQueue里面插入消息了,插入消息即发送消息,这时候有消息了就会唤醒Looper循环去处理消息。处理消息就是调用dispatchMessage(msg) 方法,最终调用到我们重写的Handler的handleMessage()方法。

三、通过一些问题的研究加强对消息机制的理解

源码分析完了,下面看一下文章开头的两个问题:

  • Looper中是如何能调用到Handler的方法的?
  • Handler是如何能往MessageQueue中插入消息的?

这两个问题源码分析中已经给出答案,这里做一下总结,首先搞清楚以下对象在消息机制中的关系:

Looper,MessageQueue,Message,ThreadLocal,Handler
  1. Looper对象有一个成员MessageQueue,MessageQueue是一个消息队列,用来存储消息Message
  2. Message消息中带有一个handler对象,所以Looper取出消息后,可以很方便的调用到Handler的方法(问题1解决)
  3. Message是如何带有handler对象的?是handler在发送消息的时候把自己封装到消息里的。
  4. Handler是如何发送消息的?是通过获取Looper对象从而取得Looper里面的MessageQueue,然后Handler就可以直接往MessageQueue里面插入消息了。(问题2解决)
  5. Handler是如何获取Looper对象的?Looper在创建的时候同时把自己保存到ThreadLocal中,并提供一个public的静态方法可以从ThreadLocal中取出Looper,所以Handler的构造方法里可以直接调用静态方法取得Looper对象。

带着上面的一系列问题看源码就很清晰了,下面是知乎上的一个问答:

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

原因很简单,循环里有阻塞,所以死循环并不会一直执行,相反的,大部分时间是没有消息的,所以主线程大多数时候都是处于休眠状态,也就不会消耗太多的CPU资源导致卡死。

  1. 阻塞的原理是使用Linux的管道机制实现的
  2. 主线程没有消息处理时阻塞在管道的读端
  3. binder线程会往主线程消息队列里添加消息,然后往管道写端写一个字节,这样就能唤醒主线程从管道读端返回,也就是说looper循环里queue.next()会调用返回...

这里说到binder线程,具体的实现细节不必深究,考虑下面的问题:
主线程的死循环如何处理其它事务?
首先需要看懂这个问题,主线程进入Looper死循环后,如何处理其他事务,比如activity的各个生命周期的回调函数是如何被执行到的(注意这里是在同一个线程下,代码是按顺序执行的,如果在死循环这阻塞了,那么进入死循环后循环以外的代码是如何执行的)。
首先再看main函数的源码

Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();
thread.attach(false);

if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
}

Looper.loop();

在Looper.prepare和Looper.loop之间new了一个ActivityThread并调用了它的attach方法,这个方法就是开启binder线程的,另外new ActivityThread()的时候同时会初始化它的一个H类型的成员,H是一个继承了Handler的类。此时的结果就是:在主线程开启loop死循环之前,已经启动binder线程,并且准备好了一个名为H的Handler,那么接下来在主线程死循环之外做一些事务处理就很简单了,只需要通过binder线程向H发送消息即可,比如发送 H.LAUNCH_ACTIVITY 消息就是通知主线程调用Activity.onCreate() ,当然不是直接调用,H收到消息后会进行一系列复杂的函数调用最终调用到Activity.onCreate()。
至于谁来控制binder线程来向H发消息就不深入研究了,下面是《Android开发艺术探索》里面的一段话:

ActivityThread 通过 ApplicationThread 和 AMS 进行进程间通讯,AMS 以进程间通信的方式完成 ActivityThread 的请求后会回调 ApplicationThread 中的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 收到消息后会将 ApplicationThread 中的逻辑切换到 ActivityThread 中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。

这个问题就到这里,更多内容看知乎原文

最后

和其他系统相同,Android应用程序也是依靠消息驱动来工作的。网上的这句话还是很有道理的。

文章参考:

《Android开发艺术探索》
Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
Android线程管理之ThreadLocal理解及应用场景
Android 消息机制——你真的了解Handler
Android Handler到底是什么

Android-Handler消息机制实现原理的更多相关文章

  1. Android(java)学习笔记202:Handler消息机制的原理和实现

     联合学习 Android 异步消息处理机制 让你深入理解 Looper.Handler.Message三者关系   1. 首先我们通过一个实例案例来引出一个异常: (1)布局文件activity_m ...

  2. Android(java)学习笔记145:Handler消息机制的原理和实现

     联合学习 Android 异步消息处理机制 让你深入理解 Looper.Handler.Message三者关系   1. 首先我们通过一个实例案例来引出一个异常: (1)布局文件activity_m ...

  3. Android Handler 消息机制原理解析

    前言 做过 Android 开发的童鞋都知道,不能在非主线程修改 UI 控件,因为 Android 规定只能在主线程中访问 UI ,如果在子线程中访问 UI ,那么程序就会抛出异常 android.v ...

  4. Android Handler消息机制源码解析

    好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础.下面我们分析一下Handler的源码实现. Handler消息机制有4个 ...

  5. Android Handler消息机制不完全解析

    1.Handler的作用 Android开发中,我们经常使用Handler进行页面的更新.例如我们需要在一个下载任务完成后,去更新我们的UI效果,因为AndroidUI操作不是线程安全的,也就意味着我 ...

  6. Android Handler消息机制深入浅出

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38408493 作为Android开发者,Handler这个类应该是再熟悉只是了.由于差点儿不 ...

  7. Handler消息机制的一些原理(直接用code讲解)——Android开发

    package com.example.handlertest; import android.os.Bundle; import android.os.Handler; import android ...

  8. Android Native -- Message/Handler/Looper机制(原理篇)

    ⌈Android Native消息队列处理系列文章⌋ Android Native -- Message/Handler/Looper机制(原理篇) Android Native -- Message ...

  9. Android消息传递之Handler消息机制

    前言: 无论是现在所做的项目还是以前的项目中,都会遇见线程之间通信.组件之间通信,目前统一采用EventBus来做处理,在总结学习EventBus之前,觉得还是需要学习总结一下最初的实现方式,也算是不 ...

  10. 【原创】源码角度分析Android的消息机制系列(五)——Looper的工作原理

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. Looper在Android的消息机制中就是用来进行消息循环的.它会不停地循环,去MessageQueue中查看是否有新消息,如果有消息就立刻 ...

随机推荐

  1. Django基础之数据库与ORM

    一.数据库配置 1.django默认支持sqlite,mysql, oracle,postgresql数据库. django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 , 引擎名称 ...

  2. Android ListView用法

    写了一个简单的示例来说明ListView的用法:给定一个单词,下面有四个含义,找出正确的一个,无论是否成功,则会跳转到下一个单词:主要用到的知识有: findViewById().  ListView ...

  3. 拼多多2018校招编程题汇总 Python实现

    题目原址 列表补全 在商城的某个位置有一个商品列表,该列表是由L1.L2两个子列表拼接而成.当用户浏览并翻页时,需要从列表L1.L2中获取商品进行展示.展示规则如下: 用户可以进行多次翻页,用offs ...

  4. RESTful-rest_framework认证组件、权限组件、频率组件-第五篇

    认证组件.权限组件.频率组件总结:  认证组件格式: 1 写一个认证类 from rest_framework.authentication import BaseAuthentication cla ...

  5. c++中读取文件最快的方法

    https://www.byvoid.com/blog/fast-readfile 可以看看了.

  6. 【转】lightmap

    Shader "Diffuse Lightmap" { Properties { _MainTex ("Texture 1", 2D) = "whit ...

  7. UVALive 6609 Minimal Subarray Length(RMQ-ST+二分)

    题意:给定长度为N的数组,求一段连续的元素之和大于等于K,并且让这段元素的长度最小,输出最小长度即可,若不存在这样的元素集合,则输出-1 题目链接:UVAlive 6609 做法:做一个前缀和pref ...

  8. redis学习(六)主从复制

    主从复制:主机更新数据后,根据配置和策略,自动同步到备机的master/slaver机制,master以写为主,slaver以读为主.redis支持主从复制. 下面通过实例来讲解主从复制的四个模式: ...

  9. kubernetes-dashboard

    1.导入kubernetes-dashboard 镜像 [root@node1 DNS]# docker load < kube-dashboard.tar 6bc90c4dba69: Load ...

  10. RSA DSA

    RSA https://blog.csdn.net/sunmenggmail/article/details/11994013 https://baike.baidu.com/item/RSA%E7% ...