View的异步Inflate+全局缓存:加速你的页面

2020 年 5 月 24 日

View的异步Inflate+全局缓存:加速你的页面

一、背景


Android 在 View 的使用中,过多的布局文件 inflate 影响性能,尤其在一些滚动列表、样式种类很丰富的场景下,inflate 次数相对较多,整体 inflate 耗时就会增加,导致滚动过程卡顿。


所以,需要 View 的异步 inflate,甚至 View 的全局缓存,通过这些方式,去减少 UI 线程 inflate 的耗时及次数,以便减少卡顿,提升性能。


二、现有的解决方案


要实现 View 的异步 Inflate,最简单粗暴的方式是直接 new Thread 执行 inflate,或者使用 HandleThread+Handler 的方式,或者使用 android 官方 android.support.v4.view 包的 AsyncLayoutInflater 类。但都存在一个问题,就是如果 inflate view 实例的时候 view 的构造方法里面直接有 new Handler()(原本想新建一个主线程 Handler 用于 UI 操作),将会达不到要求。


ListView、RecycleView 等控件,自身实现了“缓存复用”机制,这使得滑动过程性能得以提升,但更多的是控件间、或者页面内的缓存复用,而对于页面之间(activity 与 activity)缓存复用,没一个有效的解决方案。当然,如果把 View 的缓存做成全局单例,是可以做到页面之间的缓存复用,但 View 和 context 强绑定在一起,全局缓存可能会导致 activity 实例不能正常释放,导致内存泄露问题;也可能在 context 使用上存在问题,如用 APPlication 实例传递给 View 的 context,代码中又直接把 context 当成 activity 用,将导致一些问题。


三、异步 Infllate+全局缓存问题分析及解决方案


从现有方案中,提炼出两个核心问题,一、异步 Inflater 的时候,View 中 new Handler 导致的安全问题;二、全局缓存,View 的 context 问题。


  1. 异步 Inflater 的时候,View 中 new Handler 导致的安全问题解决方案


对于直接 new Thread 执行 inflate,或者使用 HandleThread+Handler 的方式,或者使用 android 官方 android.support.v4.view 包的 AsyncLayoutInflater 类。


示例代码



三种方式的对比:



第一种和第三种方式,run 方法中由于没有使用 Looper.loop 机制,这使得 new Handler 即使调用 post 方法发消息,并不会正在执行,导致 UI 不能正常刷新。


第二种,虽然 new Handler 能正常工作,但如刷新 UI,很可能会 crash,比如如下的异常:



所以 Handler hander = new Handler(),往 handler 抛的消息需要抛到主线程,比如改写成 Handler hander = new Handler(Looper.getMainLooper())。但是我们无法更改 View 的 Handler 构造代码,下面方案通过了反射的方式,强制把后台的线程的 Looper 设置为 mainLooper,这样后台线程 new Handler()方式也能把消息抛到主线程消息队列



使用示例:



  1. 全局缓存,View 的 context 问题解决方案


在全局缓存时,为了解决创建 view 的 context 不一定是 activity 导致的问题,或者是 activity 导致的内存泄露问题,对 Context 做封装:新建了 ViewContext 代理类:



inflate 的时候,将用 ViewContext 传入 View,方式如下:



四、基本实施及使用


  1. 基本架构



  1. 实施思路


1)View 的缓存大小应控制,且可动态修改:在 View 的缓存方面,设计一个缓存大小机制,且允许动态的修改对应的缓存大小,这样可以根据具体需要设置,从而更好的控制内存使用;


2)缓存 View 的状态处理,方便管理。异步创建 View,放入缓存池并标记可用,每次从缓存池获取 View 后,标记状态不可用,待回收后在标记可用;


3)异步创建 View,可预加载。提供 View 的异步创建,并放入缓存中,结合预加载,能有效的减少实际创建 View 所需的耗时,提升性能;


4)内存管理策略–应用低内存自动释放缓存。通过 context 取到 APPlication 注册一个 ComponentCallbacks,监听 APP 内存状态,适当的释放缓存;


5)缓存优先级策略–可设置缓存释放优先级。提供设置缓存释放优先级的能力,业务方可以更精准的利用缓存,更好的满足业务所需;


6)View 创建方式。对于 View 的创建,可以设计一个 IViewCreeator,创建 View 的过程由使用方决定。如布局文件中只有 TextView,可以传入 layoutId 选择 new TextView()的方式。


  1. 实施



基本使用如下,新建单例(如 ViewHelper):



使用 ViewHelper 如下:



使用前后的效果:



  1. 注意事项


1)使用异步 Inflate+全局缓存构建的 View,在使用时需要重新设置 LayoutParams,不然显示上可能不是最终想要的结果;


