|
209M->102M,贝壳B端iOS包瘦身之路
209M->102M,贝壳B端iOS包瘦身之路
陈旭
贝壳产品技术
贝壳产品技术 “贝壳产品技术公众号”作为贝壳官方产品技术号,致力打造贝壳产品、技术干货分享平台,面向互联网/O2O开发/产品从业者,每周推送优质产品技术文章、技术沙龙活动及招聘信息等。欢迎大家关注我们。 242篇内容
2021年12月17日 18:14
1. 背景安装包瘦身是性能优化的重要一环,一个特别大的安装包带来的体验是非常糟糕的,比起 C 端受 App Store的发版制约,用户可以长期不升级, B 端经纪人升级的意愿更高,可控性更强,以贝壳 B 端 Link 和 A+ 为例,按照两周一个版本的节奏,一年发布20多个版本,经纪人平均升级10个版本以上,收益也会更加明显。安装包过大带来的问题,大概可以分为体验和技术两个方面。从体验角度看:(1)每个版本更新带来的流量浪费以及增加的下载和安装时间,给经纪人的体验造成了不好的影响。(2)大量占用经纪人的手机空间。 (3)大量无用代码也会拖慢app的启动速度。 (4)经纪人满意度调研中,有18.9%的人反馈包太大,占性能满意度的第二。 从技术角度看: (1)存在大量的冗余代码和资源。 (2)重复的技术方案。 (3)代码只增不减,后来者不敢删,维护成本高。去年底 Link和 A+ iOS 的下载包大小已经超过200M, 到了不得不治的地步,必须进行大刀阔斧的治理,并且管控起来,保证未来的可持续优化。2. 方案2.1 前言包大小可以分为下载大小和安装大小:下载大小指下载的压缩 App 大小,安装大小指在户设备上占的磁盘空间。查看下载大小可以通过设置->iTunes Store与App Store->App下载,然后选择始终询问(使用蜂窝网络下载 App 始终需要先请求许可),这样扫码下载App 时就会提示安装包有多大。实际上安装包瘦身瘦的主要是下载大小,但是由于下载大小是压缩后的产物,不利于分析,后面会用安装大小进行分析,一方面方便以平台的视角进行展示和分析,另一方面,下载大小和安装大小也呈强相关关系,一个对应压缩产物,一个对应解压产物,安装大小优化了,下载大小自然会随之优化。包大小分析平台:从 keones 平台可以看到每个组件占用的大小,以及每个版本的新增,为治理存量和控制增量提供数据支撑。从 App 组成来说可以分为可执行文件、资源、framework等,下面介绍的包大小优化主要包含二进制优化和资源优化两部分。2.2 二进制优化2.2.1 编译优化编译优化包含编译选项优化和编译指令优化。(1)编译选项优化: - Link-Time Optimization: Incremental - Optimization Level: "Fastest ,Smallest", 或者 "Smallest,Aggressive Size Optimizations" - Deployment Postprocessing: YES - Dead Code Stripping: YES - Strip Linked Product: YES - Symbols Hidden by Default :YES - Strip Swift Symbols: YES - Strip Debug Symbols During Copy: YES - Enable C++ Exceptions: NO - Enable Objective-C Exceptions: NO - Asset Catalog Compiler -options: space - Compress PNG Files: YES - Remove Text Metadata From PNG Files: YES - Make Strings Read-Only: YES其中,把 OC 和 C++的异常关掉属于比较极端的优化,从Cocoa的设计理念来说,不应该抛出异常。(2)编译指令优化:-Wl,-rename_section,__TEXT,__cstring,__RODATA,__cstring-Wl,-rename_section,__TEXT,__objc_methname,__RODATA,__objc_methname-Wl,-rename_section,__TEXT,__objc_classname,__RODATA,__objc_classname-Wl,-rename_section,__TEXT,__objc_methtype,__RODATA,__objc_methtype-Wl,-rename_section,__TEXT,__gcc_except_tab,__RODATA,__gcc_except_tab-Wl,-rename_section,__TEXT,__const,__RODATA,__const-Wl,-rename_section,__TEXT,__text,__BD_TEXT,__text-Wl,-rename_section,__TEXT,__textcoal_nt,__BD_TEXT,__text-Wl,-rename_section,__TEXT,__StaticInit,__BD_TEXT,__text-Wl,-rename_section,__TEXT,__stubs,__BD_TEXT,__stubs-Wl,-rename_section,__TEXT,__picsymbolstub4,__BD_TEXT,__picsymbolstub4,-Wl,-segprot,__BD_TEXT,rx,rx__TEXT 段的迁移主要针对 app store 上架的 C 端 App 起作用,对 B 端 App不起作用,这里简单提下,感兴趣的同学可以参考头条的《今日头条优化实践:iOS 包大小二进制优化,一行代码减少 60 MB 下载大小 》。2.2.2 无用/重复技术栈下线这个不需要过多强调,不仅对瘦身有直接收益,对于降低维护成本和提升代码质量也有很大的益处。但实际操作中并不容易,要下线某个 SDK 要么由于低 PV 推动产品下线,要么能够在能力上进行替换,每项任务都要以月为维度。(1)业务已无应用场景,通过 ABTest 逐步下掉上上签 SDK 和合合 SDK。(2)贝经院下掉 AsyncDisplayKit, 借产品改版的机会,逐渐把 UI 绘制方式换掉,最后下掉。(3)VR 的 3D 渲染库用 OC 重写了一遍,下掉了 swift, 瘦身约10M。(目前 App 只有这一处用到了swift, 从当前看下掉是合适的)。(4)Link 从业务角度看不应该包含 CA ,但是由于耦合了部分基础定位接口,解耦后下掉。 (5)客源业务引入 OpenCV 识别身份证,替换后下掉。(6)Flutter 新旧容器并存,经过多个版本推动所有业务方切到新的 BKRunner , 然后下线旧的 FlutterBoost 。 (7)实名认证 SDK 使用自研 SDK 代替百度、商汤和旷视,目前仍有较多路要走。2.2.3 无用代码下线这里有个误区,有的同学会觉得 API 不是会被编译优化掉么?其实想想就知道了,由于 OC 是动态化语,存在运时调的可能,所以一般不会进编译优化。第一种方式, __objc_class_list 和 __objc_class_refs 做差集,可以输出静态无用方法,这块工具和脚本非常多,需要注意的是,可以添加上对 performSelector 和 NSClassFromString这种动态化写法的 grep 进一步增加准确性。 第二种方式,0 PV扫描。hook VC 的生命周期,统计本地 native 页面总和以及线上运行的 VC 总和,两者做差就是0 PV的页面,时间拉长,比如一个月线上没有任何用户访问,基本可以认为是无用方法,和第一种方式结合,一个静态一个动态会更准确。第三种方式以代码覆盖率的方式,但是成本太高,一般采用轻量级的类级别的代码覆盖率。原理就是 OC 的类首次初始化时,+initialize 会被执行,系统会自动标记。2.2.4 重复代码下线原理:计算每个法的 md5 值进较,这个有一些现成的工具,像 PMD 等。2.2.5 代码内部优化(1)调试工具和代码都不要上到 release 包,需要抽到单独组件,根据包类型选择配置。(2)用编译宏进行隔离,比如类型宏 Debug、平台宏__iOS__ 和业务宏。(3)保持 inline 函数内部逻辑的简单,会被展开到每个调用的地方。(4)向 OC 函数传递 C++ 参数类型时使用void *, 因为C++对象生成的方法签名会很长。(5)podspec 里注意写法,客源bundle资源目录迁移后生成了两份。2.3 资源优化2.3.1 启动图使用一份,用 storyboard 自适应企业包没有 app store 自带的 App Slicing, 这里说的一份启动图不是一张启动图,如果是一张图无论怎么拉伸填充都无法适应所有机型分辨率,要么顾上不顾下,要么顾左不顾右。我们是拆成了几张小图,分别拉伸填充,原来各种机型加起来 4,5M,缩减到100多K。2.3.2 无用图片删除使 LSUnusedResources 查找图删除,需注意拼接组成的图片名。2.3.3 相同图删除通过检测 md5 值来发现图内容相同但是名字不同的情况,来替换和删除。2.3.4 相似图替换原理比较简单,通过像素比较,R、G、B 差值都在极小的阈值内,可以认为两张图片高度相似。2.3.5 Assets 管理图通常来说,磁盘占空间使用磁盘的 block size 来计算, 件使用 byte 来计算。举个例子, block size 是4kb,件 size 是2kb , 那么件占的磁盘空间就是4kb。当小图片数量多的时候,对磁盘空间的浪费就会很严重。类似其他技术栈里的雪碧图,把多张小图片合并成一张图片,可以减少磁盘空间的浪费。XCode 使用 Assets 管理图,做的也是类似的事情,所以我们尽量不要把图片放到 bundle 里。另外需要注意的是 Assets 合并也会有较大收益。2.3.6 资源压缩有些图片可以转 iconFont,但是推动起来会比较困难。大图转 webp 。使用工具压缩,比如ImageOptim和tinypng,通常可以循环压缩,需要让 UI 关注不要失真。2.3.7 文本优化(1)删除JSON、XML里的空格,也能减小文件大小(2)离线包、配置文件等进行压缩,使用前解压缩。(3)本地数据库尽量小,通过服务端来更新。2.3.8 图片云化图片云化和前面的优化手段不冲突,是互为补充的,思路很简单,就是把本地图转移到 CDN,实际使时再下载。我们除了首页的几个 icon,基本都放到了 CDN 上,一下优化了几十M,俗话说,一招鲜吃遍天,普通的优化手段做的到位,也能产生不可思议的效果。下面重点介绍下实现步骤,首先提出一个问题,苹果有 On-Demand Resource,为什么要自己云化呢?On-Demand Resource 存在下面几个问题:(1)只能针对 C 端, B 端企业包享受不了红利。(2)只能在主工程操作,无法添加业务资源。 (3)苹果的服务器,不能保证速度和稳定性。(4)测试需要打 Testflight 测试,搭建私有资源服务器。 相比之下,我们图片云化做到的效果: (1)业务方无需代码开发,即可实现图片云化,只需打勾确认。 (2)提前预加载,体验和本地无差别。 (3)支持批量云化,云化1000张图,提交代码后脚本几秒钟即可自动完成。 (4)平时开发时图片都在本地,不影响开发效率,只在打包的时候通过脚本删除本地图片,使用云化。下面详细介绍下我们的步骤,总体可以分为四个步骤:开发阶段、打包阶段、预加载和渲染。(1)开发阶段:收集需要云化的图片资源。研发同学在 xcode 里对图片的 on demand resource tags 打上 cloud 标签,当然也支持脚本一键把组件的所有图片自动打上标签。获取本地可云化列表时遍历比较有没有 cloud 这个标签。(2)打包阶段:根据云化列表删除已经云化的图片。(3)预加载:根据云化列表预加载图片。读取所有云化图片信息,获取所有图片 url,通过 SDWebImage 批量下载。(4)图片加载和渲染:获取图片时先判断本地图片是否存在,不存在则使用云化图片。最后,像二三方库的裁剪、native 代码转 H5 等动态化方式实现等,还有许多其他手段,这里不再详述。3. 规范随着业务增长,SDK 、业务代码和图片等资源一定是持续增加的,为了防止安装包的野蛮增长,规范和卡口非常重要,比如二三方库的接入标准、图片最大尺寸的要求等。从机制方面,大概有两种声音,一种是近乎严苛的标准,每个版本都不允许新增,特殊情况留口子;另一种是每个版本允许适当的增量,对重大需求预判。我们这里更倾向于第一种,古人云:“取法于上,仅得为中,取法于中,故为其下。”不新增一定做不到,但我们要增加劣化的成本和难度,让大家不放松。4. 成果去年12月份定的目标是瘦身30%,当时觉得几乎完不成,瘦身又不是今天才开始,之前年年都有,好多手段都用过了,而图片资源一共才占20%左右,而这又是优化大头,全干掉又能怎么样(当然最后几乎全干掉了),而推动产品下线功能也没抱太大指望,产品经理加需求简单,下需求太难了。后来经过各方的努力和各种方案的应用,以 A+ 为例,从2020年12月底的5.43版本的209M到2021年12月初的5.65版本的102M,下载大小减少52%,这期间除了各业务线22个版本的需求迭代,还包含 flutter 相关增长10M、TRTC 升级增长5M、接入自研人脸识别 SDK + 声纹识别 SDK 增长5.5M、VR 3D渲染库引入 swift 基础库增长10M,实际瘦身幅度还要远远超过这些。任何优化都要考虑成本,我们在瘦身过程中尽量把其他业务线的研发成本降到最低,以图片云化为例,其他业务线只需要对确认云化的图片打勾,用脚本自动化上传CDN,业务方读取图片的地方也不需要修改,SDK内部兜底本地或云化。最终,瘦身这100M,我们投入的工时不到100人天,Link、A+ 、21世纪、房江湖等 App 都是一套,平均1人天给这些 App 都瘦了1M,还是很划算的。当然后面继续优化难度会越来越大,可能3人天、5人天或者更久才能优化1M, 至少目前我们已经性价比最大化的完成了目标。5. 总结上面介绍了很多优化手段,更重要的是根据实际进行落地,就像无用代码删除,客源大部分业务由 native 转到了 Flutter,但旧的 native 代码一直未删除,这7万多行就是将近2.7M的收益。从我个人的角度看,瘦身实践中最重要的有4点:第一是平台化。可视化的包大小分析可以帮助我们看到 App 里各个部分的详细组成,以及每次构建的对比,让我们从全局知道哪里可以优化,谁导致了劣化。第二是规范。良好的规范超越了技术本身,合情合理,又有强制的约束力。 第三是降低横向拉通的成本。大家都很忙,瘦身很重要,但可能不是各自的核心目标,让十几条业务线的研发同学都积极参与并不简单,就像图片云化,如果能把细节屏蔽,参与的成本极低,想必大家也会乐于参与。 第四是沉淀实践经验和工具。业务一定是向前发展的,需求也是不断增加的,当再次需要治理时,利用已有的经验和工具,处理那些增加的部分,成本将会变得很低。6. 感谢这里对二手房源、客源、CA 、平台 B 端、入职道、新房 B 端、新房 C 端、租赁 B 端、VR、IM、Flutter 平台等团队每一位付出过的战友说声辛苦了,无论是10M、1M还是100K,都是收益,没有大小之分,只要意识有了,行动有了,相信我们的 App 会越来越好。另外要特别感谢 C 端的小伙伴,前期积累的图片云化的方案,对我们的瘦身治理帮助很大。参考资料1. 《今日头条优化实践:iOS 包大小二进制优化,一行代码减少 60 MB 下载大小 》(https://mp.weixin.qq.com/s__biz=MzI1MzYzMjE0MQ==&mid=2247487459&idx=1&sn=3dd9276f5af78ca5a377adec37e3e916&chksm=e9d0c401dea74d17e9f1bdd5ea764cc0cd7e845c6ebadde752d36608306b09e762a1681c7252&token=2034623783&lang=zh_CN#rd)2. LSUnusedResources(https://github.com/tinymind/LSUnusedResources/)3. ImageOptim(https://imageoptim.com)4. tinypng(https://tinypng.com)
预览时标签不可点
移动端37大前端69移动端 · 目录#移动端上一篇Native和Flutter混合开发ViewPager的解决方案下一篇贝壳APP页面预加载框架关闭更多小程序广告搜索「undefined」网络结果
|
|