一篇文章扒掉“桥梁Handler”的底裤

一篇文章扒掉“桥梁Handler”的底裤
Android跨进程要掌握的是Binder, 而同一进程中最重要的应该就是Handler 消息通信机制了。我这么说,大家不知道是否认同,如果认同,还希望能给一个关注哈。

什么是Handler?

Handler主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的[函数]即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作。

Handler特点

  1. 传递Message。用于接受子线程发送的数据, 并用此数据配合主线程更新UI。 在Android中,对于UI的操作通常需要放在主线程中进行操作。如果在子线程中有关于UI的操作,那么就需要把数据消息作为一个Message对象发送到消息队列中,然后,由Handler中的handlerMessage方法处理传过来的数据信息,并操作UI。当然,Handler对象是在主线程中初始化的,因为它需要绑定在主线程的消息队列中。 类sendMessage(Message msg)方法实现发送消息的操作。 在初始化Handler对象时重写的handleMessage方法来接收Message并进行相关操作。
  2. 传递Runnable对象。用于通过Handler绑定的消息队列,安排不同操作的执行顺序。 Handler对象在进行初始化的时候,会默认的自动绑定消息队列。利用类post方法,可以将Runnable对象发送到消息队列中,按照队列的机制按顺序执行不同的Runnable对象中的run方法。

Handler怎么用?

public class HandlerActivity extends AppCompatActivity {
    private static final String TAG = "HandlerActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        testSendMessage();
    }

    public void testSendMessage() {
         Handler handler = new MyHandler(this);
         Message message = Message.obtain();
         message.obj = "test handler send message";
         handler.sendMessage(message);
    }

    //注1: 为什么要用静态内部???
    static class MyHandler extends Handler {
        WeakReference<appcompatactivity> activityWeakReference; // &#x6CE8;2&#xFF1A;&#x4E3A;&#x4F55;&#x8981;&#x7528;&#x5F31;&#x5F15;&#x7528;&#xFF1F;&#xFF1F;&#xFF1F;
        public MyHandler(AppCompatActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            Log.d(TAG, (String) msg.obj);
        }
    }
}
</appcompatactivity>

Handler源码怎么读?

从使用方式的场景,咱们一步一步的探究里面是怎么实现的,还有上面的标注的两点,在后面我都会介绍的,各位客官听我慢慢道来。首先,看下 &#x56DB;&#x5927;&#x91D1;&#x521A;关系图,文字表述再多,不如一张图来的直接。

一篇文章扒掉“桥梁Handler”的底裤
通过上图就可以简单看出Handler、MessageQueue、Message、Looper 这四者是怎么样互相持有对方的,大概可以了解消息的传递。

下面我们先来一张时序图,看下消息是怎么一步步发送出来的。

一篇文章扒掉“桥梁Handler”的底裤
此刻,应该要开车了。 &#x524D;&#x65B9;&#x9AD8;&#x80FD;&#xFF01;&#xFF01;&#xFF01;
  1. 进入的是Handler.sendMessage 方法
public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}
  1. 接下来继续调用Handler.sendMessageDelayed方法
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
  1. 接着走Handler.sendMessageAtTime 方法,这里面就要用到MessageQueue 对象了,此处说明一下,这个mQueue 是在哪里获取到的,是在Handler 构造方法里。此处贴图,从图中可以看出 mLooper=Looper.myLooper() mQueue=mLooper.mQueue Handler 中的MessageQueue 是Looper 中持有的MessageQueue 对象 。

一篇文章扒掉“桥梁Handler”的底裤

