作者:闲鱼技术——三莅
本期内容
一个小技巧快速鉴定Flutter页面
Flutter页面在iOS高刷机上FPS竟然比原生页面更高?
如何在Android平台建立流畅度的统一指标
rasterCache命中率影响因素
一个小技巧快速鉴定Flutter页面
一个简单的小技巧快速判断某个页面是不是flutter页面:
双指/三指滑动页面,页面滚动速度是滑动速度的两倍/三倍
原生页面:
Flutter页面:
主要原因是在多指拖动事件处理中,flutter的处理逻辑是累加多个手指的操作效果 大家可以本地尝试通过记录每个手指的移动路径,在handleEvent中获取平均移动Offset�(而不是累加效果)来解决这个问题
void _checkMultiPointerUpdate() {
if (_multiPointerMoveTrackers.isEmpty) {
return;
}
final Offset localDelta = _getMultiPointerLocalDelta();
final Offset position = _getMultiPointerPosition();
final Offset localPosition = _getMultiPointerLocalPosition();
_checkUpdate(
sourceTimeStamp: _multiPointerMoveTrackers.last.timeStamp,
delta: _getDeltaForDetails(localDelta),
primaryDelta: _getPrimaryValueFromOffset(localDelta),
globalPosition: position,
localPosition: localPosition,
);
}
Flutter页面在iOS高刷机上FPS竟然比原生页面更高?
最近在跑性能测试对比各个页面的FPS数据,发现在ios高刷机上flutter页面FPS数据更高 具体看滑动曲线,发现Flutter页面的大部分场景都是120,少数卡顿场景低于120,
但是原生页面FPS会在60~120之间动态浮动,少数情况能上升到120
通过CADisplayLink回调也可以看到实时帧耗时,原生页面大部分场景在16.6ms左右,flutter页面滚动场景维持在8.3ms左右 要了解这背后的原理就要先了解iOS上的动态刷新率支持ProMotion
ProMotion 是 iOS 在支持高刷之后出现的动态刷新率支持,也就是不同场景使用不同的屏幕刷新率,目的是实现体验上提升的同时降低了电池的消耗。
所以即使在设置高刷打开的情况下,出于体验和能耗的综合考虑,原生页面也不会默认维持120HZ的帧率,而Flutter Engine在启动时会限制刷新频率为屏幕最大刷新频率,所以大部分大部分场景可以维持在120HZ左右 vsync_waiter_ios.cc
- (instancetype)initWithTaskRunner:(fml::RefPtr<fml::TaskRunner>)task_runner
callback:(flutter::VsyncWaiter::Callback)callback {
self = [super init];
if (self) {
current_refresh_rate_ = [DisplayLinkManager displayRefreshRate];
_allowPauseAfterVsync = YES;
callback_ = std::move(callback);
display_link_ = fml::scoped_nsobject<CADisplayLink> {
[[CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)] retain]
};
display_link_.get().paused = YES;
[self setMaxRefreshRateIfEnabled];
}
return self;
}
- (void)setMaxRefreshRateIfEnabled {
NSNumber* minimumFrameRateDisabled =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"];
if (![minimumFrameRateDisabled boolValue]) {
return;
}
double maxFrameRate = fmax([DisplayLinkManager displayRefreshRate], 60);
double minFrameRate = fmax(maxFrameRate / 2, 60);
if (@available(iOS 15.0, *)) {
display_link_.get().preferredFrameRateRange =
CAFrameRateRangeMake(minFrameRate, maxFrameRate, maxFrameRate);
} else if (@available(iOS 10.0, *)) {
display_link_.get().preferredFramesPerSecond = maxFrameRate;
}
}
如何在Android平台建立流畅度的统一指标
帧率是衡量移动App页面性能体验的重要指标,对于不同技术栈的页面,建立统一的帧率统计指标是做好体验优化的前提。
计算fps,首先想到的是系统命令dumpsys gfxinfo,但是它不适用于flutter,通过闲鱼可以观察到,dumpsys gfxinfo在flutter页面并不能获取到卡顿信息,因为gfxinfo统计的数据来自于平台渲染器,flutter通过OpenGL自渲染,不涉及平台的渲染器也就拿不到统计数据。
另一种方案是dumpsys SurfaceFlinger
具体命令和分析如下
adb shell dumpsys SurfaceFlinger --latency com.taobao.test/com.taobao.test.maincontainer.activity.MainActivity#0
第一行数字,表示当前的 VSYNC 间隔,单位纳秒
下面有127行数据列表,每行三个数字
- desiredPresentTime: 下一个 HW-VSYNC 的时间
- actualPresentTime:present fence时间戳(可以代表上屏时间)
- frameReadyTime: acquire fence时间戳
dumpsys SurfaceFlinger命令打印出最新 127 帧的 present fence 的 signal time,当某帧 present fence 被 signal 的时候,说明这一帧已经被显示到屏幕上了。所以可以通过** **present fence来计算出一秒内有多少帧被刷到屏幕上,从而统计出fps,这种统计方式的优势在于针对不同的技术栈,可以客观的得到屏幕真实的刷新次数,保证了数据的fps数据的准确性,也可以方便对比竞品数据。
rasterCache命中率影响因素
flutter3.3中官方新增了RasterCache实现了图层的间接光栅化,来优化raster线程光栅化耗时,下面我们来看下rasterCache是否命中受哪些因素的影响 有两种类型的RasterCacheItem DisplayListRasterCacheItem:对应DisplayListLayer的缓存,framework层对应PictureLayer� LayerRasterCacheItem:CacheableContainerLayer的缓存(ClipShapeLayer、ColorFilterLayer、ImageFilterLayer、OpacityLayer、ShaderMaskLayer的父类) 对于DisplayListRasterCacheItem**:** 缓存条件:
- will_change:标志layer后续会变化,不加入缓存
- is_complex:标志layer复杂度高,加入缓存
- complexity_score:通过DisplayList指令计算分,达到一定的复杂度,判断值得加入缓存
- visible且连续符合条件3次
对于LayerRasterCacheItem,缓存条件:
- PrerollContext不包含TextureLayer:TextureLayer内容变化不可控,不做缓存
- PrerollContext不包含PlatformViewLayer:PlatformViewLayer内容变化不可控,不做缓存
- 连续3次满足缓存条件
- ClipShapeLayer额外需要满足clip_behavior_ == Clip::antiAliasWithSaveLayer(Clip::antiAliasWithSaveLayer是高耗时操作,使用缓存策略,默认值Clip::hardEdge不进缓存)
并且限制每帧最多缓存3个,用来避免对当前帧的性能产生太大的影响
以DisplayListRasterCacheItem为例,整体流程如下: