Android Handler消息机制源码解析

九路 等级 336 0 0

好记性不如烂笔头,今天来分析一下Handler的源码实现

Handler机制是Android系统的基础,是多线程之间切换的基础。下面我们分析一下Handler的源码实现。

Handler消息机制有4个类合作完成,分别是Handler,MessageQueue,Looper,Message Handler : 获取消息,发送消息,以及处理消息的类 MessageQueue:消息队列,先进先出 Looper : 消息的循环和分发 Message : 消息实体类,分发消息和处理消息的就是这个类

主要工作原理就是: Looper 类里面有一个无限循环,不停的从MessageQueue队列中取出消息,然后把消息分发给Handler进行处理

先看看在子线程中发消息,去在主线程中更新,我们就在主线程中打印一句话。

第一步: 在MainActivity中有一个属性uiHandler,如下:

    Handler uiHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what == 100){
                Log.d("TAG","我是线程1 msg.what=" + msg.what + " msg.obj=" + msg.obj.toString());
            }else if(msg.what == 200){
                Log.d("TAG","我是线程2 msg.what=" + msg.what + " msg.obj=" + msg.obj.toString());
            }
        }
    };

创建一个Handler实例,重写了handleMessage方法。根据message中what的标识来区别不同线程发来的数据并打印

第二步: 在按钮的点击事件中开2个线程,分别在每个线程中使用 uiHandler获取消息,并发送消息。如下


        findViewById(R.id.tv_hello).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //线程1
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //1 获取消息
                        Message message = uiHandler.obtainMessage();
                        message.what = 100;
                        message.obj = "hello,world";

                        //2 分发消息
                        uiHandler.sendMessage(message);
                    }
                }).start();

                //线程2
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //1 获取消息
                        Message message = uiHandler.obtainMessage();
                        message.what = 200;
                        message.obj = "hello,android";

                        //2 分发消息
                        uiHandler.sendMessage(message);
                    }
                }).start();
            }
        });

使用很简单,两步就完成了从子线程把数据发送到主线程并在主线程中处理 我们来先分析Handler的源码

Handler 的源码分析

Handler的构造函数

   public Handler() {
        this(null, false);
    }

调用了第两个参数的构造函数,如下

 public Handler(Callback callback, boolean async) {
        //FIND_POTENTIAL_LEAKS 为 false, 不走这块
        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 " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

主要是下面几句: mLooper = Looper.myLooper(); 调用Looper的静态方法获取一个Looper 如果 mLooper == null ,就会抛出异常 Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"; 说明我们的线程中如果没有一个looper的话,直接 new Handler() 是会抛出这个异常的。必须首先调用 Looper.prepare(),这个等下讲Looper的源码时就会清楚了。

接下来,把 mLooper中的 mQueue赋值给Handler中的 mQueue,callback是传出来的值,为null 这样我们的Handler里面就保存了一个Looper变量,一个MessageQueue消息队列.

接下来就是 Message message = uiHandler.obtainMessage();

obtainMessage()的源码如下:

  public final Message obtainMessage()
    {
        //注意传的是一个 this, 其实就是 Handler本身
        return Message.obtain(this);
    }

又调用了Message.obtain(this);方法,源码如下:

public static Message obtain(Handler h) {
        //1 调用obtain()获取一个Message实例m
        Message m = obtain();

        //2 关键的这句,把 h 赋值给了消息的 target,这个target肯定也是Handler了
        m.target = h;

        //3 返回 m
        return m;
    }

这样,获取的消息里面就保存了 Handler 的实例。 我们随便看一下 obtain() 方法是如何获取消息的。如下

  public static Message obtain() {
        //sPoolSync同步对象用的
        synchronized (sPoolSync) {
            //sPool是Message类型,静态变量
            if (sPool != null) {
                //就是个单链表,把表头返回,sPool再指向下一个
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }

        //如果sPool为空,则直接 new 一个
        return new Message();
    }

obtain()获取消息就是个享元设计模式,享元设计模式用大白话说就是: 池中有,就从池中返回一个,如果没有,则新创建一个,放入池中,并返回。

使用这种模式可以节省过多的创建对象。复用空闲的对象,节省内存。

最后一句发送消息uiHandler.sendMessage(message);源码如下:

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

sendMessageDelayed(msg, 0) 源码如下

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

又调用了sendMessageAtTime() 源码如下:

   public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        // Handler中的mQueue,就是前面从Looper.get
        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);
    }

调用enqueueMessage()

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //注意这句,如果我们发送的消息不是 uiHandler.obtainMessage()获取的,而是直接 new Message()的,这个时候target为null
        //在这里,又把this 给重新赋值给了target了,保证不管怎么获取的Message,里面的target一定是发送消息的Handler实例
        msg.target = this;

        // mAsynchronous默认为false,不会走这个
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

最后调用queue.enqueueMessage(msg, uptimeMillis)源码如下:

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            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;
                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;
    }

enqueue单词的英文意思就是 排队,入队的意思。所以enqueueMessage()就是把消息进入插入单链表中,上面的源码可以看出,主要是按照时间的顺序把msg插入到由单链表中的第一个位置中,接下来我们就需要从消息队列中取出msg并分了处理了。这时候就调用Looper.loop()方法了。

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

            msg.target.dispatchMessage(msg);


            msg.recycleUnchecked();
        }
    }

