iOS UI掉帧和卡顿优化解决方案记录
UI卡顿原理
在 VSync 信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 App,App 主线程开始在 CPU 中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。随后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。
从上面的图中可以看到,CPU 和 GPU 不论哪个阻碍了显示流程,都会造成掉帧现象。所以开发时,也需要分别对 CPU 和 GPU 压力进行评估和优化。
卡顿原因分析
1、 通过工具检查
- 通过Xcode 检查视图层级关系是否有异常 ------ 未发现视图层级设置不合理的问题
- 通过instruments 的Animation Hitch工具检测:------ 碰到问题,录制完了无数据(用iphone7录制有数据的,iphne14录制无数据?),iphone7检测结果发现掉帧确实很频繁
- 用模拟器检查是否有离屏渲染 ------- 确实有离屏渲染问题
2、看代码分析
- 复用问题
-
- cell上的lottie动画组件没有复用;
- cell上有大量的removeFromSuperView导致列表滑动过程中频繁的视图添加和删除,同时本来只需要更新显示内容的控件也没有得到复用;
- GPU离屏渲染的问题
-
- 阴影、圆角设置的方式 ------ 确有圆角设置导致的离屏渲染
- 光栅化设置 ------ 无
- 设置了组透明度为 YES,并且透明度不为 1 的layer ------ 无
- 使用了高斯模糊等 ------ 无
- 渲染耗时
-
- 图片体积是否还有压缩的空间 ------ 商品图片和店铺头像确实有进一步裁减压缩的空间
- 滑动过程中停止lottie动画
- 渐变图层换成图片 ------ 减少GPU渲染耗时
- 检查是否有线程不合理问题
-
- 主线程耗时任务,比如调用UIGraphicsGetCurrentContext等接口在 CPU 上进行绘制计算 ------ 无
- 主线程等待繁忙的子线程或低优先级的后台线程任务而导致阻塞,或者在滑动过程中主线程有耗时方法 ------ 往下滑动加载下一页数据时,确实有耗时的操作放在主线程
- 主线程等待系统资源,比如使用Data(contentsOf:)进行 IO 读取等 ------ 无
- 内存占用过度:列表一直往下刷新内存会爆掉,可能存在内存泄漏,需要进一步看看
-
- 可以用内存工具检测看看有没有异常
- 看代码里有没有循环引用等引起内存问题的代码
- 缓存是否不合理
优化步骤和测试点
第一阶段 ------ 10.5.60
优化点:
- 对列表Cell的顶部的播放组件重构优化
-
- 优化代码结构;
- 避免在刷新数据的时候添加和删除界面元素;
- 优化左上角lottie动画的加载方式,避免每次刷新数据都更新Lottie动画,达到复用的目的;
第二阶段 ------ 10.5.66
优化点
- 双列Cell底部组件反复的添加和删除逻辑优化;
- 解决离屏渲染问题;
- 商品图片和介绍图预加载并裁减来减少体积(图片从100~200Byte,减少到几个Byte);
- 解决滑动过程中停止播放器定时器生效导致播放器一直在播放引起的掉帧;
- 渐变背景图层换成图片;
- 加载下一页数据代码中主线程耗时方法,尽量放到非主线程的异步线程;
- 滑动过程中,停止lottie动画;
- 优化获取size的逻辑:内部控件固定的size计算好后缓存下来;
- 减少frame的设置刷新;
- 播放器代码从UI组件抽取出来;
- 解决一个历史遗留bug;
- 显示封面图时增加渐显动画
优化总结
优化项总结
掉帧和卡顿相关优化
- 修改Cell上UI控件反复的添加和删除逻辑;
-
- cell是复用的,已经添加在cell的控件,如果刷新数据时需要隐藏的使用hidden而不是重复remove/add(这样修改测试重点要看下会不会因为cell的复用导致数据显示没有更新过来);
- lottie的url获取和创建优化:url是从config server获取的,之前是每次刷新数据都获取,改成创建控件的时候获取一次;创建之前也是每次刷新数据的收获都重新创建,改成只创建一次;
- 解决离屏渲染问题;
- 商品图片和介绍图预加载并裁减来减少体积(图片从100~200Byte,减少到几个Byte);
- 解决滑动过程中停止播放器定时器生效导致播放器一直在播放引起的掉帧;(这是个程序Bug,是造成滑动列表过程中掉帧很重要很隐蔽也是个很干扰排查的原因!)
- 渐变背景图层换成图片;
- 排查加载下一页数据代码中主线程耗时方法,尽量放到非主线程的异步线程;
- 滑动过程中,停止lottie动画;
- 优化获取size的逻辑:内部控件固定的size计算好后缓存下来;
- 减少frame的设置刷新;
体验优化
- 显示封面图时增加渐显动画
其他优化
- 播放器代码从UI组件抽取出来;
- 相同模版合并;
改善结果
改善前:无论是下拉加载数据,还是数据加载完毕往上滑动列表,都有明显掉帧和卡顿现象
改善后:往上滑动或者数据加载完毕再往下滑动,完全不会掉帧和卡顿
遗留问题
往下加载数据偶尔会出现掉帧现象。
分析可能的原因是往下滑动加载下一页数据时,接口数据回调回来后调用框架的CSCollectionViewDataManager类的addCardInstances方法在主线程耗时27ms左右导致掉帧。
心得体会
- 因为引起掉帧的因素是多方面的,一开始无从查起;可以先把最明显的一些优化点先改好,再慢慢用排除法去定位哪些模块引起问题的,从而再进一步针对的排查;
- 重构完一个单元,进行一次自测;
- 从明显的关键问题入手分析改造;
- 采用排除法排除有问题的地方能快速定位问题,但是要注意排除干扰因素;比如排除抓包干扰,抓包的时候界面会变的更加卡顿掉帧,所以记得关掉antbox或者删除掉抓包相关的依赖库;
- 排查过程中,确保单一场景。比如列表往下滑和往上滑其实是不同的场景(往下拉时会触发加载更多数据等一系列的动作),应该分别去分析两个场景下引起掉帧的原因。
其他杂记
排查法检查卡顿点过程记录
单元模块排除法找出引起掉帧的主要因素
场景:
1、往下拉
2、往上(回)拉
前提:注释播放器play和stop逻辑;注释起播加速;
步骤
1、注释topview所有组件,只留下bottomview
测试滑动效果:
往下滑动时,仍然会掉帧
网上滑动,不掉帧
结论:
1、可能商品图片加载引起掉帧
2、加载新数据引起掉帧
进一步测试:注释商品图片加载代码
测试滑动效果:
往下滑动加载新数据,还是会有些许掉帧 —— 加载新数据有可能存在阻塞主线程的方法
往上滑动不掉帧
2、 放出_introView(图片和渐变背景先隐藏)
测试滑动效果:
往下滑动,加载新数据有些掉帧;
往上滑动,基本不掉帧;再次往下滑动,也基本不掉帧了;
3、 放出tagview(lottie先隐藏)
测试滑动效果:同上
4、 放出tagview的lottie
说明:
1、 lottie的url从config server取的,初始化控件的时候取一次
2、 lottie的创建(调用框架的方法,异步获取, 并开启了缓存(降级场景只能取到静态图?))
测试滑动效果:同上
5、 放出_introView的渐变背景
测试滑动效果:同上
6、 释放封面图预加载代码
测试滑动效果:同上; 感觉往下加载新数据时,卡顿更严重一些,等分析加载新数据卡顿原因时再进一步分析;
7、 释放goodsview图片
测试滑动效果:同上;
8、 释放_introView图片加载
测试滑动效果:
往下加载新数据会掉帧;
往上滑动也出现了偶尔掉帧;
9、重新隐藏_introView图片加载再测试一遍
测试滑动效果:往回拉时基本不掉帧。
得出结论:同时只显示一张图片时,往回拉基本不掉帧。增加一张图片往回拉出现掉帧。
10、只加载封面图,隐藏其他图片加载
测试滑动效果:同2
11、释放_introView图片加载
测试滑动效果:掉帧不明显
12、释放goodsview图片加载
测试滑动效果:往回掉帧
13、goods和intro的图片都预加载
Goods预加载前224b -> 预加载 14b
Intro预加载前104b -> 预加载 8b
接下来,逐步分析加载新数据掉帧原因
方法:从日志台打印的关键日志入手,找到往回拉和往下拉日志差别;找到了didTriggerPaging方法;从这个方法入手终于揪出了两个主线程耗时方法
1、addCardInstances 耗时20多ms
2、preHandleCardInstances 平均耗时4~6ms
遗留的问题
- 记得看下预览的cell是否有离屏渲染问题
- 预加载图片两次,调查一下原因(打了两次log);
- 加载出错时,重新加载逻辑貌似现在没有;
- 启播加速放到异步线程?— 发现不影响
- reloadWithThemeConfigVO应该没有必要每加载一次都去拉取,可以优化
- addCardInstances耗时是否真的影响列表渲染?
- 播放器的播放会掉帧,注释掉播放器,完全不掉帧 —— 播放器没有停止播放,会引起掉帧
- 往下加载数据:注释掉预加载图片的代码还是会掉帧,说明预加载图片不是引起掉帧的原因?
- MLPLiveRecommendView里也有个image,但是这样的数据很少,没必要放到预加载图片
结论一二
解决播放器引起的掉帧问题(滑动过程中没有停止播放)
发现的问题
已解决:
- cell上的lottie动画组件没有复用
- cell上有大量的removeFromSuperView可能导致卡顿,代码可以优化(是为了避免数据重复,在prepareForReuse里使用,可以换成在这里刷新数据???)
- 圆角设置的方式导致离屏渲染
未解决:
- 滑动结束,一堆的cell调用了stop
- feeds接口请求参数exclContentIds随着分页数增加,数组越来越大,是必要传的吗
- 一直刷新,内存爆掉闪退(可能不是这个模块的问题)
重构风险点
- 1、preview类型数据没有自测过,记得mock数据自测
- 2、有recommend的数据也没自测过
- 3、为了本地跑真机在工程文件里去掉了一个purchase的勾选,并提交了
- 4、开关mock数据测试一下开关是否生效
- 5、测试一下开关是在什么场景下生效(每次进入直播广场?)
- 6、偶现刷着刷着闪退
待解决的问题
- 1、找到检测卡顿的工具 —— instruments的animation hitch 用iphone7(iOS14)可以录制
新学到的知识点
- 同一父视图重复调用addSubviews添加同一个View并不会产生多层级
- 测试性能时不要链接抓包工具(可删除APMockData库减少干扰);最好断开debug链接;
- 三板斧:可监控(日志、埋点)、可灰度(发布阶段)、可回滚(开关控制)
- mPaas框架
- ARC下block不需要copy,因为属性是strong,系统会将block自动拷贝到堆区,就不会有block捕获的变量值不确定的问题了
- alpha属性
1、 =0时,不会从响应链中移除
2、更改默认是有动画的
3、 hidden隐藏性能更高
4、会影响自己和subview的透明度
- opaque: 给绘图系统提供一个性能优化开关
1、UIView当有背景颜色时:并且背景颜色有透明度(透明度不为1时),将 opaque设置为YES性能较高。
2、UIVIew有背景颜色时:并且背景颜色的透明度为1,opaque的值不影 响性能。
3、UIVIew没有背景颜色时:opaque的值不影响性能。
- CALayer的opacity
耗时点
- 卡顿检测工具使用测试
- 代码熟悉过程,Cell模版注入原理(多亏师兄的指点,缩短了理解时间)
- 每修改完一个组件,需要花大量时间mock数据模拟各种场景和参数自测
- 采用排除法测试是哪个组件导致卡顿时,注册掉一个加载图片的组件,发现流畅很多;但是分析代码不知道什么原因导致卡顿,然后一顿排除测试:
怀疑是imageview的背景色设置MLP_C_FFFFFF_NODARK导致的
怀疑是图片资源的问题,更换了资源确实卡顿好很多
但是,最后发现断开antbox连接后卡顿好很多(可删除APMockData库减少干扰) - 编译瓶颈
-
- Xcode调试成为瓶颈(模拟器看离屏渲染有时跑不起来;修改一次代码,编译要好几分钟)
- 发现注释代码后,编译时间变长;
- 取消注释的代码,编译时间也很长;
- 什么代码也没改,重新编译时间也很长;看来编译时间可能跟网络环境有关(同样的电脑,同样的工程,不一样的网络环境)
后记
参考文档
Instruments性能检测
Instruments性能检测 - 简书
Animation Hitch:
iOS 底层原理39:Instruments系列(三)Animation Hitches - 简书
iOS渲染流程和卡顿分析工具-Animation Hitches - 掘金
iOS 性能检测新方式——AnimationHitches - 知乎
iOS卡顿检测:
iOS卡顿检测 - 简书
UICollectionView复用:
UICollectionView缓存机制探究 - 简书
2016笔记——UICollectionView复用 - 简书
dispatch_sync_on_main_queue:
如何安全使用dispatch_sync - 简书
圆角设置:
iOS 设置圆角、指定位置圆角及 iOS 11圆角设置 - 简书
离屏渲染:
iOS 离屏渲染探究 - 掘金
iOS圆角的离屏渲染,你真的弄明白了吗 - 掘金
iOS 渲染原理解析:
iOS 渲染原理解析
iOS 离屏渲染原理及优化(很有参考价值):
[转] iOS离屏渲染原理及优化 · Tenloy's Blog
设置圆角性能测评:
iOS设置圆角的4种方法实例(附性能评测)
卡顿原理和解决方案:iOS 深入理解列表卡顿原理和滑动优化方案_苹果手机上的列表滑动问题-CSDN博客
图片的渲染过程与优化(非常有用):iosiOS图片的渲染过程与性能优化 - 简书
相关文章:

iOS UI掉帧和卡顿优化解决方案记录
UI卡顿原理 在 VSync 信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 App,App 主线程开始在 CPU 中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行…...

transbigdata 笔记: 轨迹密集化/稀疏化 轨迹平滑
1 密集化 transbigdata.traj_densify(data, col[Vehicleid, Time, Lng, Lat], timegap15) 轨迹致密化,保证至多每隔timegap秒都有一个轨迹点 这边插补使用的是pandas的interpolate,method设置的是index 1.1 举例 transbigdata 笔记: 官方…...

反向代理的本质是什么?
反向代理是一种网络架构模式,通常用于提供静态内容、处理安全、负载均衡和缓存等任务。在这种架构中,客户端发送的请求首先到达反向代理服务器,然后由反向代理服务器将请求转发给后端的实际服务器。反向代理服务器可以处理和修改请求和响应&a…...

Kali Linux保姆级教程|零基础从入门到精通,看完这一篇就够了!(附工具包)
作为一名从事网络安全的技术人员,不懂Kali Linux的话,连脚本小子都算不上。 Kali Linux预装了数百种享誉盛名的渗透工具,使你可以更轻松地测试、破解以及进行与数字取证相关的任何其他工作。 今天给大家分享一套Kali Linux资料合集…...

UML-用例图
提示:用例图是软件建模的开始,软件建模中的其他图形都将以用例图为依据。用例图列举了系统所需要实现的所有功能,除了用于软件开发的需求分析阶段,也可用于软件的系统测试阶段。 UML-用例图 一、用例图的基础知识1.用例图的构成元…...

