AsyncLayoutInflater是Android官方出品的一个供给异步inflate布局的东西,为在布局的性能优化上供给了新的思路。本文介绍了小红书在运用以及改造这个东西上的经验,希望能和大家一同讨论更多的优化办法。
1. 布景介绍
随着小红书用户规模的不断增长,App性能对用户体验的影响显得越来越重要,例如页面的翻开速度、App的发动速度等,几十毫秒的提高都能带来事务数据上比较显著的收益。今日要介绍的是对一个官方结构的实践以及优化,期间踩了不少坑,但收益也很可观。
AsyncLayoutInflater最早于2015年呈现在support.v4包中,用来异步inflate布局。一般来讲inflate需求在主线程执行,所以是一个页面初始化过程中的耗时首要部分,这个东西供给了能够在异步inflate的才能,进而减少主线程堵塞。本文首要介绍东西的运用以及怎么改善,以及改善中遇到的一些问题。
2. 运用
AsyncLayoutInflater的运用非常简略,只需求参加一个依赖即可。
一起在代码中的运用如下:
在异步inflate好之后会有回调,这时分就能够运用view了。
3. 源码分析
这个东西最厉害的地方就在于异步inflateview竟然没有呈现线程安全相关的一些问题,下面咱们就来看看它是怎样处理线程安全的问题的。
首要,里面有一个Thread的单例,单例里有一个线程安全的阻塞行列和一个线程安全的目标池。
这个单例里有个办法是enqueue办法,会调用阻塞行列的put,将request插入行列中。由于是一个线程安全的行列+线程安全的目标池,所以这一系列操作就确保了线程安全。
下面是inflate的流程,inflate的时分会经过mInflateThread.obtainRequest从目标池里拿到一个request,然后再将这个request插入行列中。
下面是一个简化过的代码,run中有一个死循环,经过阻塞行列的take元素进行inflate的操作。
以上这个简略的东西就分析完了。这部分根本就答复了线程间怎么同步数据的一个问题,在一个典型的生产者消费者模型中参加线程安全的容器即可确保。
4. 问题与改善
在运用中仍是遇到很多线程相关的问题,以下罗列几点相对重要的问题进行论述。
4.1单线程与多线程
InflateThread在这儿的设计是一个单例单线程,当需求对线程有一些定制或许收拢的话,改动就有些麻烦了,这儿能够经过敞开一个设置线程池的办法来供给一些线程办理和定制的才能,默认能够内置一个单线程的线程池。
经过比较长期的实验咱们发现,在主线程比较闲暇的时分,单线程的作用会好一些,由于都在大核上执行了,功率更高。主线程繁忙的时分,例如冷启阶段,多线程会有更好的功率。
4.2ArrayMap与线程安全
咱们在实际运用中发现,在一些自定义View的构造函数中和darkmode的完成中运用了SimpleArrayMap或ArrayMap,ArrayMap是SimpleArrayMap的子类,自身SimpleArrayMap是用过两个static的数组来完成目标的缓存,从而起到复用的作用,在多线程的情况下会有线程安全问题,这儿会呈现复用目标不匹配导致的crash。一个简略的办法便是当呈现crash的时分讲对应的cache数组清空,即可防止。
4.3inflate、锁与线程安全
LayoutInflater的inflate办法中有一个锁,这个导致了假如你想多线程去调用inflate的时分,起不到多线程的作用,假如是单线程的情况下,还可能遇到和主线程在inflate时同样等候锁的问题。这儿mConstructorArgs是一个成员变量,经过重写LayoutInflater中的cloneInContext办法,配合目标池就能够避开这儿锁的问题。
一起inflate过程中用到的这些数组和容器类型,都不是线程安全的,假如想要去掉inflate办法开头的synchronize的约束,这些线程不安全的容器类也是需求特别留意的。
4.4BasicInflater改造
AsyncLayoutInflater自身有一个BasicInflater,根据以上的一些改善点,咱们在实践中对其做了一些改造,扩展出了能够设置线程池的接口,运用了根底架构供给的线程池,做到了对线程的统一办理。实践下来,在CPU比较繁忙的时分,多线程的线程池作用要好于单线程,当CPU比较闲暇的时分,单线程的作用会更好一些,由于能够更好的利用开释出来的CPU大核的性能。
一起重写了ArrayMap中线程不安全的一些处理办法,使得在多线程运用ArrayMap或许运用依赖ArrayMap的功用时不会呈现crash,这儿涉及到了咱们的一些自定义View和咱们的darkmode的完成。
在对于inflate的锁和一些线程不安全的容器处理上,重写了LayoutInflater的cloneInContext办法去掉了synchronized的约束,一起在onCreateView的流程中参加了线程安全的容器来保障inflate过程的线程安全。
综合来说便是重写了AsyncLayoutInflater,ArrayMap和LayoutInflater,以到达线程安全的目的,一起将这些融入到咱们的事务结构中,使得运用本钱更低。
4.5ViewCache
另一个实践是在事务侧做了进一步的封装,经过一个ViewCache的单例,提早将一些模块化的View提早inflate好,存在ViewCache中,在后续需求运用的时分从ViewCache中在获取,这样就防止了用的时分再inflate导致的耗时问题了。这块整体的代码比较简略,就不单独打开讲了,需求留意的点是有些View没有被运用需求及时开释,防止内存走漏。
5. 总结
AsyncLayoutInflater的实践与优化,前后继续了半年左右,咱们在App冷发动和笔记详情页的性能优化中获得了超越的20%的性能收益以及显著的事务收益。一起,咱们也将这个才能沉淀了到了事务结构中,方便了后续的接入和运用本钱,经过ViewCache和事务结构,根本做到了能够覆盖大部分事务需求的才能。未来,咱们将会在结构的易用性以及一些场景的运用上做进一步的优化,结合其他的优化手段给事务方供给更多的挑选,使其能在写事务的一起无需关注这部分的耗时与复杂度,从而提高开发功率。
六、作者信息
殇不患
小红书商业技能Android工程师,曾担任事务架构设计与性能优化,现在专注于交易链路的迭代与优化。