这一次,我们用最详细的方式解析Android消息机制的源码

Handler源码解析

一、创建Handler对象

使用handler最简单的方式:直接new一个Handler的对象

Handler handler = new Handler();

所以我们来看看它的构造函数的源码:

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

这段代码做了几件事:

1、校验是否可能内存泄漏
2、初始化一个Looper mLooper
3、初始化一个MessageQueue mQueue

我们一件事一件事的看:

1、校验是否存在内存泄漏

Handler的构造函数中首先判断了FIND_POTENTIAL_LEAKS的值,为true时,会获取该对象的运行时类,如果是匿名类,成员类,局部类的时候判断修饰符是否为static,不是则提示可能会造成内存泄漏。

问:为什么匿名类,成员类,局部类的修饰符不是static的时候可能会导致内存泄漏呢?

答:因为,匿名类,成员类,局部类都是内部类,内部类持有外部类的引用,如果Activity销毁了,而Hanlder的任务还没有完成,那么Handler就会持有activity的引用,导致activity无法回收,则导致内存泄漏;静态内部类是外部类的一个静态成员,它不持有内部类的引用,故不会造成内存泄漏

这里我们可以思考为什么非静态类持有外部类的引用?为什么静态类不持有外部类的引用?

问:使用Handler如何避免内存泄漏呢?
答:使用静态内部类的方式

2、初始化初始化一个Looper mLooper

这里获得一个mLooper,如果为空则跑出异常:

"Can‘t create handler inside thread that has not called Looper.prepare() "

如果没有调用Looper.prepare()则不能再线程里创建handler!我们都知道,如果我们在UI线程创建handler,是不需要调用这个方法的,但是如果在其他线程创建handler的时候,则需要调用这个方法。那这个方法到底做了什么呢?我们去看看代码:

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

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

先取sThreadLocal.get()的值,结果判断不为空,则跑出异常“一个线程里只能创建一个Looper”,所以sThreadLocal里存的是Looper;如果结果为空,则创建一个Looper。那我们再看看,myLooper()这个方法的代码:

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

总上我们得出一个结论:当我们在UI线程创建Handler的时候,sThreadLocal里已经存了一个Looper对象,所以有个疑问:
当我们在UI线程中创建Handler的时候sThreadLocal里的Looper从哪里来的?
我们知道,我们获取主线程的Looper需要调用getMainLooper()方法,代码如下:

public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

所以我们跟踪一下这个变量的赋值,发现在方法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),这个方法我们刚才已经看了,是创建一个Looper对象,然后存到sThreadLocal中;\
  • 然后判断sMainLooper是否为空,空则抛出异常
  • sMainLooper不为空,则sMainLooper = myLooper()
    至此sMainLooper对象赋值成功,所以,我们需要知道prepareMainLooper()这个方法在哪调用的,跟一下代码,就发现在ActivityThread的main方法中调用了Looper.prepareMainLooper();。现在真相大白:
    当我们在UI线程中创建Handler的时候sThreadLocal里的Looper是在ActivityThread的main函数中调用了prepareMainLooper()方法时初始化的
    ActivityThread是一个在一个应用进程中负责管理Android主线程的执行,包括活动,广播,和其他操作的类

3、初始化一个MessageQueue mQueue

从代码里我们看出这里直接调用了:mLooper.mQueue来获取这个对象,那这个对象可能在Looper初始化的时候就产生了。我们去看看Looper的初始化代码:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

代码很简单,就是创建了MessageQueue的对象,并获得了当前的线程。

至此,Handler的创建已经完成了,本质上就是获得一个Looper对象和一个MessageQueue对象!

二、使用Handler发送消息

Handler的发送消息的方式有很多,我们跟踪一个方法sendMessage方法一直下去,发现最后竟然调用了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);
    }

这段代码做了三件事:

1、给msg.target赋值,也就是Handler对象
2、给消息设置是否是异步消息。
3、调用MessageQueue 的enqueueMessage(msg, uptimeMillis)方法
我们只关注第三步:这一步把Handler的发送消息转给了MessageQueue的添加消息的方法。
所以至此,Handler发送消息的任务也已经完成了,本质上就是调用MessageQueue自己的添加消息的方法!

三、MessageQueue添加消息

MessageQueue的构造函数代码如下:

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

也没做什么特别的事情。我们去看看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;
    }

代码很长,但是通过观察这段代码我们发现这个MessageQueue实际上是个链表,添加消息的过程实际上是一个单链表的插入过程。

所以我们知道了Handler发送消息的本质其实是把消息添加到MessageQueue中,而MessageQueue其实是一个单链表,添加消息的本质是单链表的插入

四、从消息队列里取出消息

我们已经知道消息如何存储的了,我们还需要知道消息是如何取出的。
所以我们要看一下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);
            }

        }
    }

