Android进阶:用最详细的方式解析Android消息机制的源码

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Handler源码解析 一、创建Handler对象 使用handler最简单的方式:直接new一个Handler的对象 Handler handler = new Handler(); 所以我们来看看它的构造函数的源码: public Handler() { this(null,.

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

相关文章
|
10天前
|
IDE Android开发 iOS开发
深入解析Android与iOS的系统架构及开发环境差异
本文旨在探讨Android和iOS两大主流移动操作系统在系统架构、开发环境和用户体验方面的显著差异。通过对比分析,我们将揭示这两种系统在设计理念、技术实现以及市场策略上的不同路径,帮助开发者更好地理解其特点,从而做出更合适的开发决策。
38 2
|
15天前
|
传感器 C# Android开发
深度解析Uno Platform中的事件处理机制与交互设计艺术:从理论到实践的全方位指南,助您构建响应迅速、交互流畅的跨平台应用
Uno Platform 是一款开源框架,支持使用 C# 和 XAML 开发跨平台原生 UI 应用,兼容 Windows、iOS、Android 及 WebAssembly。本文将介绍 Uno Platform 中高效的事件处理方法,并通过示例代码展示交互设计的核心原则与实践技巧,帮助提升应用的用户体验。事件处理让应用能响应用户输入,如点击、触摸及传感器数据变化。通过 XAML 或 C# 添加事件处理器,可确保及时反馈用户操作。示例代码展示了一个按钮点击事件处理过程。此外,还可运用动画和过渡效果进一步增强应用交互性。
127 57
|
6天前
|
移动开发 Android开发 数据安全/隐私保护
移动应用与系统的技术演进:从开发到操作系统的全景解析随着智能手机和平板电脑的普及,移动应用(App)已成为人们日常生活中不可或缺的一部分。无论是社交、娱乐、购物还是办公,移动应用都扮演着重要的角色。而支撑这些应用运行的,正是功能强大且复杂的移动操作系统。本文将深入探讨移动应用的开发过程及其背后的操作系统机制,揭示这一领域的技术演进。
本文旨在提供关于移动应用与系统技术的全面概述,涵盖移动应用的开发生命周期、主要移动操作系统的特点以及它们之间的竞争关系。我们将探讨如何高效地开发移动应用,并分析iOS和Android两大主流操作系统的技术优势与局限。同时,本文还将讨论跨平台解决方案的兴起及其对移动开发领域的影响。通过这篇技术性文章,读者将获得对移动应用开发及操作系统深层理解的钥匙。
|
13天前
|
编解码 开发工具 UED
QT Widgets模块源码解析与实践
【9月更文挑战第20天】Qt Widgets 模块是 Qt 开发中至关重要的部分,提供了丰富的 GUI 组件,如按钮、文本框等,并支持布局管理、事件处理和窗口管理。这些组件基于信号与槽机制,实现灵活交互。通过对源码的解析及实践应用,可深入了解其类结构、布局管理和事件处理机制,掌握创建复杂 UI 界面的方法,提升开发效率和用户体验。
64 12
|
5天前
|
存储 关系型数据库 MySQL
深入解析MySQL数据存储机制:从表结构到物理存储
深入解析MySQL数据存储机制:从表结构到物理存储
14 1
|
13天前
|
存储 开发框架 数据可视化
深入解析Android应用开发中的四大核心组件
本文将探讨Android开发中的四大核心组件——Activity、Service、BroadcastReceiver和ContentProvider。我们将深入了解每个组件的定义、作用、使用方法及它们之间的交互方式,以帮助开发者更好地理解和应用这些组件,提升Android应用开发的能力和效率。
|
9天前
|
Java 开发者
Java中的异常处理机制深度解析
在Java编程中,异常处理是保证程序稳定性和健壮性的重要手段。本文将深入探讨Java的异常处理机制,包括异常的分类、捕获与处理、自定义异常以及一些最佳实践。通过详细讲解和代码示例,帮助读者更好地理解和应用这一机制,提升代码质量。
12 1
|
15天前
|
缓存 Android开发 开发者
Android RecycleView 深度解析与面试题梳理
本文详细介绍了Android开发中高效且功能强大的`RecyclerView`,包括其架构概览、工作流程及滑动优化机制,并解析了常见的面试题。通过理解`RecyclerView`的核心组件及其优化技巧,帮助开发者提升应用性能并应对技术面试。
40 8
|
16天前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
39 8
|
17天前
|
安全 Java 开发者
Java并发编程中的锁机制解析
本文深入探讨了Java中用于管理多线程同步的关键工具——锁机制。通过分析synchronized关键字和ReentrantLock类等核心概念,揭示了它们在构建线程安全应用中的重要性。同时,文章还讨论了锁机制的高级特性,如公平性、类锁和对象锁的区别,以及锁的优化技术如锁粗化和锁消除。此外,指出了在高并发环境下锁竞争可能导致的问题,并提出了减少锁持有时间和使用无锁编程等策略来优化性能的建议。最后,强调了理解和正确使用Java锁机制对于开发高效、可靠并发应用程序的重要性。
16 3

热门文章

最新文章

推荐镜像

更多
下一篇
无影云桌面