写点什么

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

  • 2020-05-24
  • 本文字数:2389 字

    阅读完需:约 8 分钟

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-05-24 18:356288

评论 2 条评论

发布
用户头像
大佬有没有项目地址源码什么的牙
2023-05-30 18:51 · 重庆
回复
用户头像
forceSetMainLoop具体是什么原理呢?没看懂
2020-07-02 14:03
回复
没有更多了
发现更多内容

双目立体匹配之视差优化

秃头小苏

7月月更 双目立体匹配

web前端培训nodejs异步IO

@零度

node.js 前端开发

语音直播app源码

开源直播系统源码

直播系统源码 开源源码 语音直播系统源码

使用ServiceWorker提高性能

devpoint

JavaScript Service Worker 7月月更

系统首页 DIY,你的个性化需求 Pro 系统来满足!

CRMEB

会用redis吗?那还不快来了解下redis protocol

冉然学Java

Java 分布式 构架 Redis 数据结构

企事业单位该如何建设知识管理体系

Baklib

Java基本概念详解

五分钟学大数据

Java 7月月更

数据仓库分层——DWD DWS ADS傻傻分不清楚

怀瑾握瑜的嘉与嘉

数据仓库 7月月更

Qt|QWT绘制柱状图一类多种颜色

中国好公民st

qt 7月月更

Redis 过期的数据会被立马删除么?大有玄机

码哥字节

redis 底层原理 7月月更

解决浏览器回退表单重复提交问题

沃德

程序员 javaWeb 7月月更

多链多币种钱包系统开发跨链技术

薇電13242772558

钱包 跨链技术

【LeetCode】数组美丽值求和Java题解

Albert

LeetCode 7月月更

java培训之Java8 Stream 代码简化是如何实现的

@零度

stream JAVA开发

微软 Edge 浏览器 Tracking Prevention 的强制措施的一个例子

汪子熙

JavaScript microsoft 浏览器 前端开发 7月月更

某易跟帖频道,接口溯源分析,反爬新技巧,必掌握一下

梦想橡皮擦

Python 爬虫 Python爬虫 7月月更

Hexo在github上构建的博客

沃德

程序员 Hexo 博客 7月月更

对象的内存分配一定都是在堆空间吗?

领创集团Advance Intelligence Group

代码优化 内存分配

CSS神奇的卡片悬停交互效果

南城FE

CSS 前端 动画 鼠标悬浮 7月月更

龙芯高级工程师直播:视频编解码基础知识入门 | 第 31 期

OpenAnolis小助手

直播 基础 视频编解码 龙蜥大讲堂 龙芯中科

为什么说企业需要具备企业知识管理的能力?

Baklib

营销玩法多变,搞懂规则是关键!

CRMEB

Java 在Word文档中查找和高亮文本

在下毛毛雨

Java word文档 查找与高亮

全面打通 DevOps 数据链的研发效能度量平台

思码逸研发效能

开源 DevOps 研发效能 效能度量

FAQ制作工具推荐

Baklib

http请求redirect的问题

飞翔

golang

MySQL进阶(一)主外键讲解

No Silver Bullet

MySQL 数据库 7月月更 主外键

基于Qt设计的课堂考勤系统(采用RDS for MySQL云数据库 )

DS小龙哥

7月月更

基于SpringBoot 的MCMS系统,完全开源,直接商用太爽了

冉然学Java

Java 源码 springboot 构架

硅谷来信:Google、Facebook员工的“成长型思维”

博文视点Broadview

View的异步Inflate+全局缓存:加速你的页面_文化 & 方法_阿里巴巴文娱技术_InfoQ精选文章