Flutter 性能优化面试题

问题1:启动优化
答:iOS中的启动优化分为Pre-main和main函数之后两个阶段。

  • main函数之前的阶段优化
    这个阶段是在main()函数执行之前,主要执行dyld(动态库)的加载、rebase/binding、Objc runtime初始化等
    1、 减少动态库的数量:合并零散的自定义framework,因为每个动态库的加载都会增加加载耗时。实测10个以内是最优的,超过20个framework聚会有明显的感知。
    2、将Utils类小的framework改为直接用源码的方式编译到主target中
    3、+load函数的检查
    不能再load函数中做任何耗时的操作,load函数的调用是加载该类时就会被调用。改用+initialize进行懒加载,它的加载时机是该类第一次被使用到时才会触发该函数的调用。

4、混编项目/OC项目中尽可能去少使用分类的数量
5、 二进制重排(Clane Order File)
1、在build Setting中搜索“Order File”,设置.order文件路径。
2、通过instrument的system trace采集启动时调用的符号顺序
3、生成order file 文件,让启动时需要的代码在二进制中连续排列,减少page Flaut的产生。
备注:可以在Xcode的scheme中设置环境变量,打印各个阶段的耗时情况。

  • Main 函数之后的优化
    主要做的就是懒加载+任务分级
    1、将启动任务分为A/B/C三个等级
    A级:主线程操作,首屏必须得加载(在didFinishLaunching函数中同步执行)。
    B级:非首屏但马上要用的(runloop的空闲执行,自定义crash日志收集、IAP队列任务的监听等)
    C级:低优先级任务:首屏展示后延迟2秒再去执行(比如说第三方商用SDK的注册和初始化)

问题2:卡顿优化(iOS/Flutter)

  • iOS中的卡顿优化分为两个阶段:1、卡顿的定位。2、卡顿的解决方案
  • 1、在实际开发过程中,用得最多的卡顿检测手段就是:监听Runloop进行卡顿监控。
    核心思路:在子线程去监控主线程的Runloop状态,当kCFRunloopBeforeSourcs或kCFRunloopAfterWaiting状态持续超过阈值200ms就说明主线程卡顿了。记录状态变化的时间,如果连续两次状态变化间隔>阈值 则需要采集堆栈信息进行上报。
    实现思路:
    1、首先是创建子线程作为我们的监控线程,对主线程进行监听。
    2、在子线程中创建CFRunloopOberver监听所有的活动状态,然后将observer注册到主线程的Runloop中,主线程的作用就是回调记录当前runloop的状态活动,当每次状态切换时发送信号,驱动子线程进行检测,并获取堆栈信息。
    3、创建信号量用于等待
    4、在子线程中启动监听循环,当runloop进入下一个状态时等待,如果主线程的Runloop状态发生变化,oberver回调就会signal、如果在阈值后还没有切换状态,就可能是卡顿了,需要累加计数。当连续2次主线程卡顿,就获取堆栈信息进行上报操作。

2、使用Xcode的Instruments的Time Profiler去定位那个函数耗时最多,关注Weight(权重)列,找到热点函数。

  • 实际开发中常见的卡顿场景及解决方案
    1、主线程IO操作或网络回调,比如:主线程同步网络操作,解决方案是进行异步处理,解析、解码等耗时的操作在子线程中执行,刷新UI再回到主线程。
    2、Cell的高度计算和圆角,防止在CellForRow函数中做圆角的裁剪操作,可能会触发离屏渲染的问题,解决方案是可以用贝瑟尔曲线裁剪生成新的圆角图片或使用CAShapeLayer+mask,又或者使用后台线程进行圆角处理

