决定再写一次有关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) {
            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();
    }

先取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)方法来处理我们的消息。

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

  1. Android AsyncTask完全解析,带你从源码的角度彻底理解

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11711405 我们都知道,Android UI是线程不安全的,如果想要在子线程里进 ...

  2. [转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分 ...

  3. [学习总结]7、Android AsyncTask完全解析,带你从源码的角度彻底理解

    我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制.之前我也写过了一篇文章从源码层面分析了Android的异步消息处理机制,感兴 ...

  4. 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...

  5. android的消息处理机制(图文+源码分析)—Looper/Handler/Message[转]

    from:http://www.jb51.net/article/33514.htm 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想.andro ...

  6. Android事件分发机制完全解析,带你从源码的角度彻底理解

    Android事件构成 在Android中,事件主要包括点按.长按.拖拽.滑动等,点按又包括单击和双击,另外还包括单指操作和多指操作.所有这些都构成了Android中的事件响应.总的来说,所有的事件都 ...

  7. android向web提交参数的4种方式总结,附带网站案例源码

    第一种:基于http协议通过get方式提交参数 1.对多个参数的封装 public static String get_save(String name, String phone) { /** * ...

  8. Android ListView工作原理完全解析,带你从源码的角度彻底理解

    版权声明:本文出自郭霖的博客,转载必须注明出处.   目录(?)[+] Adapter的作用 RecycleBin机制 第一次Layout 第二次Layout 滑动加载更多数据   转载请注明出处:h ...

  9. Android异步消息处理机制完全解析,带你从源码的角度彻底理解(转)

    开始进入正题,我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃.相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一 ...

随机推荐

  1. markdown 【demo】

    第一次开始 用markdown 编辑器 public class{ public static void main (String[] agrs){ System.out.println(" ...

  2. (十) 编写UVC程序

    目录 编写UVC程序 流程简述 11个ioctl函数 查询属性 VIDIOC_QUERYCAP 枚举格式 VIDIOC_ENUM_FMT 查询当前格式 VIDIOC_G_FMT 尝试某种格式 VIDI ...

  3. 2018-2019-2 《Java程序设计》第8周学习总结

    20175319 2018-2019-2 <Java程序设计>第8周学习总结 教材学习内容总结 本周学习<Java程序设计>第十五章: 泛型: 泛型(Generics)的主要目 ...

  4. HBase基本概念与基本使用

    1. HBase简介 1.1 什么是HBase HBASE是一个高可靠性.高性能.面向列.可伸缩的分布式存储系统,利用HBASE技术可在廉价PC Server上搭建起大规模结构化存储集群. HBASE ...

  5. Python学习第二节——基础知识

    # !/usr/bin/edv python    脚本语言的第一行,目的就是指出,你想要你的这个文件中的代码用什么可执行程序去运行它.# -*- coding:UTF-8 -*-    标明编码注释 ...

  6. 第30月第13天 supportedInterfaceOrientationsForWindow旋转

    1. 对于做视频横屏播放的情况下:做旋转有3种方法. 第一种:就是网上说的用旋转矩阵方法CGAffineTransformMakeRotation来做,直接旋转某个view,之后setFrame,至于 ...

  7. 一、下载安装superset

    1.环境介绍: 操作系统:Windows 10 python版本:3.73 2.创建虚拟环境: 打开命令行窗口,使用安装python自带的pip命令,下载pinenv 虚拟环境工具, pip inst ...

  8. windows powershell上批量修改文件名称

    $i = Get-ChildItem -Path c:\pictures -Filter *.jpg | ForEach-Object { $extension = $_.Extension $new ...

  9. android shape 圆圈 圆环 圆角

    定义圆圈:比如角标: xml布局文件 <TextView android:id="@+id/item_order_pay_count" android:layout_widt ...

  10. JAVA实例

     JAVA实例1  1 package Demo3; import java.io.File; import java.io.FileReader; import java.io.IOExceptio ...