• 再谈Android消息分发


     本文来自刘兆贤的博客_CSDN博客-Java高级,Android旅行,Android基础领域博主 ,引用必须注明出处!

    ActivityThread从main方法开始,创建Looper对象、Handler和ApplicationThread接收ActivityManagerService的IPC调用,将消息传到Handler进行异步分发(更新UI主要用异步,ApplicationThread里的dumpActivity、dumpService、dumpProvider等保存组件状态的操作,使用同步更新,MessageQueue里的消息,同步的先执行,异步后执行)。

    其中ApplicationThread,是一个Binder,继承自

    https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/app/IApplicationThread.aidl

    ActivityManagerService,也是一个Binder,继承自

    https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/app/IActivityManager.aidl

    Android消息分发机制:

    线程内调用 Looper.prepare():创建自己的Looper对象和MessageQueue,新建Hander(Looper.myLooper)用于发送本线程的消息,通过Looper.loop来执行本线程消息分发,同时结束时执行Looper.myLooper().quit()退出当前线程,不然线程一直运行,这也是HandlerThread的使用原理。

    Handler消息分发,优化判断msg.callback是否为null,如果不是先执行它,再执行handlerMessage。

    Looper什么时候退出循环?

    当MessageQueue的next方法返回null时。为什么会为null?因为设置mQuiting字段为true。什么时候mQuiting为true?ActivityThread退出的时候,执行Looper的quit方法(非safe,safe模式:当前时间小于首个Message执行时间,则一个个recycle掉,反之取到最后一个元素,实行断链操作,避免成为GcRoot)。

    子线程如何跟主线程交互?

    双方实现各自的Handler,互调对方的hander发送消息。

    为什么子线程中不能更新UI?

    因为系统判断当前线程不是主线程(ViewRootImpl.checkThread),则抛出异常。

    为什么onCreate方法里,可以在子线程中更新UI?

    因为在onResume方法才生成ViewRootImpl实例,将其加到Window上。

    为什么Loop.loop方法里是死循环(会调用MessageQueue里next方法的while循环),但主线程没有被卡死?

    因为主线程是通过Handler被动接收消息的,只有手机被操作,或者任务到期才会唤醒。在这个过程中利用Linux的epoll和pipe机制,阻塞时,让出CPU,等有新消息再唤醒。

    具体一点:会计算下次循环的时间,Binder.flushPendingCommands将未执行的binder命令刷入内核(针对阻塞时间较长的场景,确保已引用对象被挂起),通过nativePollOnce方法(linux的pipe机制,实现了IO复用),将CPU资源释放,等待唤醒。

    番外:

    NativePollOnce使用linux的epoll机制,将线程挂起,下个消息到来时被唤醒,类似thread的wait/notify操作,它会释放CPU,因此不会卡死。

    Binder.flushPendingCommands,将未执行的Binder命令刷新到Linux内核驱动里,对于阻塞较长时间的操作有用,用于将这些对象引用释放掉,以免进程持有它们超过所需时间。

    Epoll机制:

    Linux的IO多路复制技术,支持高并发。epoll相对于poll和select优势,在有10万个连接,同时10个客户端发送数据,前者只检查这10个客户端,而后者则要检查10万个连接,是否有发送数据。

    ANR机制:

    发送延迟任务,检测当前线程是否在一定时间内完成,如果提前完成则清除该任务。为什么要这么设计?因为每个生命周期方法,都依赖前面的方法完成,是一个线性关系。真正出现ANR的是,执行耗时操作,阻塞了任务分发。

    各组件阻塞时长限制:

    Service Timeout(最长,前台20秒)

    文件:ActiveServices.java

        // How long we wait for a service to finish executing.

        static final int SERVICE_TIMEOUT = 20*1000; // 前台    // How long we wait for a service to finish executing.

        static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10; // 后台

    Broadcast Timeout(10秒,同ContentProvider)

    文件:ActivityManagerService.java

        // How long we allow a receiver to run before giving up on it.

        static final int BROADCAST_FG_TIMEOUT = 10*1000;  // 前台

        static final int BROADCAST_BG_TIMEOUT = 60*1000;  // 后台

    InputDispatching Timeout(5秒,同Activity)

    文件:ActivityManagerService.java

        // How long we wait until we timeout on key dispatching.

        static final int KEY_DISPATCHING_TIMEOUT = 5*1000;

    ContentProvider Timeout(10秒,同Broadcast)

        // How long we wait for an attached process to publish its content providers

        // before we decide it must be hung.

        static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;

    怎么防止应用崩溃退出?

    在监测到unCaughtException后,将Loop.loop进行try.catch操作,将错误抓住,并使用while(true)保证下次也会将错误抓住。这里由于Message的堵塞机制,所以不会一直执行loop操作。

            while (true) {

                //用于保证下次crash仍能进来

                try {

                    //由于消息的阻塞机制,并不会一直执行loop

                    Looper.loop();

                } catch (Throwable e) {

                    isChoreographerException(e);

                    if (sExceptionHandler != null) {

                        sExceptionHandler.bandageExceptionHappened(e);

                    }

                }

            }

    解决ANR的技巧:

    一、AsyncLayoutInflater,异步解析xml生成View,解决IO加载慢,影响UI线程的问题。

    注意事项:

    1、在主线程中调用,如果在子线程中调用,在OnInflateFinishedListener回调onInflateFinished时,需要回到主线程操作setContentView或者其他View操作;因为回调是在当前线程内完成的(实际上,AsyncLayoutInflater内部本来就是在子线程中操作,没必要再加一个子线程)。

    2、inflate结束会主动将传递过来的context置空,不用担心内存泄露,但需要判断异步返回View时,当前页面是否正在运行。

    3、在当前Activity中,该类可以重复使用;原因是SynchronizedPool会缓存InflateRequest;属于一个生产者/消费者模式,无调用次数限制。

    4、其内部的InflateThread一直在运行,没有销毁方法,故其最佳使用场景是在Application中,接收各种infalte操作,从而避免许多AsyncLayoutInflater对象无法回收。

    二、Looper.myQueue().addIdleHandler。当queueIdle返回false则直接删除此任务,返回true保持idleHandler存活(下次处理完其他消息再回调它)。

        public static interface IdleHandler {

            /**

             * Called when the message queue has run out of messages and will now

             * wait for more.  Return true to keep your idle handler active, false

             * to have it removed.  This may be called if there are still messages

             * pending in the queue, but they are all scheduled to be dispatched

             * after the current time.

             */

            boolean queueIdle();

        }

    主要应用场景:用于在消息空闲时,处理一些不那么重要的操作。比如可以使用post方法获取View的宽高,也可以使用idleHandler,区别前者作为同步消息执行,而后者空闲里执行,可以用于优化一些操作。

    怎么提高消息优先级?

    1. handler调用postAtFrontOfQueue,执行一个Runnable。
    2. 不要发送延时消息。
    3. 建立消息屏障,将asynchronous为true的消息先返回。

  • 相关阅读:
    怎样优雅地增删查改(八):按用户关系查询
    SRE 排障利器,接口请求超时试试 httpstat
    【JVM笔记】同步控制字节码指令
    [ vulhub漏洞复现篇 ] Django SQL注入漏洞复现 CVE-2021-35042
    戳进来,带你走近飞凌嵌入式旗舰级AIoT芯RK3588开发板
    Python+Selenium定位不到元素常见原因及解决办法(报:NoSuchElementException)
    A股风格因子看板 (2023.09 第08期)
    怎样在Windows10系统中安装配置PL/SQL
    武大50名学生造卫星,南航“00后”学生造火箭,后浪制造未来可期
    解决【面板运行时发生错误: ‘解析软件列表发生错误,已尝试自动修复,请刷新页面重试!‘】的图文教程
  • 原文地址:https://blog.csdn.net/liuxian13183/article/details/126145157