• Handler消息机制,postDelayed会造成线程阻塞吗?对内存有什么影响?


    线程阻塞

    涉及到了操作系统的一些内容(需要了解的可以来这里

    简单来说,Android是基于Linux系统的,而在Linux系统中,所有的进程都是从init进程直接或者间接fork出来的,App也不例外。每开启一个App,就相当于开启一个新的线程。在21世纪的进程,本着物尽其用的原则,就必然存在异步处理,也就需要了多个线程。多个线程之间就会存在调度的情形,而线程阻塞就是其中的一种状态(Android的多线程这个博客讲的挺详细)。

    postDelayed干了什么

    Android消息处理机制

    • Handler.postDelayed最后通过MessageQueue.enqueueMessage方法将Message插入MessageQueue中,执行插入操作唤醒当前线程,由于MessageQueue会有多个线程操作,这里用了同步锁保证线程操作安全。所以Handler.postDelayed不会造成线程阻塞,只是执行了Message入队的操作。
    • 对于阻塞的说法也是有的,Handler对Message的处理是管道操作,MessageQueue对所有Message统一进行了管理,在Looper.loop()不停对MessageQueue遍历的时候会进行一个判断,如果队列头部的Message没到执行时间,计算一下剩余执行时间,然后调用nativePollOnce()阻塞线程,直到阻塞时间到或者下一次有Message进队。
    Handler.java
    public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
            return sendMessageDelayed(getPostMessage(r), delayMillis);
        }
    
    
       public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
        
       public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
       // mQueue是Handler绑定的Lopper中的Message队列
            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);
        }
        
        private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
    
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            //调用了MessageQueue的插入Meessage方法
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    MessageQueue.java
     boolean enqueueMessage(Message msg, long when) {
            synchronized (this) {
        		...
               for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //插入Message
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
    			...
    			if (needWake) {
    			//唤醒线程
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    对内存的影响

    Handler导致内存泄漏一般发生在发送延迟消息的时候,当Activity关闭之后,延迟消息还没发出,那么主线程中的MessageQueue就会持有这个消息的引用,而这个消息是持有Handler的引用,而handler作为匿名内部类持有了Activity的引用,所以就有了以下的一条引用链。

    主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity

    其根本原因是因为这条引用链的头头,也就是主线程,是不会被回收的,所以导致Activity无法被回收,出现内存泄漏,其中Handler只能算是导火索。

    而我们平时用到的子线程通过Handler更新UI,其原因是因为运行中的子线程不会被回收,而子线程持有了Actiivty的引用(不然也无法调用Activity的Handler),所以就导致内存泄漏了,但是这个情况的主要原因还是在于子线程本身。

    参考链接:https://cloud.tencent.com/developer/article/1800399

  • 相关阅读:
    SwiftUI SQLite 教程之 构建App本地数据库实现创建、读取、更新和删除(教程含完成项目源码)
    公开课|“技术+法律”隐私计算如何助力数据合规
    快速搭建接口自动化测试框架
    怒刷LeetCode的第11天(Java版)
    运筹帷幄决胜千里,Python3.10原生协程asyncio工业级真实协程异步消费任务调度实践
    ArcGIS Engine:C#基础语法的了解
    丁鹿学堂:前端异步进阶之Rxjs的使用
    2035. 将数组分成两个数组并最小化数组和的差 折半搜索
    企业运维实践-Nginx使用geoip2模块并利用MaxMind的GeoIP2数据库实现处理不同国家或城市的访问最佳...
    【Debian系统】:安装debian系统之后,很多命令找不到,需要添加sudo之后才能使用,以下解决方法
  • 原文地址:https://blog.csdn.net/qq_27016061/article/details/133805394