一篇文章扒掉“桥梁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)

大家都在看

  • centos7用rpm安装mysql5.7【初始用yum安装发现下载非常慢,就考虑本地用迅雷下载rpm方式安装】

    1.下载 4个rpm包 mysql-community-client-5.7.26-1.el7.x86_64.rpmmysql-community-common-5.7.26-1….

    Linux 2023年6月7日
    0102
  • 这几天的杂学

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

    Linux 2023年6月7日
    0100
  • Jenkins配置国内插件源

    由于Jenkins插件下载慢,配置国内源 进入Jenkins的更新目录: jenkins-home 下的updates 目录 替换目录里面的default.json 文件内容(je…

    Linux 2023年6月14日
    0297
  • 从前端走向后端

    每次过年回老家聚会,遇到不熟悉的亲戚朋友,经常被问到职业是什么。一开始,我总是很认真的回答这个问题,结果常常引出一番尴尬的问答。 &#x201C;&#x4F60;&…

    Linux 2023年6月6日
    0103
  • mit6.824 笔记 一

    分布式是复杂的系统再考虑分布式系统前应该尽可能尝试其他方法。 人们使用大量的相互协作的计算机驱动力是: 人们需要获得更高的计算性能。可以这么理解这一点,(大量的计算机意味着)大量的…

    Linux 2023年6月7日
    0109
  • 云主机搭建WordPress个人博客

    安装宝塔控制面板 宝塔面板是一个简单、好用的面板,它的功能就是将LNMP和服务器的各种管理集成到一个可视化的WEB环境来管理,通过面板,我们普通人不需要掌握具体的技术,只需要动动鼠…

    Linux 2023年6月8日
    0109
  • powershell配置自动补全

    powershell配置自动补全 一、需求: 看到老师上课用mac命令行有自动补全功能,发现真的爽。但是自己的windows powershell不能使用自动补全功能。有了需求,就…

    Linux 2023年6月13日
    0137
  • 实验一 密码引擎-4-国䀄算法交叉测试

    任务详情 0 2人一组,创建一个文件,文件名为小组成员学号,内容为小组成员学号和姓名1 在Ubuntu中使用OpenSSL用SM4算法加密上述文件,然后用龙脉eKey解密,提交代码…

    Linux 2023年6月8日
    0101
  • CentOS 7.6 安装 MySQL-5.7.31(RPM方式安装)

    准备工作: 注:5.7.31版本安装步骤及初始化和之前版本有较大区别 CentOS 7.6 系统: 带GUI的服务器 默认安装 MySQL 5.7.31 安装包: 1.RPM安装包…

    Linux 2023年6月8日
    088
  • 【.Net vs Java? 】 看一看二者的类有多像?

    1. 包(Package)、命名空间(NameSpace) 在Java中常用的是包(Package),较少提到NameSpace的概念。Java官方文档中这样说: 为了使类型更易于…

    Linux 2023年6月7日
    092
  • powershell版,Fail2Ban脚本,阻止黑客攻击sshd

    关键字 powershell Deny Hosts Fail2Ban ssh linux 近期惊闻 黑客团伙利用SSH暴力破a解,入侵远程设备 用于挖矿和DDoS攻击 疑似来自罗马…

    Linux 2023年6月14日
    081
  • Linux 下安装 node.js

    这里介绍两种安装方式: 编译安装和使用编译后的安装包安装。 安装目录: /usr/local 一、使用编译安装包安装 1、进入安装目录: 2、下载安装包: 3、解压: 4、进入解压…

    Linux 2023年6月13日
    0100
  • Redis监控技巧(转)

    来自:http://blog.nosqlfan.com/html/4166.html Redis 监控最直接的方法当然就是使用系统提供的 info 命令来做了,你只需要执行下面一条…

    Linux 2023年5月28日
    0100
  • 白话linux操作系统原理

    虽然计算机相关专业,操作系统和计算机组成原理是必修课。但是大学时和真正从事相关专业工作之后,对于知识的认知自然会发生变化。还很有可能,一辈子呆在学校的老师们只是照本宣科,自己的理解…

    Linux 2023年5月27日
    0118
  • 【Example】C++ 标准库 std::atomic 及 std::memory_order

    C++ 标准库提供了原子操作。(我已经懒得写序言了) ==================================== 先来说原子操作的概念: 原子操作是多线程当中对资源进…

    Linux 2023年6月13日
    079
  • CentOS7 小技巧总结

    1.CentOS7 解决无法使用tab自动补全 csharp;gutter:true; 原因:CentOS在最小化安装时,没有安装自动补全的包,需要手动安装。 yum -y ins…

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