前言
Flutter是谷歌的移动UI结构,能够快速在iOS和Android上构建高质量的原生用户界面。 Flutter能够与现有的代码一起作业。在全世界,Flutter正在被越来越多的开发者和组织运用,而且Flutter是完全免费、开源的,能够用一套代码一起构建Android和iOS运用,功能能够到达原生运用相同的功能。可是,在较为杂乱的 App 中,运用 Flutter 开发也很难防止产生各种各样的功能问题。在这篇文章中,我将介绍一些 Flutter 功能优化方面的运用实践。
一、优化检测东西
flutter编译方式
Flutter支撑Release、Profile、Debug编译方式。
-
Release方式,运用AOT预编译方式,预编译为机器码,经过编译生成对应架构的代码,在用户设备上直接运转对应的机器码,运转速度快,履行功能好;此方式关闭了一切调试东西,只支撑真机。
-
Profile方式,和Release方式类似,运用AOT预编译方式,此方式最重要的作用是能够用DevTools来检测运用的功能,做功能调试剖析。
-
Debug方式,运用JIT(Just in time)即时编译技能,支撑常用的开发调试功能hot reload,在开发调试时运用,包括支撑的调试信息、服务扩展、Observatory、DevTools等调试东西,支撑模拟器和真机。
经过以上介绍咱们能够知道,flutter为咱们供给 profile方式发动运用,进行功能剖析,profile方式在Release方式的基础之上,为剖析东西供给了少量必要的运用追踪信息。
如何敞开profile方式?
假如是独立flutter工程能够运用flutter run –profile发动。假如是混合 Flutter 运用,在 flutter/packages/flutter_tools/gradle/flutter.gradle 的 buildModeFor 办法中将 debug 方式改为 profile即可。
检测东西
1、Flutter Inspector (debug方式下)
Flutter Inspector有很多功能,其间有两个功能更值得咱们去重视,例如:“Select Widget Mode” 和 “Highlight Repaints”。
Select Widget Mode点击 “Select Widget Mode” 图标,能够在手机上检查当时页面的布局结构与容器类型。
经过“Select Widget Mode”咱们能够快速检查生疏页面的布局实现办法。
Select Widget Mode方式下,也能够在app里点击相应的布局控件检查
Highlight Repaints
点击 “Highlight Repaints” 图标,它会为一切 RenderBox 制作一层外框,并在它们重绘时会改变色彩。
这样做帮你找到 App 中频频重绘导致功能耗费过大的部分。
例如:一个小动画可能会导致整个页面重绘,这个时分运用 RepaintBoundary Widget 包裹它,能够将重绘范围缩小至自身所占用的区域,这样就能够削减制作耗费。
2、Performance Overlay(功能图层)
在完结了运用发动之后,接下来咱们就能够运用 Flutter 供给的烘托问题剖析东西,即功能图层(Performance Overlay),来剖析烘托问题了。
咱们能够经过以下办法敞开功能图层
功能图层会在当时运用的最上层,以 Flutter 引擎自绘的办法展现 GPU 与 UI 线程的履行图表,而其间每一张图表都代表当时线程最近 300 帧的表现,假如 UI 产生了卡顿,这些图表能够协助咱们剖析并找到原因。 下图演示了功能图层的展现款式。其间,GPU 线程的功能状况在上面,UI 线程的状况显现在下面,蓝色垂直的线条表明已履行的正常帧,绿色的线条代表的是当时帧:
假如有一帧处理时刻过长,就会导致界面卡顿,图表中就会展现出一个赤色竖条。下图演示了运用呈现烘托和制作耗时的状况下,功能图层的展现款式:
假如赤色竖条呈现在 GPU 线程图表,意味着烘托的图形太杂乱,导致无法快速烘托;而假如是呈现在了 UI 线程图表,则表明 Dart 代码耗费了很多资源,需求优化代码履行时刻。
3、CPU Profiler(UI 线程问题定位)
在视图构建时,在 build 办法中运用了一些杂乱的运算,或是在主 Isolate 中进行了同步的 I/O 操作。 咱们能够运用 CPU Profiler 进行检测:
你需求手动点击 “Record” 按钮去主动触发,在完结信息的抽样采集后,点击 “Stop” 按钮结束录制。这时,你就能够得到在这期间运用的履行状况了。
其间:
x 轴:表明单位时刻,一个函数在 x 轴占有的宽度越宽,就表明它被采样到的次数越多,即履行时刻越长。
y 轴:表明调用栈,其每一层都是一个函数。调用栈越深,火焰就越高,底部就是正在履行的函数,上方都是它的父函数。
经过上述CPU帧图咱们能够大概剖分出哪些办法存在耗时操作,针对性的进行优化
一般的耗时问题,咱们一般能够运用 Isolate(或 compute)将这些耗时的操作挪到并发主 Isolate 之外去完结。
例如:杂乱JSON解析子线程化
Flutter的isolate默许是单线程模型,而一切的UI操作又都是在UI线程进行的,想运用多线程的并发优势需新开isolate 或compute。无论如何await,scheduleTask 都只是延后使命的调用时机,仍然会占用“UI线程”, 所以在大Json解析或很多的channel调用时,一定要观测对UI线程的耗费状况。
二、Flutter布局优化
Flutter 运用了声明式的 UI 编写办法,而不是 Android 和 iOS 中的指令式编写办法。
-
声明式:简单的说,你只需求告诉计算机,你要得到什么样的成果,计算机则会完结你想要的成果,声明式更重视成果。
-
指令式:用具体的指令机器怎么去处理一件事情以到达你想要的成果,指令式更重视履行进程。
flutter声明式的布局办法经过三棵树去构建布局,如图:
-
Widget Tree: 控件的装备信息,不涉及烘托,更新价值极低。
-
Element Tree : Widget树和RenderObject树之间的粘合剂,担任将Widget树的变更以最低的价值映射到RenderObject树上。
-
RenderObject Tree : 真正的UI烘托树,担任烘托UI,更新价值极大。
1、常规优化
常规优化即针对 build() 进行优化,build() 办法中的功能问题一般有两种:耗时操作和 Widget 层叠。
1)、在 build() 办法中履行了耗时操作
咱们应该尽量防止在 build() 中履行耗时操作,因为 build() 会被频频地调用,尤其是当 Widget 重建的时分。 此外,咱们不要在代码中进行堵塞式操作,能够将一般耗时操作等经过 Future 来转化成异步办法来完结。 对于 CPU 计算频频的操作,例如图片压缩,能够运用 isolate 来充分运用多核心 CPU。
2)、build() 办法中堆叠了很多的 Widget
这将会导致三个问题:
1、代码可读性差:画界面时需求一个 Widget 嵌套一个 Widget,但假如 Widget 嵌套太深,就会导致代码的可读性变差,也晦气于后期的维护和扩展。
2、复用难:由于一切的代码都在一个 build(),会导致无法将公共的 UI 代码复用到其它的页面或模块。
3、影响功能:咱们在 State 上调用 setState() 时,一切 build() 中的 Widget 都将被重建,因而 build() 中回来的 Widget 树越大,那么需求重建的 Widget 就越多,也就会对功能越晦气。
所以,你需求 控制 build 办法耗时,将 Widget 拆小,防止直接回来一个巨大的 Widget,这样 Widget 会享有更细粒度的重建和复用。
3)、尽可能地运用 const 结构器
当构建你自己的 Widget 或者运用 Flutter 的 Widget 时,这将会协助 Flutter 只是去 rebuild 那些应当被更新的 Widget。 因而,你应该尽量多用 const 组件,这样即使父组件更新了,子组件也不会从头进行 rebuild 操作。特别是针对一些长时刻不修正的组件,例如通用报错组件和通用 loading 组件等。
4)、列表优化
-
尽量防止运用 ListView默许结构办法
不管列表内容是否可见,会导致列表中一切的数据都会被一次性制作出来
-
主张运用 ListView 和 GridView 的 builder 办法
它们只会制作可见的列表内容,类似于 Android 的 RecyclerView。
其实,本质上,就是对列表采用了懒加载而不是直接一次性创立一切的子 Widget,这样视图的初始化时刻就削减了。
2、深入光栅化优化
优化光栅线程
屏幕显现器一般以60Hz的固定频率刷新,每一帧图画制作完结后,会继续制作下一帧,这时显现器就会宣布一个Vsync信号,按60Hz计算,屏幕每秒会宣布60次这样的信号。CPU计算好显现内容提交给GPU,GPU烘托好传递给显现器显现。 Flutter遵循了这种方式,烘托流程如图:
flutter经过native获取屏幕刷新信号经过engine层传递给flutter framework
一切的 Flutter 运用至少都会运转在两个并行的线程上:UI 线程和 Raster 线程。
-
UI 线程
构建 Widgets 和运转运用逻辑的当地。
-
Raster 线程
用来光栅化运用。它从 UI 线程获取指令将其转化成为GPU指令并发送到GPU。
咱们一般能够运用Flutter DevTools-Performance 进行检测,过程如下:
-
在 Performance Overlay 中,检查光栅线程和 UI 线程哪个负载过重。
-
在 Timeline Events 中,找到那些耗费时刻最长的事件,例如常见的 SkCanvas::Flush,它担任处理一切待处理的 GPU 操作。
-
找到对应的代码区域,经过删去 Widgets 或办法的办法来看对功能的影响。
三、Flutter内存优化
1、const 实例化
const 对象只会创立一个编译时的常量值。在代码被加载进 Dart Vm 时,在编译时会存储在一个特殊的查询表里,只是只分配一次内存给当时实例。
咱们能够运用 flutter_lints 库对咱们的代码进行检测提示
2、检测耗费多余内存的图片
Flutter Inspector:点击 “Highlight Oversizeded Images”,它会识别出那些解码大小超过展现大小的图片,而且系统会将其倒置,这些你就能更简单在 App 页面中找到它。
经过下面两张图能够明晰的看出运用“Highlight Oversizeded Images”的检测作用
针对这些图片,你能够指定 cacheWidth 和 cacheHeight 为展现大小,这样能够让 flutter 引擎以指定大小解析图片,削减内存耗费。
3、针对 ListView item 中有 image 的状况来优化内存
ListView 不会毁掉那些在屏幕可视范围之外的那些 item,假如 item 运用了高分辨率的图片,那么它将会耗费十分多的内存。
ListView 在默许状况下会在整个滑动/不滑动的进程中让子 Widget 保持活动状况,这一点是经过 AutomaticKeepAlive 来确保,在默许状况下,每个子 Widget 都会被这个 Widget 包裹,以使被包裹的子 Widget 保持活跃。 其次,假如用户向后翻滚,则不会再次从头制作子 Widget,这一点是经过 RepaintBoundaries 来确保,在默许状况下,每个子 Widget 都会被这个 Widget 包裹,它会让被包裹的子 Widget 只是制作一次,以此获得更高的功能。 但,这样的问题在于,假如加载很多的图片,则会耗费很多的内存,最终可能使 App 溃散。
经过将这两个选项置为 false 来禁用它们,这样不可见的子元素就会被主动处理和 GC。
4、多变图层与不变图层别离
在日常开发中,会经常遇到页面中大部分元素不变,某个元素实时改变。如Gif,动画。这时咱们就需求RepaintBoundary,不过独立图层合成也是有耗费,这块需实测把握。
这会导致页面同一图层从头Paint。此时能够用RepaintBoundary包裹该多变的Gif组件,让其处在独自的图层,待最终再一块图层合成上屏。
5、降级CustomScrollView,ListView等预烘托区域为合理值
默许状况下,CustomScrollView除了烘托屏幕内的内容,还会烘托上下各250区域的组件内容,例如当时屏幕可显现4个组件,实践仍有上下共4个组件在显现状况,假如setState(),则会进行8个组件重绘。实践用户只看到4个,其实应该也只需烘托4个, 且上下滑动也会触发屏幕外的Widget创立毁掉,形成翻滚卡顿。高功能的手机可预烘托,在低端机降级该区域距离为0或较小值。
四、总结
Flutter为什么会卡顿、帧率低?总的来说均为以下2个原因:
-
UI线程慢了–>烘托指令出的慢
-
GPU线程慢了–>光栅化慢、图层合成慢、像素上屏慢
所以咱们一般运用flutter布局尽量依照以下原则
Flutter优化基本原则:
-
尽量不要为 Widget 设置半透明作用,而是考虑用图片的方式代替,这样被遮挡的 Widget 部分区域就不需求制作了;
-
控制 build 办法耗时,将 Widget 拆小,防止直接回来一个巨大的 Widget,这样 Widget 会享有更细粒度的重建和复用;
-
对列表采用懒加载而不是直接一次性创立一切的子 Widget,这样视图的初始化时刻就削减了。
五、其他
假如咱们对flutter动态化感兴趣,咱们也为咱们预备了flutter动态化平台-Fair
欢迎咱们运用 Fair,也欢迎咱们为咱们点亮star
Github地址:github.com/wuba/fair
Fair官网:fair.58.com