可以看到,loop()方法就是在无限循环中不停的从queue中拿出下一个消息 然后调用 msg.target.dispatchMessage(msg) , 上文我们分析过,Message的target保存的就是发送的Handler实例,这我们的这个demo中,就是uiHandler对象。

说白了就是不停的从消息队列中拿出一个消息,然后发分给Handler的dispatchMessage()方法处理。

Handler的dispatchMessage()方法源码如下:

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

可以看到,一个消息分发给dispatchMessage()之后 1 首先看看消息的callback是否为null,如果不为null,就交给消息的handleCallback()方法处理,如果为null

2 再看看Handler自己的mCallback是否为null,如果不为null,就交给mCallback.handleMessage(msg)进行处理,并且如果返回true,消息就不往下分发了,如果返回false

3 就交给Handler的handleMessage()方法进行处理。

有三层拦截,注意,有好多插件化在拦截替换activity的时候,就是通过反射,把自己实例的Handler实例赋值通过hook赋值给了ActivityThread相关的变量中,并且mCallback不为空,返回了false,这样不影响系统正常的流程,也能达到拦截的目的。说多了。

前面分析了handler处理消息的机制,也提到了Looper类的作用,下面我们看看Looper的源码分析

Looper源码分析

我们知道,APP进程的也就是我们应用的入口是ActivityThread.main()函数。 对这块不熟悉的需要自己私下补课了。

ActivityThread.main()的源码同样经过简化,如下:

文件位于 /frameworks/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) {
    //1 创建一个looper
    Looper.prepareMainLooper();

    //2 创建一个ActivityThread实例并调用attach()方法
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    //3 消息循环
    Looper.loop();
}

可以看到,主线程中第一句就是创建一个looper,并调用了Looper.loop()进行消息循环,因为线程只有有了一个looper,才能消息循环,才能不停的从消息队列中取出消息,分发消息,并处理消息。没有消息的时候就阻塞在那,等待消息的到来并处理,这样的我们的app就是通过这种消息驱动的方式运行起来了。

我们来看下 Looper.prepareMainLooper() 的源码,如下

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

第一句,调用了prepare(false) 源码如下:

    private static void prepare(boolean quitAllowed) {
        //1 查看当前线程中是否有looper存在,有就抛个异常
        //这表明,一个线程只能有一个looper存在 
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }

        //2 创建一个Looper并存放在sThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }

sThreadLocal的定义如下 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

是一个静态的变量。整个APP进程中只有一个sThreadLocal,sThreadLocal是线程独有的,每个线程都调用sThreadLocal保存,关于sThreadLocal的原理,其实就是类似HashMap(当然和HashMap是有区别的),也是key,value保存数据,只不过key就是sThreadLocal本身 ,但是映射的数组却是每个线程中独有的,这样就保证了sThreadLocal保存的数据每个线程独有一份,关于ThreadLocal的源码分析,后面几章会讲。

既然Looper和线程有关,那么我们来看下Looper类的定义,源码如下:

/**
  * Class used to run a message loop for a thread.  Threads by default do
  * not have a message loop associated with them; to create one, call
  * {@link #prepare} in the thread that is to run the loop, and then
  * {@link #loop} to have it process messages until the loop is stopped.
  *
  * <p>Most interaction with a message loop is through the
  * {@link Handler} class.
  *
  * <p>This is a typical example of the implementation of a Looper thread,
  * using the separation of {@link #prepare} and {@link #loop} to create an
  * initial Handler to communicate with the Looper.
  *
  * <pre>
  *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }</pre>
  */
public final class Looper {
    .......
}

我们看上面的注释

  * <pre>
  *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }</pre>

这就是经典的Looper的用法 ,可以在一个线程中开始处调用 Looper.prepare(); 然后在最后调用 Looper.loop();进行消息循环,可以把其它线程中的Handler实传进来,这样,一个Looper线程就有了,可以很方便的切换线程了。 下章节我们来自己设计一个Looper线程,做一些后台任务。

Handler的消息机制源码就分析到这了

收藏
评论区

相关推荐

Android Handler消息机制源码解析
好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础。下面我们分析一下Handler的源码实现。 Handler消息机制有4个类合作完成,分别是Handler,MessageQueue,Looper,Message Handler : 获取消息,发送消息,以及处理消息的类 Mes
Android HandlerThread源码解析
在上一章Handler源码解析文章中,我们知道App的主线程通过Handler机制完成了一个线程的消息循环。那么我们自己也可以新建一个线程,在线程里面创建一个Looper,完成消息循环,可以做一些定时的任务或者写日志的功能。这就是HandlerThread的作用 Android Handler消息机制源码解析(https://www.cnblogs.co
4.1 手写Java PriorityQueue 核心源码
本章先讲解优先级队列和二叉堆的结构。下一篇代码实现 从一个需求开始 假设有这样一个需求:在一个子线程中,不停的从一个队列中取出一个任务,执行这个任务,直到这个任务处理完毕,再取出下一个任务,再执行。 其实和 Android 的 Handler 机制中的 Looper 不停的从 MessageQueue 中取出一个消息然后处理是一样的。 不过这个需
vue的watch监听
vue的watch监听函数 watch用来响应数据的变化,watch 的用法大致有以下三种: 1. 监听某个变量 watch: { name(newName, oldName) { console.log(newName) // 改变前的值 console.log(oldName) // 改变后的值
还怕问源码?Github上神级Android三方源码解析手册,已有7.6 KStar
或许对于许多Android开发者来说,所谓的Android工程师的工作“不过就是用XML实现设计师的美术图,用JSON解析服务器的数据,再把数据显示到界面上”就好了,源码什么的,看也好不看也罢,反正应用层的开发用不上,再加上现在优秀的轮子越来越多,拿来主义泛滥,能用就是,反正老板也不关心是不是你自己写的,用我现在老大的话来说,阅读源码似乎只是一种“锦上添花”
Android高频面试题:该怎样在Android面试中聊聊多线程不被忽悠?
多线程可以说是Android面试的高频问题了,而多线程涉及的内容非常多,因此在面试当中往往不知道从何说起,本文并不是为了科普多线程或者研究多线程的知识,而是尝试组织语言
Android开发你必须了解的几个原理
随着互联网的迅速发展,Android技术也是发生很大的变化,要求也是越来高了,在11,12年只要会基本的Android组件,会listview,分享就感觉很牛了,智能手机的发展,及用户普通追求高效率,用户体验的提升,要求开发人员必须会懂实现原理及优化APP程序;不管是面试他人还是被面试目前都经常问到原理性的问题,handler实现原理,activity启动原
Android 内存泄露:详解 Handler 内存泄露的原因与解决方案
前言 在Android开发中,内存泄露 十分常见 1.内存泄露的定义:本该被回收的对象不能被回收而停留在堆内存中 2.内存泄露出现的原因:当一个对象已经不再被使用时,本该被回收但却因为有另外一个正在使用的对象持有它的引用从而导致它不能被回收。这就导致了内存泄漏。 本文将详细讲解内存泄露的其中一种情况:在Handler中发生的内
2021 春招通关 BAT 最新 Android 常用面试真题
前言金三银四招聘旺季已经开启,如果有面试准备的,临阵磨枪,不快也光。如果没面试想法的,也可以看看这些问题以及它们的解析,对提升技术也是很有帮助的。内容涵盖五大部分:Java基础部分、Android部分、数据结构与算法部分、常用的开源库部分、计算机网络认识部分。 Java基础部分内容主要包括操作系统相关、 JDK&JVM&JRE、面向
Gin + Vue全栈开发实战
      尝试地写了第一篇自己学习Go Web框架的感受和入门的文章,发现反响还不错,大家也提出了很多的问题来一起交流。近期也渐渐地出现了很多有关go语言开发的相关文章,包括有在蚂蚁金服的大牛的分享,我也一直有在看博客园和学习,这里越来越多人的去学习
Android 操作系统架构开篇
版权声明: 本站所有博文内容均为原创,转载请务必注明作者与原文链接,且不得篡改原文内容。为便于日常查阅本博客,可通过 方便检索文章 一、引言众所周知,Android是谷歌开发的一款基于Linux的开源操作系统,从诞生至今已有10余年,这一路走来Android遇到哪些问题?大版本升级朝着什么方向演进?Android的未来如何?我的公号 讲解了Android一
每日一题(六)
写在前面 冲冲冲! [ASIS 2019]Unicorn shop进去是一个买独角兽的页面,但是找不到自己有多少钱???前三个独角兽很便宜,就几块钱,肯定不是 flag ,买最贵的独角兽时,回显了一句Only one char(?) allowed!,只能输入一个字符,然而最贵的独角兽要 1337 块回显报错源码中有两句注释因为那句We still have
每日一问(一)Handler相关知识
1、Handler负责发送Message和处理Mesage2、Message就是消息载体,可用what区分,也可传递对象3、Message Queue消息队列,存储Message,先进先出4、Looper循环取出Message Queue里的Message交给Handler处理。5、一个线程只有一个Looper和Message Queue,子线程中使用Han
全靠这份Android知识点PDF大全,月薪30K
第一阶段:Android 基础知识回顾: 回顾Android 开发编程,深入理解Android系统原理和层次结构,深入分析Handler源码和原理; 回顾Java,C/C++,Kotlin、dart 在Android开发中必用的语言,熟悉一下几种语言混淆后的特性; 回顾Android IPC和JNI的底层原理和热更新技术回顾Native开发要点,使用C++结
Vue刷新页面有哪几种方式
在Vue项目中,刷新当前页除了 window.reload(),你还能想到什么办法?而且这种办法会重新加载资源出现短暂的空白页面。体验不是很好。在某个详情页面的时候,我们经常需要通过路由中的详情 id 去获取内容,当我们在不同的详情页来回切换的时候,打开的页面是同一个,只是需要通过监听路由中的参数 id 的变化去重新请求详情接口。如果这个详情页只需要一个接口