备注:卡顿的核心方案:主Runloop的监控+堆栈采集上报、图片的解码在子线程,Cell进行预排版(预排版的意思是:将耗时的计算,比如文本计算、布局、高度进行缓存)提前在子线程中完成,把结果存储到model中,主线程在上屏时只需要把提前计算好的高度缓存拿出来直接用即可,不做任何的再次计算。

  • Flutter的卡顿优化:检测、排查和解决方案
    Flutter追求的是60fps/120fps的流程刷新率渲染,每帧是16.7ms/8ms的预算,任何一帧的渲染时长超过了它就说明有卡顿的产生。
    1、卡顿产生的原理
    Flutter每一帧的渲染分为两个核心的线程(UI线程(dart ),和GPU线程)
    UI线程:处理输入事件、执行动画的回调、Build阶段(构建widget树),Layout阶段(布局),Paint阶段(生成Layer tree)
    GPU线程:合成Layer tree,对数据进行纹理合成、光栅化等操作,最后将数据提交给GPU进行渲染。
    备注:UI线程卡顿意味着Dart代码太慢,Raster线程卡顿说明GPU操作太慢。
    2、卡顿检测手段
    1、开发阶段:PerformanceOverlay,它是MaterialApp组件中的卡顿检测组件,一键开启性能图层,开启后屏幕顶部会出现两条性能指示条,红色是卡顿帧,绿色是正常帧.
    2、使用DevTools工具中的Timeline视图里逐帧展开,能看到每一帧中Build/layout/paint各个阶段的精确耗时,以及是哪段Dart代码导致的。
    3、线上监控卡顿:Flutter提供了帧耗时的回调,利用SchedulerBinding.instance.addTimingsCallback回调在每帧结束时获得帧对象,对象中包含了buildDuration(构建阶段的耗时,layout+paint)、rasterDuration(GPU合成、光栅化等耗时)。目的是收集每一帧耗时并统计卡顿率。
  • Flutter项目中卡顿的场景及解决方案
    1、Feed信息流的滑动卡顿,比如首页信息流快速滑动时可能存在掉帧的问题,因为每个Item内部都使用了MediaQuery.of(context).padding,通过contxt向上查找MediaQuery,而MediaQuery是继承自InhertedWidget的,当MediaQuery发生变化时,所有依赖于它的widget都会被重新build。解决方案:使用builder进行模式隔离InhertedWidget的依赖,将依赖于InhertedWidget的widget剥离到最小颗粒度,让MediaQuer的依赖仅限于需要rebuild的weidget。

2、图片列表的raster线程卡顿
1、GridView图片列表中原图加载,GPU纹理上传耗时巨大的,
2、图片设置圆角使用的是widget的ClipRRect进行圆角的处理
3、滚动时正规GridView都重绘了可见的Item
解决方案
1、限制解码的尺寸告诉imageProvider在解码时缩放到指定宽高的缩略图减少纹理上传的耗时
2、当给图片列表设置圆角时,不要给整个item的weiget设置圆角,而是直接给图片设置圆角原因是使用ClipRRect会导致skia的saveLayer,它会产生离屏渲染。
3、在使用列表时,一定要去做显示保留,确保在滚动时只去重新绘制新出现的Item,已有的就不需要重建了。addRepaintBoindaries:true
4、减少不必要的rebuild

问题3:IAP(支付优先)和IAA(广告优先)
重点讲IAA:
1、移动端的广告常见的计费模式:eCPM(实际千次展示首页,是衡量广告收益的核心指标),eCPM = (总收益 / 总展示次数) x 1000,eCPM越高,说明广告位的变现效率越高。
2、常见的广告形式:Banner横幅广告、信息流广告、插屏广告、激励广告。
3、联盟广告的集成:初始化-请求权限-创建广告-加载-展示(可以提前初始化,比如在didFinishLaunch中进行初始化,同时开启优化初始化选项等操作。)权限的请求,主要是拿到真实的IDFA,如果用户同意,拿到IDFA后广告能精准投放,eCPM就会高,如果用户不同意,就无法精确投放广告,eCPM就会下降30-50%.

4、如何提高eCPM
1、展示频率的控制

  • 每个用户每天最多能展示N次,防止用户疲劳导致点击率下降拖低了eCPM。
  • 广告竞价优化:可以多个平台进行同时请求,谁出的价高就展示谁。
  • 激励视频的合理嵌入,因为激励视频的eCPM是最高的,
  • 广告位策略
    横幅广告放在不遮挡操作的位置,插屏在操作完成后展示,不要在启动时弹插屏,一是违规二是体验差。
  • 通过ATT获取授权拿到IDFA,精准投放广告可以提高eCPM。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容