likes
comments
collection
share

2月Flutter小报|读小报,涨知识

作者站长头像
站长
· 阅读数 7

作者:闲鱼技术——三莅

本期内容

一个小技巧快速鉴定Flutter页面

Flutter页面在iOS高刷机上FPS竟然比原生页面更高?

如何在Android平台建立流畅度的统一指标

rasterCache命中率影响因素

一个小技巧快速鉴定Flutter页面

一个简单的小技巧快速判断某个页面是不是flutter页面: 双指/三指滑动页面,页面滚动速度是滑动速度的两倍/三倍 原生页面: 2月Flutter小报|读小报,涨知识 Flutter页面: 2月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,

2月Flutter小报|读小报,涨知识 但是原生页面FPS会在60~120之间动态浮动,少数情况能上升到120 2月Flutter小报|读小报,涨知识

通过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自渲染,不涉及平台的渲染器也就拿不到统计数据。 2月Flutter小报|读小报,涨知识 另一种方案是dumpsys SurfaceFlinger 具体命令和分析如下 adb shell dumpsys SurfaceFlinger --latency com.taobao.test/com.taobao.test.maincontainer.activity.MainActivity#0 2月Flutter小报|读小报,涨知识 第一行数字,表示当前的 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数据的准确性,也可以方便对比竞品数据。 2月Flutter小报|读小报,涨知识

rasterCache命中率影响因素

flutter3.3中官方新增了RasterCache实现了图层的间接光栅化,来优化raster线程光栅化耗时,下面我们来看下rasterCache是否命中受哪些因素的影响 有两种类型的RasterCacheItem DisplayListRasterCacheItem:对应DisplayListLayer的缓存,framework层对应PictureLayer� LayerRasterCacheItem:CacheableContainerLayer的缓存(ClipShapeLayer、ColorFilterLayer、ImageFilterLayer、OpacityLayer、ShaderMaskLayer的父类) 对于DisplayListRasterCacheItem**:** 缓存条件:

  1. will_change:标志layer后续会变化,不加入缓存
  2. is_complex:标志layer复杂度高,加入缓存
  3. complexity_score:通过DisplayList指令计算分,达到一定的复杂度,判断值得加入缓存
  4. visible且连续符合条件3次

对于LayerRasterCacheItem,缓存条件:

  1. PrerollContext不包含TextureLayer:TextureLayer内容变化不可控,不做缓存
  2. PrerollContext不包含PlatformViewLayer:PlatformViewLayer内容变化不可控,不做缓存
  3. 连续3次满足缓存条件
  4. ClipShapeLayer额外需要满足clip_behavior_ == Clip::antiAliasWithSaveLayer(Clip::antiAliasWithSaveLayer是高耗时操作,使用缓存策略,默认值Clip::hardEdge不进缓存)

并且限制每帧最多缓存3个,用来避免对当前帧的性能产生太大的影响 以DisplayListRasterCacheItem为例,整体流程如下: 2月Flutter小报|读小报,涨知识