2天时间,聊今年最热的 Agent、上下文工程、AI 产品创新等话题。2025 年最后一场~ 了解详情
写点什么

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:356411

评论 2 条评论

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

一款宝藏工具:Apipost

Xd

Java 后端 接口测试

项目研发流程规范

源字节1号

读书笔记之《深入理解Java虚拟机:JVM高级特性与最佳实践》

蔡农曰

Java JVM 后端开发 后端技术

Java中二进制转换的多种方法

工程师日月

Java’ 7月月更

设计微博系统中"微博评论"的高性能高可用计算架构

Geek_7a789a

Qt | 播放音频文件 QMediaplayer

YOLO.

音视频 qt 多媒体 播放 7月月更

动态注册广播流程学习

北洋

Andriod 7月月更

【Docker 那些事儿】如何安全地进入到容器内部

Albert Edison

Docker Kubernetes 容器 云原生 7月月更

【LeetCode】装满杯子需要的最短总时长Java题解

Albert

LeetCode 7月月更

拆分电商系统为微服务

Geek_7a789a

冻结JS对象方法技术详解

猪痞恶霸

js 7月月更

2022 读过的书 -- 《Github入门与实践》

SkyFire

GitHub 读书

TiFlash 源码解读(四) | TiFlash DDL 模块设计及实现分析

TiDB 社区干货传送门

集群管理

Python Asyncio 二探:使用和用途

宇宙之一粟

Python asyncio 7月月更

kafka 生产者分区策略演进

丛培欣

kafka

企业无忧 | Apipost私有化部署活动 火热开启

Xd

后端 接口测试工具

Envoy整体架构概述

阿泽🧸

envoy 7月月更

小程序视图容器

小恺

7月月更

小程序电商业务微服务设计与基础设施选型

Geek_e8bfe4

2022读过的书--《操作系统导论》

SkyFire

读书 操作系统

架构实战营 模块六作业

挖了蘑菇哩斯

架构实战营

架构实战营模块 6 作业

Naoki

架构实战营

大风车吱呀吱哟哟地转,这个风车加载指示组件真有趣!

岛上码农

flutter ios 前端 安卓开发 7月月更

TypeScript学习后续之受挫的经历

为自己带盐

7月月更

【刷题记录】6. Z 字形变换

WangNing

7月月更

Java注解详情与自定义注解实战

宁在春

aop 注解 反射 Java’ 7月月更

应用容器化迁移步骤

穿过生命散发芬芳

应用迁移 7月月更

leetcode 312. Burst Balloons 戳气球(困难)

okokabcd

LeetCode 动态规划 分治 数据结构与算法

Electron入门【01】

坚果

7月月更

机器学习-回归分析

AIWeker

机器学习 回归分析 7月月更

ArrayBlockingQueue源码分析-新增和获取数据

zarmnosaj

7月月更

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