服务24亿级用户App的大前端实践

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"茄子科技(海外 SHAREitGroup)是国内出海的先行者,2015 年成立后就“走出去”。2017 年,主要产品 SHAREit(国内茄子快传)全球用户总量超 12 亿,成为新兴市场的“国民应用”。截止目前,茄子科技产品矩阵全球累计安装用户量近 24亿,覆盖 200 多个国家和地区,涵盖全球 45 种语言。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服务全球几十亿用户,茄子科技如何提升 App 的用户体验?怎样解决 App 的崩溃问题?如何应对海外复杂的网络问题?...... 针对上述问题,InfoQ 记者采访了茄子科技前端负责人。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"提供高品质用户体验"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用户体验是 App 应用的核心追求之一。在所有用户体验问题中,崩溃属于最高优先级。一旦出现崩溃,就意味着 App 彻底不可用,这会给用户体验造成非常大的伤害。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"解决应用崩溃问题"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"据茄子科技前端负责人介绍,为解决崩溃问题,他们自建了一个 APM 平台,这样可以快速发现问题和协助定位问题。APM 平台的价值在于把人力从繁琐、重复性工作中解放出来,从而专注于问题本身。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在自建 APM 平台中,要着重提高的是客户端的抓取成功率和平台易用性。前者是为了尽量全面还原崩溃现场,后者是为了尽可能提升团队解决问题的效率。除了 APM 平台,要长期解决崩溃问题还需要人和一套持续运转的机制。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最初解决崩溃问题时,落脚点在于解决存量问题和遏制新增问题。首先,他们发起一场集中性战役——全力解决存量崩溃问题。经过一个阶段的治理,崩溃率下降到一个极低水平。其次,为防止崩溃率再次飙升,他们又开始了崩溃预防,把问题消灭在萌芽阶段:梳理出已解决的 Crash 问题,然后归类,并由点到面地思考总结出预防手段。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如,针对服务端 API 脏数据引起的问题,统一在网络层进行校验和兜底;针对一些第三方库引发的问题,用 AOP 手段进行 Hook。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在交付流程中,添加编译检查阶段、自动化测试覆盖、更广的灰度触达等环节,尽可能将问题提前暴露。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"即使问题发生在真实场景,APM 平台也能实现分钟级报警,并且自动把问题分配到相关同学。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"茄子科技前端负责人表示,“我们以 APM 平台为依托,预防为主,建立了一套完善的响应机制”。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"App 优化实践"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了应用崩溃,性能优化则是影响用户体验另一个关键。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"据了解,茄子科技旗下的 SHAREit,其用户覆盖 200 多个国家和地区。不仅用户设备非常复杂,而且网络状况也不好,因此用户更容易遇到 App 卡顿、启动慢等体验类问题。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为解决这些体验类问题,他们首先搭建了基于线上的性能指标体系,充分衡量用户遇到的体验类问题。然后根据重要程度评估优先级,抓住重点,逐个击破。比如,在启动速度优化上,常规的优化手段作用有限,他们为此开发和落地了任务调度框架。所有启动任务通过任务调度框架进行调度,这样不仅可以充分利用启动阶段 CPU 的能力,而且还能方便地收集到每一个任务在线上的真实执行情况。之后根据任务执行情况,进行针对性调整。同时,他们还通过 ASM 收集到在启动阶段抢先执行的 Msg,然后与业务方一起确认并且后移。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"网络优化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在网络层面,针对海外用户的网络环境复杂、质量较低等问题,茄子科技通过HTTPDNS、协议优化、按需加载、CDN 预热、离线资源包和服务端渲染等方案,大幅提升网络请求的成功率。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"考虑到 SHAREit 也是一个内容型产品,他们还在播放器层面进行优化,加入多码率预缓存策略、软硬解码自适应、动态追帧等技术,大幅提升视频相关的业务指标。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"包体优化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在包体积方面,茄子科技进行过一次大的优化,应用包大小从 42M 缩减为15M。据悉,包体优化主要分为两个方面:技术层面和业务层面。在技术上,他们采取了五项举措:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一是删减无用代码、资源,精简第三方库;二是代码层面的统一优化,比如 R文件删除、使用 R8;三是图片压缩、格式转换和剔除重复图片;四是资源的混淆;五是 dex 的再次压缩。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在业务上,梳理出 App 的整体功能,下掉了渗透率较低的功能,并且对重点功能均实现 Bundle 化,这样在减小包大小的同时,也方便在不同国家的精细化运营。并且,技术同学为业务同学提供专门工具,从技术视角支撑和指导业务的优化方向。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"茄子科技前端负责人表示,体验类的优化工作并不只涉及技术,单纯的技术优化非常容易遇到瓶颈或遇到无法推动业务方修改的困境,从而无法实现优化收益最大化。因此,他们在 SHAREit 的实践首先是调整项目定位,由技术同学主导,加入业务同学,统一战线。在更高层面统一双方目标,技术同学负责发掘优化项,提供工具和具体方案,业务同学负责更大规模的落地,各自发挥优势,双方共担项目,共享成果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了启动、网络优化、包体优化外,他们还对卡顿、内存、布局、电量、存储和线程等性能的多个维度进行优化,“我们的追求就是让用户在使用产品时有一个高品质体验”。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前端工程化建设:从石器时代到信息时代"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前端工程化,通俗的理解是,尽可能快速地实现可信赖的产品。其中两个关键词,“快速”,讲求开发速度、构建速度、测试速度、问题定位速度;“可信赖”,包括代码的质量、产品的性能、安全、及重现和定位问题能力。茄子科技前端负责人介绍,SHAREit 的前端工程化建设经历了三个阶段:石器时代,工业时代,信息时代。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"石器时代"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在石器时代,典型特征是单工程的 MVC 架构,公司发展初期人比较少,反而效率更高,问题也比较少。但随着公司发展越来越快,SHAREit 从单业务发展到多业务的平台型产品时,单工程架构在人员节奏方面出现了较多问题,如代码耦合导致一些问题浮现出来。这时,便开始工程组件化重构,进入到工业化时代。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"工业时代"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"工业化时代,典型的特征是组件化。茄子科技使用的是多工程、多仓库的方案,每个组件或 SDK 都有自己独立的仓库,都可以独立于主工程进行单独的编译和运行。这方面分为三大层:APP 的壳、业务层、基础层,基础层再往下还有更多、更细粒度的划分。前端团队自上而下通过 AAR 的方式进行依赖。组件化采用“分而治之”的思想,很好地解决了多团队协同作战的问题。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"信息时代"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"随着公司孵化更多产品,每个产品在不同国家定制不同功能,规模越来越大了,用以前的组件化形式很难高效地支撑现有的业务,于是,茄子科技引入了GoogleBundle 技术,进入到信息化时代。其实针对这一问题,国内多采用插件化的方案,但由于海外不能做插件化技术,茄子科技只能运用谷歌自有特性。在信息化时代,典型的特征是 Bundle 组件化。Bundle 模块具有反向依赖的特征。通过增加 Bundle 壳及自动化检测工具的形式,让 AppBundle 的特性和以前已有的组件化融合,让开发者保持已有的开发模式。通过 AppBundle,可以做到针对每个国家用一个 APP 按需定制不同功能,如内容、直播、游戏等。为提升效率和质量,茄子科技还搭建了客户端 CI\/CD 平台,流程主要有编码规范检测、大文件 \/ 图片检测、静态代码扫描,关键文件触发 Review、代码Review、安全风险检测,预编译、包体积监控,编译速度监控等,能做到自动打包、自动检测、自动化测试与发布。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"据悉,前端团队已经涵盖开发、测试、构建、部署一系列流程,通过自研APM 系统的预警机制、自动分配、辅助信息等,能够及时发现且快速定位问题,并优化迭代,最终形成整个研发流程闭环。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Bundle 技术的应用与实践"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2019 年,茄子科技前端团队开始接触 Bundle 技术,并在当时做了试点。通过这次尝试,他们发现 Bundle 模块的开发、编译和发布等都与原来的 Library形式有很多不同。因此,他们做了 Bundle 组件化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"据悉,他们将 Bundle 的开发、编译、发布等特性与现有组件化方式融合,让业务开发者保持原有开发模式对 Bundle 的特性几乎无感知。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在具体实践中,茄子科技前端团队做了四件事:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一,为每个 Bundle 组件增加一个壳。Bundle 组件还是以原有的 Library 开发和发布。Bundle 壳依赖 App 壳和 Bundle 组件。Bundle 壳中只是配置了Bundle 的集成属性,无任何逻辑代码。这样很好的解决了 Bundle 模块反向依赖宿主的特性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二,资源隔离。Bundle 组件与宿主资源隔离,不能互相访问。针对这种情况,他们做了自动化检测工具,将问题暴露在编译阶段。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"举个例子,GameListFragment 是 Bundle 组件的代码,继承了BaseRequestListFragment(宿主的基础 SDK 代码)。其布局资源文件使用了 ActionPullToRefresh 控件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/af\/afdd58e89cb472a4ea09de1118254351.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当调用 findViewById(R.id.pull_to_refresh) 时,Bundle 组件调用了宿主资源,这样编译是可以通过的,但是会在运行时出现问题。解决办法是由子类提供getXXId 方法,父类复写即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/18\/1880df050d8b30455a924820e94269d5.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三,APT\/AOP 等第三方库不好用,他们通过修改其源码进行适配。比如使用的 Android 路由框架 Router,修改其自定义的 GradlePlugin 的扫描Scope,让它可以扫描到 Bundle 组件及其依赖。此外,通过 Bundle 模块安装状态,判断是否将 apt 生成路由信息注入到 ServiceLoaderInit,保证有无Bundle 模块时,路由信息都可以成功注册。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第四,线下模拟真实的 Bundle 模块组合。结合 CI\/CD 在打包时,增加按国家、语言等多种维度的产出 aab 集合,方便测试人员在线下测试各种 Bundle 模块组合成 aab 的情况。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"结果,Bundle 组件化的实施不仅极大减小了应用包大小,SHAREit 从原来的42M 缩小到 15M。而且,App 实现了动态按需下发,除本身支持的国家、语言、屏幕等之外,还会按照需求下发,比如创作者中心模块只有在创作者登录后才会有入口引导下载。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章