2)使用异步 Inflate+全局缓存构建的 View,如果 View 的解析过程中,存在 Theme 相关的,可能会导致 View 构建失败。如原生的 TabLayout,解析时会读取 Theme 中的属性,如果 context 传入的是 APPlication 且没有设置相关 Theme,就会报错;


3)使用异步 Inflate+全局缓存构建的 View,需要及时的调用 refreshCurrentActivity 方法,这样尽可能的保持 context 是当前的 activity 实例。在使用 context 的时候,避免直接把 context 强转 activity,而是使用 ViewContext 的 getActivity 方法获取。


五、总结



选择异步 Inflate ,应根据需要,合理的选择。如只需 activity 级别的,选择原生 AsyncLayoutInflater 的方式,就能很好的满足要求,并且没有 context 使用问题。如想更早的准备或者跨页面复用,View 的异步 Inflate+全局缓存是更好的选择,但要注意 context 使用问题,因为 inflate 所需的 context 不一定是 activity,也正是这点使得单例缓存的 View,不用担心内存泄露问题,满足多页面的缓存复用。


目前,优酷在 AsyncView 项目已经实现 View 的异步 Inflate+全局缓存,该项目已经对公司内部开源,是 AIOSO 的子项目之一,也在进一步的落实对外开源。它是“低侵入式”的,没有对 Android 原生 UI 进行改造,Android UI 框架开发的 APP 都可以方便接入。该项目已经在优酷 APP 上大量使用,反馈效果良好,主要体现在:


1)在帧率方面,整体带来了 5%左右提升。尤其在低端机,体感上有明显的提升;


2)在启动方面,结合各业务端提前做一些预加载任务,整体带来了 20%左右的提升。


作者 | 阿里文娱高级无线开发工程师 瑞源


2020 年 5 月 24 日 18:351405

评论 1 条评论

发布
用户头像
forceSetMainLoop具体是什么原理呢?没看懂
2020 年 07 月 02 日 14:03
回复
没有更多评论了
发现更多内容

在培训机构花了好几万学Java,当了程序员还常被鄙视,这是招谁惹谁了?

四猿外

Java 学习 程序员 个人成长 转行程序员

避免争执

孙苏勇

职场 随笔杂谈

HTML中实现合并单元格

JDoe

html

ARTS|Week 1 第一次使用LeetCode

Puran

LeetCode ARTS活动

最香远程开发解决方案!手把手教你配置VS Code远程开发工具,工作效率提升N倍

柠檬橙

Linux 后台开发 vscode 后端

地铁上看书的老外引发的思考

小天同学

写作 读书 个人感想 日常思考

团队与领导力健康检查 | 体检表

Bob Jiang

团队建设

解决版权难题,“豪横”字体自己做

zhoo299

设计 CG

MySQL死锁系列-常见加锁场景分析

程序员历小冰

MySQL

时序数据库

pydata

纯CSS“返回顶部”特效

寇云

CSS css3

前端工程化之创建项目

春生

前端 前端工程 前端架构 全栈工程师

如何通过样本数据推断其分布

张利东

Python

自定义列表样式

寇云

CSS css3

重学 Java 设计模式:实战原型模式

小傅哥

Java 设计模式 小傅哥 复杂代码优化 重构

《中国互联网简史》系列笔记之P2P

dongh11

读书笔记

Eureka 实例注册状态保持 STARTING 的问题排查

张晓辉

spring Spring Cloud netflix

机器学习项目是如何开发和部署的?

陆道峰

人工智能 学习

偏头疼告诉我的,我想告诉每一个人

zkback

认识数据产品经理(四 与互联网产品经理的区别)

马踏飞机747

大数据 互联网 产品经理 职业规划

做好领路人——写给技术新人的导师建议

南方

管理 新人

为什么第三方联调应该先行?

大伟

python实现·十大排序算法之基数排序(Radix Sort)

南风以南

Python 排序算法 基数排序

Vol.9 Web前端发展历程及前端工程化

Lanpeng20

前端 前端工程

控制 Pod 内容器的启动顺序

张晓辉

Kubernetes

不懂送女朋友什么牌子的口红?没关系!Python 数据分析告诉你。

JackTian

Python 程序员 数据分析 python 爬虫 口红

只用CSS实现响应式Full-Width img 2种方法

寇云

CSS css3

写给产品经理的信(5):谈谈项目管理(青铜-王者)

夜来妖

产品 极客时间,项目管理 项目管理 产品经理 项目

Rust 遇上 C/C++(二):函数传参

Coding Fatty

c c++ rust 编程语言

一款开源且具有交互视图界面的实时 Web 日志分析工具!

JackTian

开源 GoAccess 实时 Web 日志分析工具 交互式查看器

为什么要学习 Markdown?究竟有什么用?

JackTian

markdown markdown语法 markdown编辑器

View的异步Inflate+全局缓存:加速你的页面-InfoQ