Flutter 动态化热更新的思考与实践(六)---- 动态列表滚动优化

1,413 阅读4分钟

一、前言

很长时间没有更新Flutter动态化的文章了,这部分的开发主要是在公司正常工作之外做的实验性探索,都是利用零散时间一次一次重构和优化。截至目前已完成较为完整的版本,并且已试验性的上线了公司App一小块业务功能,进行小范围内试用。

Flutter动态化的原理和思路可以参照之前的文章,经过这段时间的优化实践,梳理了下目前动态化执行的流程:

dynamic_flutter_flow

Runtime 的执行过程如下:

runtime_flow

其中比较关键的是Runtime Parser ,主要功能是解析Ast语法树,并执行对应的语法功能,包含两个部分:

  1. 基础语法解析器
  2. 扩展解析器

详见下图:

runtime_parser


  ///解析器接口,用于解析如下表达式
  ///* target(int a, String b)
  ///* target({int a, String b})
  ///* target.property(int a, String b)
  ///* target.property({int a, String b})
  ///* target.property
  dynamic parse(
    AstRuntime runtime,
    dynamic target, {
    String property,
    List<Expression> arguments,
  });

解析器基本上是个体力活,枚举大部分常用语法和公共基础类,同时提供了一个扩展解析器的接口,如果需要解析自定义对象(如自定义UI组件或工具类等),实现此接口并添加到解析器中即可,跟随需求需要不断迭代补充完善解析器。

二、业务场景实战

我们要实践的业务功能是一个消息列表界面,样式如下:

通知提醒列表v2

界面的元素不算复杂,也是比较常见的列表界面,我们对这个界面进行动态化处理后的运行效果如下:

ppsyd-3mp47

可以明显看到在滚动的时候卡顿非常严重,体验非常不好

1. 问题分析

打开DevTool分析:

dev_tools_profile

一般人眼识别连贯动作的帧数是24fps,那么一帧的时长不宜超过40ms,通常我们感到比较舒适的帧数是60fps,也是目前比较常用的标准,那么一帧的时长就用控制在15ms以内。由上图可见,这个列表在滚动的时候,一帧的时长竟达到了560.4ms,滑动过程卡顿感非常强烈。问题的原因在DevTool上也能清楚看到,就是在解析器这块,递归调用了很多次,截图不够完整,下面其实还有很多调用,目前没有想到好的办法优化这部分,只能用这种比较粗暴的方法解析Ast树中的每个节点。

2. 解决思路

解析算法优化这条路暂时行不通(能力不足= =...,哪位大侠可以指点一二「抱拳」),剩下有几个优化的方向:

  1. 在子线程中执行解析器
  2. 解析器部分使用效率更高的语言实现,比如C/C++
  3. 利用CPU空闲时间,手动控制渲染时机

对这几个方向进行可行性分析:

首先,第一种方式调研后在Flutter中不太适用,我们知道Flutter中的线程是叫isolate,并且isolate不支持共享内存,多个isolate之间通过内部提供的端口类进行通信,通信的数据类型只能是基本数据类型和内部的SendPort对象,如果想传输其他类对象,除非序列化成基本数据类型进行传输。那么带来的一个问题就是针对Widget类型的对象就处理不了,在子isolate中解析构建好的Widget对象没办法传给父isolateWidget本身又比较复杂也没有提供序列化的方法,所以第一种方案被Pass掉。

第二种方案存在同样的问题,就是在 C/C++中如何构建Widget对象?已经如何将构建好的对象传输到Flutter层?

剩下比较可行的就是第三种方案了,思路同目前常见列表滚动优化方法一样,就是在滑动的时候不进行解析,滑动结束的交互空闲时段,在执行解析器任务。

3. 第三种方案实现

实现流程如下:

dynamiclistview_flow

整个原理过程不算复杂,其中比较重要的是如何获取已显示列表项的index区间,Flutter SDK中是没有提供相关方法的,使用的是第三方的Package:scrollable_positioned_list,也是Google出品,感兴趣的童鞋可以看看源码实现。

最终优化后的效果:

ast_dynamic_list

这个方案还算不上完美,虽然滑动体验较之前好了许多,但是加载列表项等待的时长还是挺长的,有其他优化思路的小伙伴欢迎在评论区一同探讨~