好,这里就说这么多,接着开车:

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    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);
}
  1. 接着时序图上的流程走,此时要进入到MessageQueue.enqueueMessage 方法中,该方法就是将msg 对象存入到MessageQueue 队列中,注意此处,将该handler 对象赋值给了msg.target,这个后面会用到的,很关键。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis); //3&#xFF0C;&#x5373;&#x5C06;&#x8FDB;&#x5165;MessageQueue.enqueueMessage &#x65B9;&#x6CD5;&#x3002;
}
  1. 接着来看MessageQueue.enqueueMessage 方法,该方法就是按照时间的顺序插入到Message 这个链表结构的数据对象中去。
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) { //4. &#x540E;&#x9762;&#x8BF4;&#x660E;&#xFF0C;&#x8FD9;&#x4E2A;&#x4E5F;&#x5C31;&#x662F;&#x56DB;&#x5927;&#x91D1;&#x521A;&#x56FE;&#x91CC;&#x7684;msg.target &#x6240;&#x6301;&#x6709;&#x7684;Handler &#x5BF9;&#x8C61;&#x3002;
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        ...

        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 {
            // &#x94FE;&#x8868;&#x7684;&#x63D2;&#x5165;&#x64CD;&#x4F5C;&#xFF0C;&#x4E0D;&#x592A;&#x719F;&#x6089;&#x7684;&#x53EF;&#x4EE5;&#x770B;&#x770B;&#x6570;&#x636E;&#x7ED3;&#x6784;&#x3002;(&#x6B64;&#x5904;&#x662F;&#x6839;&#x636E;&#x65F6;&#x95F4;&#x6765;&#x6392;&#x5E8F;&#x7684;)
            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); //&#x753B;&#x91CD;&#x70B9;&#xFF0C;&#x6B64;&#x5904;&#x5524;&#x9192;&#x7B49;&#x5F85;&#x7684;next &#x65B9;&#x6CD5;&#x3002;
        }
    }
    return true;
}

此时,一条消息就相当于入队了。 MessageQueue 从名称来看是队列,实际上,使用的还是Message.next 指针来进行操作的,也即是链表的操作。消息的入队完成,后面将会介绍该消息是怎么发送出去的。

  1. Loop.loop&#x65B9;&#x6CD5;,敲重点。省略了部分代码,只关注核心代码。这里用到了死循环,不停的获取Message 对象,获取到之后直接调用Message.target 变量所持有的Handler 对象,然后调用Handler.dispatchMessage 方法,这样就完成了消息的分发。
public static void loop() {
    final Looper me = myLooper();
    ...

    final MessageQueue queue = me.mQueue;
    ...

    for (;;) {
        Message msg = queue.next(); // might block   //7.&#x901A;&#x8FC7;MessageQueue.next()&#x65B9;&#x6CD5;&#x83B7;&#x53D6;Message&#x5BF9;&#x8C61;&#x3002;
        ...

        final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        final long end;
        try {
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...

        msg.recycleUnchecked();
    }
}

7-8. MessageQueue.next() 方法获取Message 对象。

Message next() {
    ...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) { //&#x6B7B;&#x5FAA;&#x73AF;
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);   // 5: &#x907F;&#x514D;&#x4E86;&#x963B;&#x585E;&#x7684;&#x5173;&#x952E;&#x70B9;&#xFF0C;&#x91CA;&#x653E;&#x8D44;&#x6E90;&#xFF0C;&#x5904;&#x4E8E;&#x7B49;&#x5F85;&#x3002;&#x7591;&#x70B9;&#xFF1A;&#x5904;&#x4E8E;&#x7B49;&#x5F85;&#xFF0C;&#x80AF;&#x5B9A;&#x9700;&#x8981;&#x4E00;&#x4E2A;&#x4E1C;&#x897F;&#x6765;&#x5524;&#x9192;&#x5B83;&#x3002;&#x4E0A;&#x9762;&#x7B2C;5&#x6B65;&#x5206;&#x6790;enqueueMessage&#x7684;&#x65F6;&#x5019;&#x6709;&#x884C;&#x4EE3;&#x7801;if (needWake) {
            nativeWake(mPtr); //&#x753B;&#x91CD;&#x70B9;&#xFF0C;&#x6B64;&#x5904;&#x5524;&#x9192;&#x7B49;&#x5F85;&#x7684;next &#x65B9;&#x6CD5;&#x3002;
        } &#x3002;

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.

            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) { //******&#x6B64;&#x6761;&#x4EF6;&#x53EF;&#x4EE5;&#x5148;&#x4E0D;&#x770B;&#xFF0C;&#x56E0;&#x4E3A;&#x901A;&#x8FC7;Handler &#x53D1;&#x9001;&#x7684;&#x6D88;&#x606F;target &#x90FD;&#x4F1A;&#x6301;&#x6709;Handler&#xFF0C;&#x8BE5;&#x903B;&#x8F91;&#x4E0D;&#x4F1A;&#x89E6;&#x53D1;&#x3002;&#x6D88;&#x606F;&#x540C;&#x6B65;&#x5C4F;&#x969C;&#x7684;&#x65F6;&#x5019;&#x4F1A;&#x4F18;&#x5148;&#x89E6;&#x53D1;&#x8BE5;&#x903B;&#x8F91;&#x3002;
                // Stalled by a barrier.  Find the next asynchronous message in the queue.

                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) { //&#x67E5;&#x627E;&#x5F53;&#x524D;&#x7684;msg &#x5BF9;&#x8C61;&#x3002;
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.

                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.

                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.

                nextPollTimeoutMillis = -1;
            }
         ...

        nextPollTimeoutMillis = 0;
    }
}
  1. Handler.dispatchMessage 方法,此处有判断,如果在Activity中使用view.post方法调用的时候,就会走到handleCallback 回调中。通过sendMessagexxx函数发送消息的就会走到handleMessage回调中去。
