Android 性能优化(六):启动优化的详细流程
书接上文,Android 性能优化(一):闪退、卡顿、耗电、APK
从用户体验角度有四个性能优化方向:
- 追求稳定,防止崩溃
- 追求流畅,防止卡顿
- 追求续航,防止耗损
- 追求精简,防止臃肿
防止卡顿
卡顿的场景通常与用户交互体验最直接,分别为UI、启动、跳转、响应四个方面,如下图所示。
主要有两大因素:
- 界面绘制:层级深、元素复杂、频繁刷新等。由于这些原因导致卡顿的场景更多出现在启动后的初始界面以及跳转到页面的绘制上。
- 数据处理:数据处理量太大。一般分为三种情况,一是数据在处理 UI 线程,二是数据处理占用 CPU 高,导致主线程拿不到时间片,三是内存增加导致 GC 频繁,从而引起卡顿。
UI优化
主要是绘制和刷新的卡顿,典型表现在页面的列表的滑动加载和刷新。
针对绘制可以使用懒加载,针对刷新可以使用局部刷新。详情参考:
(四)RecycleView 滑动到置顶、Adapter局部刷新
(七)RecycleView 性能提升、卡顿优化
启动优化
启动速度是用户第一印象,过长会让用户失去耐心; 根据行业数据统计,启动速度与用户留存有着直接相关;
对于开发者来说,通过各种技术手段来提升启动性能缩减启动时长,对整站业务的各项指标提升都会有较大帮助。因此,启动优化是各个客户端团队在体验优化方向上十分重要的一环。
评估量化
在计划优化立项之前,先确定两件事。
一、目标任务的启动是否值得优化?
衡量一个任务是否要启动有两个关键指标:“投入产出比”、“产出风险比”。
- 投入产出比:当完成优化任务需要 3人/天,收获90分位 100ms 的优化产出;另一个只收获90分位 10ms 的优化产出。显而易见选前者。因为优化的前期追求的的是优化收益,等到后续开启二期任务、三期任务需要做到更加极致时,才会考虑成本高但收益低的任务。
- 产出风险比:当做优化任务时,可能会有风险点影响某个业务,这时需要衡量风险影响范围的大小,然后和业务方商讨劣化和优化的抉择,最后敲定任务排期。
二、优化的预期结果是否可量化?
有个比较简单的方法就是对标,比如,竞品App或主流App。
当然,启动优化最终目标是“秒开”,即需要 1s 内完成用户的启动流程。
量化建设
工作的价值体现就在于建设丰富的数据大盘来明确以什么形式、从什么角度、监控什么指标。
启动优化的整体监控可以为:
- 秒开率、2秒开率、3秒开率...
- 90分位总体性能、90分位各阶段性能;(波动较小,不会被极端的case影响曲线)
- 分版本各阶段各项指标、整体各阶段各项指标、主版本各阶段各项指标
- 分场景,如有无广告等等
- ...
耗时定义
业内常见的app启动过程阶段一般分为「启动阶段」和「首刷阶段」。
- 启动阶段:指用户点击icon到见到app的首页,起点为
Application
的onCreate,终点
Activity
的onWindowFocusChanged
()。
- 首刷阶段:指用户见到app的首页到首页列表内容展现起点为
Activity
的onCreate,
终点列表的onAttachedToWindow() 。
为了确保启动优化量化指标的数据能稳定和完整。因此,排除其他启动场景的统计:
- 启动过程中App退后台
- 用户未登录场景
- 特殊场景下的开屏广告,比如有复杂的联动动效
站外push、deeplink
拉起。
检测工具
Android Studio 自带的Profiler工具。
如果是 Android 10 及以上就用 Perfetto。
启动阶段
在App的启动流程中,有非常多的启动任务全部在Application的onCreate里被执行,有主线程的有非主线程的,但是不可避免的是,二者都会对启动的性能带来损耗。所以我们需要做的第一件重要的事情就是“启动任务重排与删减”。
可以把启动任务按优先级分类:
- 刚需高优任务:这类任务初始化,根本无法进入后续流程。所以,不可延迟,必须第一时间最高优先级执行。比如网络库、路由库、存储库等基础库的初始化。
- 非刚需高优任务:这类任务不初始化,不影响后续首页UI刷新,比如拉取ab实验配置、ip直连、push推送通知服务、长链接等基础建设项。
- 非刚需低优任务:这类任务对启动阶段后续流程无决定性影响,可以放在启动阶段结束之后再执行或懒加载。比如 x5内核、在线客服sdk、在线反馈SDK、登陆相关极验/深知SDK、LBS定位、搜索记录、城市json数据解析、Flutter/RN/Weex引擎初始化等。
TaskManager.getInstance().beginWith(A).then(B).then(C, D).then(E).enqueue();
TaskManager.getInstance().runAfterStartup({ xxx; })
通过任务的大致非精细化的排布,我们不仅仅可以对启动任务能够很好的进行监控,还可以更加容易的找出不合理项。
首刷阶段
从业务的角度出发,找到对启动流程影响最大的业务为切入点,尝试寻求更优的解法。
根据业务流程进行如下考虑:
- IO 请求优先级提高,例如开屏广告接口请求提前发起。
- IO wait期间,优化各项初始化,减少大量数据处理导致主线程拿不到时间片CPUTime。
- 首页UI复杂度简化,列表的第一屏数据尽量走缓存。
1、 SplashActivity与MainActivity合并
当App启动时,会先进入SplashActivity等广告接口返回展示广告,然后转进MainActivity。这个流程对性影响最大的点是:等广告接口时,无法对后续业务做预加载,业务都在MainActivity。另外,启动阶段需要连续启动两个Activity,至少带来 百毫秒 级别的劣化。
合并前后,流程图如下:
广告原先是以Activity的形式在展现,现在被抽离成 View 的形式去承载逻辑,在 onCreate 中,广告View添加进 DecorView中,完成对首页布局的遮罩。在广告View展示logo图时,底下首页框架是可以进行预加载和渲染。
补充注意事项:
MainActivity 作为launchAtivity的单实例问题。App从后台进前台,其他二级页面跳转到首页的依然走生命周期的现象。MainAcitvity 的 launchmode 需要设置为 singleTop。
首页弹窗管理器需要延迟初始化。在广告View覆盖在上面时,先暂停弹层管理器的生命周期,避免出现其他内容盖住广告。
2、多Tab延迟加载+懒加载
一般MainActivity中的Tab会以fragment形式被提起预加载。而首刷阶段的终点是在首页列表View的onAttachedToWindow()生命期中。
因此,可以考虑其他Tab进行延迟加载和懒加载。即用户不迫切切换Tab,就在首页View绘制时进行预加载;否则就在用户点击Tab切换事件中进行加载(可能卡顿劣化,需风险评估)。
3、布局优化
比如,广告View可以在接口返回数据且经过素材校验后,再进行布局的加载。类似的Banana位、搜索框、其他图片广告等。
- 布局复用,使用<include>标签重用layout;
- 提高显示速度,使用<ViewStub>延迟View加载;
- 减少层级,使用<merge>标签替换父级布局;
-
其他:使用wrap_content会增加measure计算成本;kotlin by lazy,动态内容加载的场景等。
4、绘制优化
过度绘制是指在屏幕上的某个像素在同一帧的时间内被绘制了多次。
在多层次重叠的 UI 结构中,如果不可见的 UI 也在做绘制的操作,就会导致某些像素区域被绘制了多次,从而浪费了多余的 CPU 以及 GPU 资源。
- 移除 XML 中非必须的背景,移除 Window 默认的背景、按需显示占位背景图片;
- 使用 canvas.clipRect() 识别可见区域,只在这个区域内才会被绘制。
可以通过LayoutInspector和Android的调试工具去各自分析了,如果打开LayoutInspector肉眼可见都是红色,赶紧改。
5、page cache
给首页的数据流增加页面级别缓存,让首页首刷不依赖接口返回就可以展示。
以上启动优化的点可以更详细的、更极致的优化,需要根据各自APP情况而定。
阶段成果
优化结果:
- 从2.87 降至最低时2.1,降幅26.8%
- 再进行优化后效果不明显,遇到瓶颈
优化瓶颈
瓶颈是什么?——APP加壳
- APP加固中的加壳,严重拖慢了APP启动性能;
- 且爱加密对加壳后的性能损耗无法优化;
如何突破瓶颈?——混淆+VMP
- 壳的作用:对APPDex文件进行加密,防止APP被反编译,导致源代码泄露;
- 替代方案: 对代码进行混淆,这也是行业通用做法 VMP加密,一种虚拟机加密技术,加密后将原有代码片段替换成新的片段,在执行时再解密;
- 去壳需得到安全部门认可: 通过三方机构对加壳、仅混淆、混淆+VMP加密包进行安全测试; 得出结论为安全性 混淆+VMP > 仅混淆 > 仅加壳
防劣化同样重要
在线上优化工作开展完成且取得了相应成果后,绝对不能放松警惕。
优化一次获得的效果并不是最重要的,最重要的是要有持续的、稳定的优化效果。
对于线上用户来说,其实可能并不关心这个版本或者这几个版本是不是变快了,大家可能更需要的是长时间的良好体验。
对于我们这些开发者来说,长时间的良好体验可能才能改变大家对自家 App 的性能印象,让大家认可自家 App 的启动性能,这也是我们优化的初衷。
因此,防劣化和优化同样重要。
相关文章:

Android 性能优化(六):启动优化的详细流程
书接上文,Android 性能优化(一):闪退、卡顿、耗电、APK 从用户体验角度有四个性能优化方向: 追求稳定,防止崩溃追求流畅,防止卡顿追求续航,防止耗损追求精简,防止臃肿 …...

QT程序打包
将exe文件单独拿出来放入一个单独的文件夹 保存qt安装路径下有如下这个文件 windeployqt.exe 在TCPFile.exe文件夹中使用以下cmd命令运行 即可打包 windeployqt 文件名.exe 成功打包!...

ARMday7作业
实现三个按键的中断,现象和代码 do_ipr.c #include "stm32mp1xx_gic.h" #include "stm32mp1xx_exti.h" extern void printf(const char *fmt, ...); unsigned int i 0; void do_irq(void) {//获取要处理的中断的中断号unsigned int irqnoGI…...
Unity构建详解(4)——SBP的依赖后处理
【AddHashToBundleNameTask】 这个Task的作用很明确,给Bundle的名字附加一个Hash值: 根据bundle所包含的asset计算出来一个hash值添加在原来Bundle的末尾替换了BundleBuildContent.BundleLayout和aa.bundleToAssetGroup中的key 有两个问题,…...

使用GO对PostgreSQL进行有意思的多线程压测
前言 针对PostgreSQL进行压缩,有很多相关的工具。有同学又要问了,为何还要再搞一个?比如,pgbench, sysbench之类的,已经很强大了。是的,它们都很强大。但有时候,在一些特殊的场景,可…...