jmeter--8.加密传输
目录 1. Base64加密 2. MD5加密 3. SHA加密(sha1\sha\sha224\sha256\sha384\sha512) 4. RSA加密-公钥加密,私钥解密 1. Base64加密 1.1 在需要加密传输的接口下新增BeanShell 预处理程序,${username}可替换成value值ÿ…...
微信小程序canvas画布转图片转pdf文件
关键步骤介绍 步骤一:将canvas页面保存为图片 for(var a=0;a<this.data.page_canvas.length;++a){ var t_page_img = await this.canvas_to_image(this.data.page_canvas[a]) t_img.push(t_page_img) } this.data.page_canvas是保存的canvas界面,this.c…...

【Linux操作】国产Linux服务管理操作
【Linux操作】国产Linux服务管理操作 前言SAMBA配置服务器端1. 安装相关包2. 配置/etc/samba/smb.conf,在此文件末尾添加如下内容,并保存退出。3. 创建/home/share并更改权限4. 启动samba服务 客户端• Windows客户端• 麒麟客户端 Telnet1、telnet语法2…...

大语言模型系列-word2vec
文章目录 前言一、word2vec的网络结构和流程1.Skip-Gram模型2.CBOW模型 二、word2vec的训练机制1. Hierarchical softmax2. Negative Sampling 总结 前言 在前文大语言模型系列-总述已经提到传统NLP的一般流程: 创建语料库 > 数据预处理 > 分词向量化 > …...

vue项目运行报错this[kHandle] = new _Hash(algorithm, xofLen)
自从昨天分盘重装了最新版本的Node之后,项目是一启一个报错 出现这个报错时,需要在package.json文件中 dev命令行 增加:set NODE_OPTIONS–openssl-legacy-provider 出现该问题的原因: node.js V17开始版本中发布的是OpenSSL3.0,…...

APP兼容性测试,这几个面试硬技能,包教包会
兼容性测试主要通过人工或自动化的方式,在需要覆盖的终端设备上进行功能用例执行,查看软件性能、稳定性等是否正常。 对于需要覆盖的终端设备,大型互联网公司,像 BAT,基本都有自己的测试实验室,拥有大量终…...

【学习iOS高质量开发】——熟悉Objective-C
文章目录 一、Objective-C的起源1.OC和其它面向对象语言2.OC和C语言3.要点 二、在类的头文件中尽量少引用其他头文件1.OC的文件2.向前声明的好处3.如何正确引入头文件4.要点 三、多用字面量语法,少用与之等价的方法1.何为字面量语法2.字面数值3.字面量数组4.字面量字…...

Qt/QML编程之路:Grid、GridLayout、GridView、Repeater(33)
GRID网格用处非常大,不仅在excel中,在GUI中,也是非常重要的一种控件。 Grid 网格是一种以网格形式定位其子项的类型。网格创建一个足够大的单元格网格,以容纳其所有子项,并将这些项从左到右、从上到下放置在单元格中。每个项目都位于其单元格的左上角,位置为(0,0)。…...

