Android进阶:三、这一次,我们用最详细的方式解析Android消息机制的源码
来源:Android开发架构     阅读:505
源码超市
发布于 2019-06-11 04:35
查看主页

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

总上我们得出一个结论:当我们在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();        }    }

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

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

但是实际受骗没有消息的时候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工程师朋友们加入安卓开发技术进阶互助:856328774免费提供安卓开发架构的资料(包括Fultter、高级UI、性能优化、架构师课程、 NDK、Kotlin、混合式开发(ReactNative+Weex)和一线互联网公司关于Android面试的题目汇总。
免责声明:本文为用户发表,不代表网站立场,仅供参考,不构成引导等用途。 系统环境 服务器应用
相关推荐
Activiti工作流从入门到入土:工作流简介
Python自制微信机器人:群发消息、自动接收好友
零基础学python:并发服务器、面向连接、多进程、多线程、单进程
白话中台战略-番外篇:从互联网巨头变阵看中台战略
excel 多表合并有很多种方法可以实现,SQL进行多表合并
首页
搜索
订单
购物车
我的