代码太长我删了部分代码。可以看出这个方法主要的功能是很简单的。

  • 获取Looper对象,如果为空,抛异常。
  • 获取消息队列MessageQueue queue
  • 遍历循环从消息队列里取出消息,当消息为空时,循环结束,消息不为空时,分发出去!
    但是实际上当没有消息的时候queue.next()方法会被阻塞,并标记mBlocked为true,并不会立刻返回null。而这个方法阻塞的原因是nativePollOnce(ptr, nextPollTimeoutMillis);方法阻塞。阻塞就是为了等待有消息的到来。那如果在有消息加入队列,loop()方法是如何继续取消息呢?
    这得看消息加入队列的时候有什么操作,我们去看刚才的enqueueMessage(msg, uptimeMillis)方法,发现
    if (needWake) {
    nativeWake(mPtr);
    }

    当needWake的时候会调用一个本地方法唤醒读取消息。
    所以这里看一下消息分发出去之后做了什么?

    msg.target.dispatchMessage(msg);

    上面讲过这个target其实就是个handler。所以我们取handler里面看一下这个方法代码

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

    代码非常简单,当callback不为空的时候调用callback的handleMessage(msg)方法,当callback为空的时候调用自己的handleMessage(msg)。一般情况下我们不会传入callback,而是直接复写Handler的handleMessage(msg)方法来处理我们的消息。

原文地址:https://blog.51cto.com/14332859/2408647

时间: 2024-07-28 14:53:34

这一次,我们用最详细的方式解析Android消息机制的源码的相关文章

Android进阶:三、这一次,我们用最详细的方式解析Android消息机制的源码

决定再写一次有关Handler的源码 Handler源码解析 一.创建Handler对象 使用handler最简单的方式:直接new一个Handler的对象 Handler handler = new Handler(); 所以我们来看看它的构造函数的源码: public Handler() { this(null, false); } public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { f

Android消息机制(Handler、MessageQueue、Looper)详细介绍

Android的消息机制其实在android的开发过程中指的也就是Handler的运行机制,这也就引出了android中常见的面试问题: 简述Handler.Looper.MessageQueue的含义,以及它们之间的关系 简述Handler的运行机制 说明Handler.Looper以及Message之间的关系 Handler机制为什么这么重要呢? 我们知道android设备作为一台移动设备,不管是内存或者还是它的性能都会受到一定的限制:过多的使用内存会使内存溢出(OOM):另外一方面,大量的

Mifare Classic的详细破解,含理论、实践、源码

Mifare Classic在08年的时候就已经被破解.一直以来都想实操一下,但是很有不务正业的嫌疑. 最近有朋友问mifare的3次握手具体算法,于是google了一番,总结如下: Practical Attacks on the MIFARE Classic 英国伦敦帝国大学的一份paper,61页,2009年.貌似还是我国武大学生.其中详细介绍了破解的方方面面.基本上理论.软件.硬件.实操都可以参照实施了.其列出的参考文献链接也非常丰富,涵盖了本博文列出的所有链接. Dismantling

android源码大放送(实战开发必备),免费安卓demo源码,例子大全文件详细列表

免费安卓demo源码,例子大全文件详细列表 本列表源码永久免费下载地址:http://www.jiandaima.com/blog/android-demo 卷 yunpan 的文件夹 PATH 列表 卷序列号为 0000-73EC E:. │ jiandaima.com文件列表生成.bat │ 例子大全说明.txt │ 本例子永久更新地址~.url │ 目录列表2016.03.10更新.txt │ ├─前台界面 │ ├─3D标签云卡片热门 │ │ Android TagCloudView云标签

ConcurrentHashMap 源码详细分析(JDK1.8)

ConcurrentHashMap 源码详细分析(JDK1.8) 1. 概述 <HashMap 源码详细分析(JDK1.8)>:https://segmentfault.com/a/1190000012926722 Java7 整个 ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全.所以很

GitHub超详细图文攻略 - Git客户端下载安装 GitHub提交修改源码工作流程 Git分支 标签 过滤 Git版本工作流(转载)

最近听同事说他都在使用GitHub,GitHub是程序员的社区,在里面可以学到很多书上学不到的东西,所以最近在准备入手这方面的知识去尝试学习,正好碰到这么详细完整的文章,就转载了,希望对自己和大家有帮助. GitHub操作总结 : 总结看不明白就看下面的详细讲解. GitHub操作流程 : 第一次提交 : 方案一 : 本地创建项目根目录, 然后与远程GitHub关联, 之后的操作一样; -- 初始化Git仓库 :git init ; -- 提交改变到缓存 :git commit -m 'desc

Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 概要  和学习ArrayList一样,接下来呢,我们先对LinkedList有个整体认识,然后再学习它的源码:最后再通过实例来学会使用LinkedList.内容包括:第1部分 LinkedList介绍第2部分 LinkedList数

Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 概要 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayList.先对ArrayLis

【转】Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例

概要 这一章,我们对TreeMap进行学习.我们先对TreeMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeMap.内容包括:第1部分 TreeMap介绍第2部分 TreeMap数据结构第3部分 TreeMap源码解析(基于JDK1.6.0_45)第4部分 TreeMap遍历方式第5部分 TreeMap示例 转载请注明出处:http://www.cnblogs.com/skywang12345/admin/EditPosts.aspx?postid=3310928 第1部