当前位置: 首页 > 科技观察

速度与安全齐头并进!异步布局改造大大提升客户端布局性能

时间:2023-03-20 11:07:07 科技观察

一、背景介绍随着小红书用户规模的不断增长,App性能对用户体验的影响越来越重要,比如页面打开速度、App启动速度等等,几十毫秒的提升可以在业务数据上带来显着的收益。今天要介绍的是一个官方框架的实践和优化。这期间,踩了很多坑,但是收获也很可观。AsyncLayoutInflater于2015年首次出现在support.v4包中,用于异步膨胀布局。一般来说,inflate需要在主线程执行,所以是页面初始化过程中比较耗时的main部分。该工具提供了异步膨胀的能力,从而减少主线程拥塞。本文主要介绍该工具的使用和改进方法,以及改进中遇到的一些问题。2.使用AsyncLayoutInflater使用很简单,只需要添加一个依赖即可。同时在代码中使用如下:异步inflate完成后会有回调,此时可以使用view。3.源码分析这个工具最厉害的地方在于异步inflateview不存在线程安全相关的一些问题。我们来看看它是如何处理线程安全问题的。首先是Thread的单实例,它有一个线程安全的阻塞队列和一个线程安全的对象池。这个单例中的一个方法是enqueue方法,它会调用阻塞队列的put,将请求插入到队列中。因为是线程安全的队列+线程安全的对象池,所以这一系列的操作都保证了线程安全。下面是inflate的过程。inflate时会通过mInflateThread.obtainRequest从对象池中获取一个请求,然后将请求插入到队列中。下面是一个简化的代码。run中存在死循环,通过阻塞队列的take元素进行inflate操作。以上简单工具的分析就结束了。这部分基本上回答了一个关于如何在线程之间同步数据的问题,可以通过在典型的生产者消费者模型中加入线程安全的容器来保证。4.问题与改进在使用中仍然存在很多与线程相关的问题。下面就一些比较重要的问题来说明。4.1单线程和多线程InflateThread这里的设计是单实例单线程。当需要对线程进行一些定制或者缩水的时候,修改起来会有点麻烦。这里可以通过开启设置线程池的方法来提供一些线程管理和自定义的能力,可以默认内置一个单线程的线程池。通过比较长时间的实验,我们发现在主线程比较空闲的时候,单线程的效果会更好,因为是在大核上执行的,效率更高。当主线程比较忙的时候,比如冷启动阶段,多线程会有更好的效率。4.2ArrayMap和线程安全我们在实际使用中发现,SimpleArrayMap或ArrayMap用于一些自定义的View构造函数和darkmode实现。ArrayMap是SimpleArrayMap的子类,SimpleArrayMap本身就是使用两个静态数组来实现对象的缓存,从而起到多路复用的作用。在多线程的情况下,会存在线程安全问题。这里,会出现复用对象不匹配导致的崩溃。一个简单的方法就是在发生crash的时候清空对应的缓存数组,就可以避免。4.3inflate、锁和线程安全LayoutInflater的inflate方法中存在锁,导致如果要多线程调用inflate,无法达到多线程的效果。如果是单线程,可能也会遇到和主线程在inflate时等待锁一样的问题。这里mConstructorArgs是一个成员变量。通过重写LayoutInflater中的cloneInContext方法,配合对象池,可以避免这个锁问题。同时,inflate过程中使用的数组和容器类型不是线程安全的。如果要去掉inflate方法开头的同步限制,这些线程不安全的容器类也需要特别注意。4.4BasicInflater的改造AsyncLayoutInflater本身就有一个BasicInflater。根据上面的一些改进,我们在实践中对其进行了一些修改,扩展了可以设置线程池的接口,利用基础设施提供的线程池来实现线程的统一管理。实际中,当CPU比较忙的时候,多线程线程池的效果要比单线程好。当CPU比较空闲的时候,单线程的效果会更好,因为可以更好的利用释放出来的CPU大核。表现。同时重写了ArrayMap中一些线程不安全的处理方法,这样在多线程使用ArrayMap或使用依赖ArrayMap的函数时就不会出现crash。这涉及到我们的一些自定义视图和我们的暗模式实现。针对inflate锁和一些线程不安全的容器处理,重写了LayoutInflater的cloneInContext方法去掉了synchronized限制,在onCreateView过程中增加了一个线程安全的容器,保证了inflate过程的线程安全。总的来说,AsyncLayoutInflater、ArrayMap、LayoutInflater都是通过重写实现线程安全的,同时将这些集成到我们的业务框架中,让使用成本更低。4.5ViewCache的另一种做法是在业务端做进一步的封装。通过ViewCache的单实例,预先将一些模块化的View膨胀起来,存储在ViewCache中,以后需要使用的时候再从ViewCache中获取。这样就避免了使用时inflate带来的耗时问题。这块整体代码比较简单,就不单独展开了。需要注意的是,一些不用的View需要及时释放,避免内存泄露。5.总结AsyncLayoutInflater的实践和优化。它持续了大约半年。我们在App冷启动和备注详情页面的性能优化中获得了20%以上的性能提升和显着的商业收益。同时,我们也将这种能力沉淀到业务框架中,方便后续的接入和使用成本。通过ViewCache和业务框架,我们基本实现了覆盖大部分业务需求的能力。未来我们会进一步优化框架的易用性和部分场景的使用,结合其他的优化方式,为业务方提供更多的选择,让他们写业务的时候不用关注这部分消耗时间和复杂度,从而提高开发效率。六、作者信息小红书业务技术Android工程师,负责业务架构设计和性能优化,目前专注于交易环节的迭代和优化。

最新推荐
猜你喜欢