Flutter瘦身大作战
摘要: 作者:闲鱼技术-三莅 背景 闲鱼技术团队于2018年上半年率先引入了Flutter技术实现客户端开发,到目前为止成功改造并上线了复杂的商品详情和发布业务。随着改造业务的增多,安装包体积急剧上增。安装包体积决定了用户等待下载的时间和可能会耗费的流量,如何控制安装包体积,减小flutter产物的大小成为当务之急。
作者:闲鱼技术-三莅
背景
闲鱼技术团队于2018年上半年率先引入了Flutter技术实现客户端开发,到目前为止成功改造并上线了复杂的商品详情和发布业务。随着改造业务的增多,安装包体积急剧上增。安装包体积决定了用户等待下载的时间和可能会耗费的流量,如何控制安装包体积,减小flutter产物的大小成为当务之急。本文从闲鱼客户端项目实践角度给出了一些通用的包大小检测以及优化方案,希望为对Flutter感兴趣的团队提供参考。
闲鱼客户端采用的Flutter和Native混合开发的模式,下面我们以ios端为例分析项目中flutter产物的大小(ipa包瘦身需求更为急切)。
ios工程对Flutter有如下依赖:
- Flutter.framework : Flutter库和引擎
- App.framework: dart业务源码相关文件
- Flutter Plugin:编译出来的各种plugin的framework
- flutter_assets:Flutter依赖的静态资源,如字体,图片等
第一次引入flutter版本改造详情页后,ipa包大小增加近20M,其中包括flutter引擎代码+被改造业务代码,继续发布页flutter改造后,ipa增加4M+。进一步分析解压ipa文件后发现Flutter.framework稳定保持在20M+的大小, 增加新的flutter业务——发布页之后,App.framework增幅近10M!
Flutter.framework是Flutter库和引擎的代码,我们能做的优化空间有限,先把目标放在dart业务相关的文件App.framework上。
Flutter产物大小分析
执行如下命令编译出一个release模式下的App.framework,并使用print-snapshot-sizes
参数打印出产物具体大小
flutter build aot --release --extra-gen-snapshot-options=--print-snapshot-sizes
结果如下:
Instructions:代表AOT编译后生成的二进制代码大小
ReadOnlyData:代表生成二进制代码的元数据(例如PcDescriptor, StackMap,CodeSourceMap等)和字符串大小
VMIsolate/Isolate:代表剩下的对象的大小总和(例如代码中定义的常量和虚拟机特定元数据)
具体到业务层,想要分析各个业务模块所占用的大小该怎么办呢?
-
执行如下命令编译出一个arm64架构的App.framework,并将它的包组成结构放到指定目录build/aot.json文件中
flutter --suppress-analytics build aot --output-dir=build/aot --target-platform=ios --target=lib/main.dart --release --ios-arch=arm64 --extra-gen-snapshot-options="--dwarf_stack_traces,--print-snapshot-sizes,--print_instructions_sizes_to=build/aot.json"
-
使用dart命令将上一步生成的aot.json文件转化成结构可视化的网页
dart ./bin/run_binary_size_analysis.dart build/aot.json path_to_webpage_dir
run_binary_size_analysis.dart是dart提供的一个分析工具,在flutter引擎源码中路径如下:
- 打开生成文件夹中的index.html即可分析具体业务所占用的大小,右上角的Large Symbols和Large Files按钮可以直接定位体积占比从大到小的方法/文件。
举个例子,上面的分析显示PItemInfoInternal.fromJson
方法占用了大量体积,跟踪发现这个方法主要的操作是将Map数据转化成对象
由此我们可以推断这种类型转换的操作会导致编译生成一些体积很大的代码。
优化措施
- 减少显示类型转换操作
按照上述分析发现显示的类型转换 as String/Bool/Int
这类操作会导致App.framework体积显著增加,主要是它会增加类型检查以及抛出异常的处理逻辑:
if (x.classId < A && x.classId > B) throw "x is not subtype of String";
通过提取静态公用方法的方式可以成功减少400k+体积。
- 通过编译参数
--dwarf_stack_trace
和--obfuscate
减小生成代码的体积
dwarf_stack_trace
表示在生成的动态库文件中,不使用堆栈跟踪符号
obfuscate
表示混淆,通过减少变量名/方法名的方式减小代码体积
- 通过修改ios打包脚本xcode_backend.sh,删除dSYM符号表信息文件,App.framework成功减小20%的大小。dSYM 是保存 16 进制函数地址映射信息的中转文件,包含我们调试的 symbols,用来分析 crash report 文件,解析出正确的错误函数信息。
使用xcrun命令将dSYM从framework中剥离出来,可以大大减小App.framework的体积。
RunCommand xcrun dsymutil -o "${build_dir}/aot/App.dSYM" "${app_framework}/App" RunCommand xcrun strip -x -S "${derived_dir}/App.framework/App"
- 减少flutter和native资源重复造成的体积增大
利用桥接的方式,flutter直接使用Platform端资源文件,避免因为资源文件重复导致的包大小增加问题。
主要方式是通过BasicMessageChannel在Flutter和Platform端传递信息。Flutter端将资源名AssetName传递给Platform端,Platform端接收到AssetName后,根据name定位到资源文件,并将该文件以二进制数据格式,通过BasicMessageChannel传递回Flutter端。
总结
引入Flutter带来的安装包体积问题会给很多技术团队带来困扰。通过以上措施,Flutter产物App.framework的大小减少30%+,闲鱼技术团队后续也会考虑采取下载并懒加载等方式减少资源占用的体积;继续代码生成中的各种对比,排查避免较大产物的写法,同时也会和Google一起进一步寻找优化空间。
参考
- https://github.com/flutter/flutter
- https://github.com/flutter/engine
- https://github.com/flutter/flutter/issues/21813
- https://github.com/flutter/flutter/issues/20671
阿里云双十一1折拼团活动:已满6人,都是最低折扣了
【满6人】1核2G云服务器99.5元一年298.5元三年 2核4G云服务器545元一年 1227元三年
【满6人】1核1G MySQL数据库 119.5元一年
【满6人】3000条国内短信包 60元每6月
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
【熵增教育】探索SpringBoot中的SpringMVC——熵增学院
spring boot就是一个大框架里面包含了许许多多的东西,其中spring就是最核心的内容之一,当然就包含spring mvc。spring mvc 是只是spring 处理web层请求的一个模块。因此他们的关系大概就是这样:spring mvc < spring <springboot。 理清SpringBoot与SpringMVC的关系 Spring 框架就像一个家族,有众多衍生产品例如 boot、security、jpa等等。但他们的基础都是Spring 的 ioc和 aop ioc 提供了依赖注入的容器 aop ,解决了面向横切面的编程,然后在此两者的基础上实现了其他延伸产品的高级功能。 Spring MVC是基于 Servlet 的一个 MVC 框架 主要解决 WEB 开发的问题,因为 Spring 的配置非常复杂,各种XML、 JavaConfig、hin处理起来比较繁琐。 于是为了简化开发者的使用,从而创造性地推出了Spring boot,约定优于配置,简化了spring的配置流程。 说得更简便一些:Spring 最初利用“工厂模式”(DI)和“代理模式...
- 下一篇
Go Gin 框架 curl -I 返回 404 的问题
在使用 Go 的 Gin Web 框架的时候,发现一个有趣的问题,curl 一个 router 是正常的,但是加上 -I 参数就 404 了,就像下面这样 package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 } 正常的 curl http://127.0.0.1:8080/ping {"message":"pong"} 404的 curl -I http://127.0.0.1:8080/ping HTTP/1.1 404 Not Found Content-Type: text/plain Date: Wed, 31 Oct 2018 04:35:22 GMT Content-Length: 18 然后通过抓包发现,curl -I...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2全家桶,快速入门学习开发网站教程
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS8编译安装MySQL8.0.19
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2配置默认Tomcat设置,开启更多高级功能