Rapidité et sécurité vont de pair ! La transformation de la mise en page asynchrone améliore considérablement les performances de mise en page du client

AsyncLayoutInflater est un outil officiellement produit par Android qui fournit une disposition de gonflage asynchrone, qui fournit une nouvelle idée pour l'optimisation des performances de la disposition. Cet article présente l'expérience de Xiaohongshu dans l'utilisation et la transformation de cet outil, et espère discuter avec vous d'autres méthodes d'optimisation.

1. Présentation générale

Avec la croissance continue de l'échelle des utilisateurs de Xiaohongshu, l'impact des performances de l'application sur l'expérience utilisateur devient de plus en plus important.Par exemple, la vitesse d'ouverture des pages, la vitesse de démarrage de l'application, etc., l'amélioration de dizaines de millisecondes peut apporter des données commerciales importantes. Ce que je veux vous présenter aujourd'hui c'est la pratique et l'optimisation d'un framework officiel.Pendant cette période, j'ai sauté sur pas mal d'embûches, mais les bénéfices sont aussi considérables.

AsyncLayoutInflater est apparu pour la première fois dans le package support.v4 en 2015 pour une disposition de gonflage asynchrone. D'une manière générale, gonfler doit être exécuté sur le thread principal, c'est donc une partie principale du processus d'initialisation de la page qui prend du temps. Cet outil offre la possibilité de gonfler de manière asynchrone, réduisant ainsi la congestion du thread principal. Cet article présente principalement l'utilisation de l'outil et comment l'améliorer, ainsi que quelques problèmes rencontrés lors de l'amélioration.

2. utiliser

L'utilisation d'AsyncLayoutInflater est très simple, il suffit d'ajouter une dépendance.

Dans le même temps, l'utilisation dans le code est la suivante :

Il y aura un rappel une fois le gonflage asynchrone terminé, et la vue peut être utilisée à ce moment.

3. Analyse du code source

La partie la plus puissante de cet outil est que la vue de gonflage asynchrone ne présente pas de problèmes liés à la sécurité des threads. Voyons comment elle gère la sécurité des threads.

Tout d'abord, il existe une seule instance de Thread, qui possède une file d'attente de blocage thread-safe et un pool d'objets thread-safe.

Une méthode de ce singleton est la méthode enqueue, qui appellera le put de la file d'attente de blocage et insèrera la requête dans la file d'attente. Comme il s'agit d'une file d'attente thread-safe + d'un pool d'objets thread-safe, cette série d'opérations garantit la sécurité des threads.

Voici le processus de gonflage. Lors du gonflage, une demande sera obtenue à partir du pool d'objets via mInflateThread.obtainRequest, puis la demande sera insérée dans la file d'attente.

下面是一个简化过的代码,run 中有一个死循环,通过阻塞队列的 take 元素进行 inflate 的操作。

以上这个简单的工具就分析完了。这部分基本就回答了线程间如何同步数据的一个问题,在一个典型的生产者消费者模型中加入线程安全的容器即可保证。

4. 问题与改进

在使用中还是遇到很多线程相关的问题,以下列举几点相对重要的问题进行阐述。

4.1 单线程与多线程

InflateThread 在这里的设计是一个单例单线程,当需要对线程有一些定制或者收拢的话,改动就有些麻烦了,这里可以通过开放一个设置线程池的方法来提供一些线程管理和定制的能力,默认可以内置一个单线程的线程池。

通过比较长时间的实验我们发现,在主线程比较空闲的时候,单线程的效果会好一些,因为都在大核上执行了,效率更高。主线程繁忙的时候,例如冷启阶段,多线程会有更好的效率。

4.2 ArrayMap 与线程安全

我们在实际使用中发现,在一些自定义 View 的构造函数中和 darkmode 的实现中使用了 SimpleArrayMap 或 ArrayMap,ArrayMap 是 SimpleArrayMap 的子类,本身 SimpleArrayMap 是用过两个 static 的数组来实现对象的缓存,从而起到复用的作用,在多线程的情况下会有线程安全问题,这里会出现复用对象不匹配导致的 crash。一个简单的方式就是当出现 crash 的时候讲对应的 cache 数组清空,即可避免。

4.3 inflate、锁与线程安全

LayoutInflater 的 inflate 方法中有一个锁,这个导致了如果你想多线程去调用 inflate 的时候,起不到多线程的效果,如果是单线程的情况下,还可能遇到和主线程在 inflate 时同样等待锁的问题。这里 mConstructorArgs 是一个成员变量,通过重写 LayoutInflater 中的 cloneInContext 方法,配合对象池就可以避开这里锁的问题。

同时 inflate 过程中用到的这些数组和容器类型,都不是线程安全的,如果想要去掉 inflate 方法开头的 synchronize 的限制,这些线程不安全的容器类也是需要特别注意的。

4.4 BasicInflater 改造

AsyncLayoutInflater 本身有一个 BasicInflater,根据以上的一些改进点,我们在实践中对其做了一些改造,扩展出了可以设置线程池的接口,使用了基础架构提供的线程池,做到了对线程的统一管理。实践下来,在CPU比较繁忙的时候,多线程的线程池效果要好于单线程,当 CPU 比较空闲的时候,单线程的效果会更好一些,因为可以更好的利用释放出来的CPU 大核的性能。

同时重写了 ArrayMap 中线程不安全的一些处理方式,使得在多线程使用 ArrayMap 或者使用依赖 ArrayMap 的功能时不会出现 crash,这里涉及到了我们的一些自定义 View 和我们的 darkmode 的实现。

在对于 inflate 的锁和一些线程不安全的容器处理上,重写了LayoutInflater 的 cloneInContext 方法去掉了 synchronized 的限制,同时在 onCreateView 的流程中加入了线程安全的容器来保障 inflate 过程的线程安全。

综合来说就是重写了 AsyncLayoutInflater,ArrayMap 和 LayoutInflater,以达到线程安全的目的,同时将这些融入到我们的业务框架中,使得使用成本更低。

4.5  ViewCache

另一个实践是在业务侧做了进一步的封装,通过一个 ViewCache  的单例,提前将一些模块化的 View 提前 inflate 好,存在 ViewCache 中,在后续需要使用的时候从 ViewCache 中在获取,这样就避免了用的时候再 inflate 导致的耗时问题了。这块整体的代码比较简单,就不单独展开讲了,需要注意的点是有些 View 没有被使用需要及时释放,避免内存泄漏。

5. 总结

AsyncLayoutInflater 的实践与优化,前后持续了半年左右,我们在 App 冷启动和笔记详情页的性能优化中获得了超过的 20% 的性能收益以及显著的业务收益。同时,我们也将这个能力沉淀了到了业务框架中,方便了后续的接入和使用成本,通过 ViewCache 和业务框架,基本做到了可以覆盖大部分业务需求的能力。未来,我们将会在框架的易用性以及一些场景的使用上做进一步的优化,结合其他的优化手段给业务方提供更多的选择,使其能在写业务的同时无需关注这部分的耗时与复杂度,从而提升开发效率。

六、作者信息

殇不患

小红书商业技术 Android 工程师,曾负责业务架构设计与性能优化,目前专注于交易链路的迭代与优化。

Guess you like

Origin juejin.im/post/7220230543006236731