mac pro “RESP.app”意外退出 redis desktop manager
文章目录 redis desktop manager下载地址提示程序含有恶意代码“RESP.app”意外退出解决办法:下载python3.10.并安装重新打开RESP如果还是不行,那么需要替换错误路径(我的没用)外传 最近在研究redis的消息,看到了strea…...

VirtualBox 如何让虚拟机和主机互相通信
首先建立一张虚拟网卡 在这里进行网络设置 设置成固定ip,这张网卡专门用来通信,上面的网卡用来上网的...

【Java】源码文件开头添加注释
需求 应公司质量部要求,需要对代码做静态检查。质量部要求,源码文件必须在起始行起设置一些注释,然而项目已经开发了一年之久,且没有维护这个注释。 此时,面对好几千个源码文件,我们如何快速添加相应的注…...
GitHub 异常 - 无法连接22端口 Connection timed out
GitHub 异常 - 无法连接22端口 Connection timed out 问题描述 错误信息: 今天突然用ssh方式 pull GitHub的项目报:ssh: connect to host xx.xx.xx.xx port 22: Connection timed out 表明 SSH 连接在尝试通过 22 端口连接到远程服务器时超时。这可能是由于网络环…...

python基础学习
缩⼩图像(或称为下采样(subsampled)或降采样(downsampled))的主要⽬的有两个:1、使得图像符合显⽰区域的⼤⼩;2、⽣成对应图像的缩略图。 放⼤图像(或称为上采样…...

Python密码本连接wifi
有时候我们会忘记自己的Wi-Fi密码,或者需要连接某个Wi-Fi网络以满足合法需求。本文将介绍如何使用Python编程语言编写一个简单的连接Wi-Fi的程序。 一、密码本准备 在进行wifi猜测时,其实就是列出各种可能的密码,用来尝试去访问目标wifi&…...
Docker 设置 Redis 的密码失效
在网上找了设置Docker里的设置Redis密码,一段时间就失效了 1. 进入redis的容器 docker exec -it 容器ID redis-cli2. config set requirepass 密码 解决方法 1. 创建 redis.conf 配置文件 # Redis configuration file example. # # Note that in order to read the configu…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
云原生周刊:k0s 成为 CNCF 沙箱项目
开源项目推荐 HAMi HAMi(原名 k8s‑vGPU‑scheduler)是一款 CNCF Sandbox 级别的开源 K8s 中间件,通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度,为容器提供统一接口,实现细粒度资源配额…...
0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化
是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可,…...

GraphQL 实战篇:Apollo Client 配置与缓存
GraphQL 实战篇:Apollo Client 配置与缓存 上一篇:GraphQL 入门篇:基础查询语法 依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在: https://github.com/GoldenaArcher/graphql…...