CI/CI实战-jenkis结合gitlab 4
实时触发 安装gitlab插件 配置项目触发器 生成令牌并保存 配置gitlab 测试推送 gitlab的实时触发 添加jenkins节点 在jenkins节点上安装docker-ce 新建节点server3 安装git和jdx 在jenkins配置管理中添加节点并配置从节点 关闭master节点的构建任务数...

修复ubuntu引导
一、制作ubuntu启动U盘 进入启动盘后,点击Try ubuntu,进入U盘的ubuntu系统。 二、配置和添加源 sudo add-apt-repository ppa:yannubuntu/boot-repair && sudo apt-get update三、运行 Boot Repair重新制作引导 sudo boot-repair注意&#x…...

11.Notepad++
文章目录 一、下载和安装设置练习 以前在记事本上写的代码看上去有点累,因为所有的单词看上去都是黑色的,并且当代码出现问题后,它提示第三行,我们还需要一行一行去数。这些问题都可以由一个高级记事本: Notepad 来解…...
实现阻塞队列
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class Main { public static void main(String[] args) throws InterruptedException { BlockingQue…...
MySQL8.X驱动datetime映射问题
MySQL8.X驱动datetime映射问题 背景:项目由SSM项目迁移至SpringBoot,对mysql数据库驱动进行了升级导致出现问题。 原因:mysql驱动的8.X版本对数据库类型datetime映射为LocalDateTime。 解决:暂时不升级mysql驱动,mys…...

【Selenium】隐藏元素的定位和操作|隐藏与isDisplay方法
一、selenium 中隐藏元素如何定位? 如果单纯的定位的话,隐藏元素和普通不隐藏元素定位没啥区别,用正常定位方法就行了 但是吧~~~能定位到并不意味着能操作元素(如click,clear,send_keys) 二、隐藏元素 如下图有个输入框…...

视图的作用
目录 视图的作用 创建视图 为 scott 分配创建视图的权限 查询视图 复杂视图的创建 视图更新的限制问题 更新视图中数据的部门编号(视图的存在条件) 限制通过视图修改数据表内容 创建只读的视图 复杂视图创建 oracle从入门到总裁:h…...
动态ip白名单频繁更改问题解决方案
1. 使用静态IP地址:可以通过向ISP申请静态IP地址来解决动态IP地址的变化问题,但是这种方法会比较贵。 2. 使用动态DNS:可以使用动态DNS服务,它可以将动态IP地址映射到一个固定的域名,从而使得动态IP地址处理为域名一直…...

什么是物联网监控平台?部署物联网平台有什么作用?
随着物联网技术的飞速发展,越来越多的企业开始关注并投入到这一领域。物联网监控平台作为连接物理世界与数字世界的桥梁,正逐渐成为企业数字化转型的关键组件。今天,我们将深入探讨物联网监控平台的定义、部署物联网平台的作用,以…...

netty构建udp服务器以及发送报文到客户端客户端详细案例
目录 一、基于netty创建udp服务端以及对应通道设置关键 二、发送数据 三、netty中的ChannelOption常用参数说明 1、ChannelOption.SO_BACKLOG 2、ChannelOption.SO_REUSEADDR 3、ChannelOption.SO_KEEPALIVE 4、ChannelOption.SO_SNDBUF和ChannelOption.SO_RCVBUF 5、Ch…...
Selenium 学习(0.22)——软件测试之小结
Junit 等一系列自动化工具不用啰嗦了,自己就是小白再搞科普就装了。 把后面相关内容看了一下,使用这些测试工具一样编写代码(驱动模块【调用接口的代码片段】,桩模块【响应输出结果的代码片段,也就是被测模块调用的模块…...
贪心算法问题
分发饼干-455 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有…...
深入理解 @Transactional 注解在 Spring 中的应用
前言:在 Java 开发中,事务管理是非常重要的一环。Spring 框架提供了Transactional注解来简化事务管理的操作,本文将深入介绍Transactional注解的用法,并结合代码示例进行详细讨论。 1.Transactional 注解简介 Transactional注解是…...

Python爬虫之爬取网页图片
当我们想要下载网页的图片时,发现网页的图片太多了,无从下手,那我们写一个脚本来爬取呗。 这次的脚本是专门针对某个外国网站使用的,因此仅供参考思路。 在测试的过程中,我发现网站使用了发爬虫机制,具体就…...

AI Agent(LLM Agent)入门解读
1. 什么是AI Agent? AI Agent可以理解为一个智能体,包括感知模块、规划决策模块和行动模块,类似于人类的五官、大脑和肢体。它能帮助人类处理复杂的任务,并能根据环境反馈进行学习和调整。 五官可以理解为感知模块,大…...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...