/**
 * Handle system messages here.

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

该方法会会将msg 对象发送到客户端定义Handler 的地方,重写的handleMessage 方法。至此,Handler 发送消息的流程大致介绍完成。

总结

Handler 发送消息的时候,在Handler.enqueueMessage 方法中,将该Handler 对象添加到Message中的target 属性中,这样就完成了Message 持有Handler 的操作,为最后Message.target.dispatchMessage 做了保证。然后将该Message 对象放入到MessageQueue中的Message.next 中去,完成了消息链表的添加;而这个MessageQueue 是Looper 中所持有的对象,这样就可以通过Looper类通过对MessageQueue.next()—->Message.next()—>Message.target.dispatchMessage(msg)完成了消息的分发。

一篇文章扒掉“桥梁Handler”的底裤

知识点补充

  1. Looper 对象是怎么new 出来的?
    一篇文章扒掉“桥梁Handler”的底裤
    上图看出是在应用程序进程的ActivityThread 类中的main() 函数中调用了Looper.prepareMainLooper() 方法,就new 出来了主线程中的Looper.

一篇文章扒掉“桥梁Handler”的底裤
上图也看出,这个Looper.prepareMainLooper()方法是系统调用的,开发者不能再次调用了,否则会抛出异常。
一篇文章扒掉“桥梁Handler”的底裤
prepare这个方法真正的new Looper 了。接着来看看Looper 的构造函数

一篇文章扒掉“桥梁Handler”的底裤
此处创建了MessageQueue, Handler 中的MessageQueue 就是这块创建的。
  1. 为什么将Looper 保存在ThreadLocal 中?

ThreadLocal:线程的变量副本,每个线程隔离.我的理解就是,ThreadLocal 内部使用了当前线程为Key,需要存储的对象为Value,通过字典保存起来的,这样客户端在获取的时候,当前线程就只会获取一份保存的Value.回到Looper中,就可以知道一个线程里按理说就会只有一个Looper。

  1. Message 为什么推荐使用obtain() 方式获取Message对象,而不推荐使用new Message()?

这里涉及到池的技术的应用: Message中维护了一个消息池,消息使用完就会回收。减少对象创建和销毁的开销;java 当中的线程池也是用到了该思想。

  1. 同步屏障:

同步屏障机制的作用,是让这个绘制消息得以越过其他的消息,优先被执行。系统中UI绘制会使用到同步屏障,开发中基本用不到。核心代码: 先设置一个target=null 的消息,插入到消息链表的头部。

一篇文章扒掉“桥梁Handler”的底裤
然后在MessageQueue.next 中 优先查找同步屏障中的消息asyncHronous 设置为true的异步消息。

一篇文章扒掉“桥梁Handler”的底裤
  1. Handler为什么会导致内存泄漏以及解决方案?

Handler导致内存泄漏一般发生在发送延迟消息的时候,当Activity关闭之后,延迟消息还没发出,那么主线程中的MessageQueue就会持有这个消息的引用,而这个消息是持有Handler的引用,而handler作为匿名内部类持有了Activity的引用,所以就有了以下的一条引用链。
解决:1.使用静态内部类,如果要调用Activity中的方法,就可以在静态内部类中设置一个
WeakReference activityWeakReference; 引用。

2.在Activity销毁的时候,即onDestory()方法中调用handler.removeCallbacks,移除runnable。

结尾

OK,本次的Android进阶技术之Handler到此就全部写完了,希望喜欢的朋友不要吝啬你的赞,你的评论,点赞,收藏就是对我最大的支持,记得关注我哦,咱们文章每日都会更新,感谢大家的观看。

Original: https://www.cnblogs.com/BlueSocks/p/16035178.html
Author: BlueSocks
Title: 一篇文章扒掉“桥梁Handler”的底裤

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/607425/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 关于.Net Core生成JSON时错误:A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.

    此笔记记载了本人在.Net Core 5.0环境下生成Json数据时 A possible object cycle was detected which is not suppo…

    Linux 2023年6月14日
    0167
  • [云计算]TCP云架构-思维导图

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/Skybiubiu/p/16276893.htmlAut…

    Linux 2023年6月13日
    077
  • 版本控制gitlab

    版本控制gitlab 版本控制gitlab 什么是版本控制gitlab gitlab部署 什么是版本控制gitlab GitLab 是一个用于仓库管理系统的开源项目,使用Git作为…

    Linux 2023年6月6日
    097
  • MySQL-报错:Error when bootstrapping CMake:

    在进行MySQL的源码安装的时候,系统上找不到合适的C编译器,GCC忘了装,莫慌,直接 yum命令装上gcc,还有gcc-C++没装的话后面也会提示错误,一起装上,,, [root…

    Linux 2023年6月13日
    096
  • Postman 正确使用姿势

    前言: 请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 简介: Postman是一个接口测试工具,在做接口测试的时候,Postman相当于一个客户端,它可以模拟用户发起的…

    Linux 2023年6月14日
    068
  • Redis功能拓展-消息队列

    1.什么是消息队列,消息队列解决什么问题?从宏观上看,消息队列就是围绕队列这个数据结构而拓展开的一段特殊程序,将这类程序单独部署就可以称之为消息中间件(也称:消息队列)。在分布式系…

    Linux 2023年6月7日
    090
  • 【Example】C++ 标准库智能指针 unique_ptr 与 shared_ptr

    【概念直接搬运Docs】C 样式编程的一个主要 bug 类型是内存泄漏。 泄漏通常是由于为分配的内存的调用失败引起的 delete new 。 现代 C++ 强调”资源…

    Linux 2023年6月13日
    0122
  • redis数据库

    这一次主要是接着redis服务器接着进行代码讲解,因为redis服务器中包含大量的数据库,因为redis也对每个数据库设计了结构体 redis数据库 在上面 redisServer…

    Linux 2023年6月13日
    094
  • Java秒杀系统三:web层

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月11日
    090
  • rsync

    rsync rsync是linux系统下的数据镜像备份工具。使用快速增量备份工具Remote Sync可以远程同步,支持本地复制,或者与其他SSH、rsync主机同步。 rsync…

    Linux 2023年6月7日
    0104
  • IIC挂死问题解决过程

    猜测1:认为IIC device程序有问题 检查1:查看程序发现有可能溢出的部分,使用IIC 工具刷过量数据到slave,未出问题。 猜测2:认为IIC device寄存器进入异常…

    Linux 2023年6月6日
    0106
  • Linux安装管理

    Linux系列 包管理工具 单个软件包 管理工具 RedHat系列 Redhat Centos Fedora yum rpm .rpm Debian系列 Ubuntu apt-ge…

    Linux 2023年6月8日
    087
  • Nginx 平滑升级(不需要关闭Nginx升级)

    Nginx 平滑升级 对Nginx的版本进行更新,或者要增添新的模块,最简单的方法就是停止当前的Nginx服务,重新编译安装nginx,然后开启新的Nginx服务。但是这样会导致在…

    Linux 2023年6月13日
    084
  • Redis 常见面试题(2020最新版)

    https://www.cnblogs.com/javazhiyin/p/13839357.html 概述 什么是Redis Redis(Remote Dictionary Ser…

    Linux 2023年5月28日
    097
  • Windows Server 新增磁盘处于脱机状态解决办法

    解决方案: Cmd命令行操作如下: 1,进入diskpart模式 2、列出磁盘情况 3、选择脱机的磁盘 4、联机磁盘 5、清除磁盘属性 6、进入磁盘管理,提示初始化 每天记录一点,…

    Linux 2023年6月8日
    0100
  • ThinkPHP 使用 think-queue 实现 redis 消息队列

    简单介绍:消息队列中间件是大型系统中的重要组件,已经逐渐成为企业系统内部通信的核心手段。它具有松耦合、异步消息、流量削峰、可靠投递、广播、流量控制、最终一致性等一系列功能,已经成为…

    Linux 2023年5月28日
    084
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球