简介
常见痛点
- 隔断功能几乎一致,各端需要单独配置研发人员
- 开发、维护成本高
- 安卓、IOS发版成本高
- ...
目标
-
研发效率高
- 学习成本低
- 多端一致性高 (兼容与维护)
-
用户体验好
- 稳定性高
- 性能体验好
-
动态化
- 支持动态化下发,满足容易增长的业务需求
跨端技术方案介绍
Hybrid
基于WebView渲染,通过JSBridge把一部分系统能力开放给JS调用
Web没有,但安卓、IOS需要具备的(摄像头、录音等能力)能力,用该技术开放给他
原生渲染方案
使用JS开发,通过中间层桥接后使用原生组件来渲染UI界面
- React Native JavaScript框架,让开发者使用JavaScript和React来开发跨平台移动应用。
-
React Native 的思路是最大化地复用前端的生态和 Native 的生态,和 WebView 容器的最大区别在于 View 的消染体系。React Native 抛弃了低效的浏览器内核染,转而使用自己的 DSL 生成中间格式,然后映射到对应的平台,渲染成平台的组件。相对 WebView 容器,体验会有一定的提升。不过,渲染时需要 Javascript 和原生之间通信,在有些场景可能会导致卡顿。另外就是,渲染还是在Native层,要求开发人员对Native有一定的熟悉度。
-
React Native 是一要有JSI、Fabric、 TurboModules 组成。
-
JSI是Javascript lnterface的缩写,一个用C++写成的轻量级框架,它作用就是通过JS1,JS对象可以直接获得C++对象(Host Obiects)引用,并用对应方法。
-
有了JSI,JS和Native就可以直接通信了,调用过程如下: JS -> JSI -> C++ -> ObiectC/Java
-
JSI是整个架构的核心和基石,所有的一切都是建立在它上面
-
JSI 将支持其他JS引警;
-
JSI允许线程之间的同步相互执行,不需要JSON 序列号等耗费性能的操作;JSI 是用 C++ 编写,以后如果针对电视、手表等其他系统,也可以很方便地移植;
-
自此三个线程通信再也不需要通过Bridge,可以直接知道对方的存在,让同步通信成为现实。具体的用法可以看 官方例子。 另外一个好处就是有了JS,JS引擎不再局限于JSC,可以自由的替换为V8,Hermes,进一步提高JS解析执行的速度。
-
Fabric
- Fabric 是新的渲染系统,它将取代当前的 UI Manager.
-
UIManager:
- 当 App 运行时,React 会执行你的代码并在JS 中创建一个 ReatElementTre ,基于这树染器会在 C++ 中创建一个ReactShadowIree。UIManager 会使用 Shadow Tree 来计算Ul 元素的位置,而一旦 Layout 完成,Shadow Tree 就会被转换为由 Native Elements 组成的HostViewTree (例如: RN 里的 会变成 Android 中的 ViewGroup 和 ios 中的 UIView)。 而之前线程之间的通信都发生在 Bridge 上,这就意味着需要在传输和数据复制上耗费时间。通过JSON格式来传递消息,每次都要经历序列化和反序列化。 而得益于前面的 SL,JS 可以直接调用 Native 方法,其实就包括了UI方法,所以JS和UI线程可以同步执行从而提高列表、跳转、手势处理等的性能。
-
Turbo Modules
- 在之前的望构中JS 使用的所有 Native Modules(例如蓝,地理位置,文件存储等)都必须在应里程打开之前进行初始化,这意味着即传用户不需要某些模块,但是它仍然必须在启动时进行初始化。
- Turbo Modules 基本上是对这些旧的 Native 模块的增强,正如在前面介绍的那样,现在 JS 将能够持有这些模块的用,所以JS 代码可以仅在需要时才加载对应模块,这样可以将显着缩短 RN 应用的启动时间。
自渲染方案
利用Skia重新实现渲染管线,不依赖原生组件
Flutter
引擎将底层C++ 代码包装成 Dart 代码,通过 dart:ui 暴露给 Flutter架层。该库暴露了最底层的原语,包括用于驱动输入、图形、和文本染的子系统的类。通常,开发者可以通过 Flutter 框架层 与 Flutter 交互,该架提供了以 Dart 语言编写的现代响应式架,它包括由一系列层组成的一组丰富的平台,布局和基础库。从下层到上层,依次有:
基础的 foundational 类及一些基层之上的构建块服务,如 animation、 painting 和 gestures,它们可以提供上层常用的抽象.
渲染层 用于提供操作布局的抽象。有了渲染层,你可以构建一棵可渲染对象的树。在你动态更新这些对象时,染树也会自动根据你的变更来更新布局,
widget 层是一种组合的抽象,每一个染层中的渲染对象,都在 widoets 层中有一个对应的类。此外,widets 层让你可以自由组合你需要复用的各种类。响应式编程模型就在该层级中被引入。
Material 和 Cupertino 库提供了全面的 widgets 层的原语组合,这套组合分别实现了 Material 和 ios 设计规范。
Flutter 框架相对较小,因为一些开发者可能会使用到 、的更高层级的功能已经被分到不同的软性包中,使用 Dat 和Flutter 的该心库实现,其中包括平台插件,如 camera 和 webview; 与平台无关的功能,例如 characters、 http 和 animations还有一些软件包来自于更为宽泛的生态系统中,如 应用内支付、Apple认证和 Lottie 动画。
小程序方案
使用小程序DSL +JS开发,通过中间层桥接后调用原生能力,使用webview渲染UI界面
小程序的运行环境分渲染层和逻辑层,其中 ttml 模板和 ttss 样式工作在渲染层,is 脚本工作在逻辑层.小程序的渲染层和逻辑层分别由 2 个线程管理:渲染层的界面使了Webview 进行染;逻辑层采用JSC 线程运行JS 脚本,一个小程存在多个界面,所以演染层存在多个 WebView 线程,这两个线程的通信会经由客户端(下文中也会采用 Native 来代指客户端)做中转,逻辑层发送网络请求也经由 Native 转发,小程序的通信模型如图所示
基于小程序跨端实践
启动性能体验
- 较少包体积
- 合理使用分包
- 使用分包加载是优化小程序启动耗时效果最明显的手段,建议开发者按照功能划分,将小程序的功能按使用频率和场景拆分成分包,实现代码包的按需加载。同时需要注意控制分包数量,避免过多拆包。
- 移除无用文件
- 目前小程序打包是会将工程下所有文件时]入代码包内,在开发过程中,如果不及时清理无用的资源,会使得包体积越来越大,开发过程中要养成良好的习惯——及时清理没有使用到的资源,防止资源冗余。
- 合理使用分包
- 控制包内静态资源
- 像免车代码包中包合或车 ts 中内联过多,过大的代码包内的图片,应尽量采用网络图片。代码包内的图片一般应只包含一些体规较小的图标、声音,视等其他类型的资源应尽量游免放到码包中。小程序代码包在下载时会使用 zp 算法进行压缩,降低下载时传输的数据量,这些资源文件会占用大量代码包体积,并且通常难以进一步被压缩,对于下载耗时的影响比代码文件要大很多。
- 减少同步逻辑
- 优先使用异步API在小程序
- 启动流程中,会注入开发者代码并顺序同步执行App.onLaunch,App.onShow,Page.onLoad,Page.onShow。在小程序初始化代码(Page、App 定义之外的内容)和启动相关的几个生命周期中,应避免过度使用 Sync 结尾的同步 API
- 可避免启动时运行过多同步代码
- 在小程序初始化代码(Page,App 定义之外的内容)和启动相关的几个生命周期中,应避免执行复杂的计算逻辑
- 优先使用异步API在小程序
- 更早的展示首屏数据
- 尽早调用关键API和请求
- 首屏绘制可能会依赖API数据和网络请求,尽早的调用相关API,发送相关网络请求,能提前数据准备时间。
- 接入数据预取
- 大部分小程序在渲染首页时,需要依赖服务端的接口数据,小程序为开发者提供了数活预取,方便开发者在小程序冷启动时提前发起请求,并缓存请求内容避免非必要的reLaunchreLaunch会先关闭所有页面,非必要的reLaunch会导致首页白屏时间明显增长。
- 尽早调用关键API和请求
- 合理缓存数据
- 网络数据缓存
- 小程序提供了 tt.getStorage、tt.setStorage 等读写本地存储的能力,数据存储在本地,返回的会比网络请求快。如果开发者基于某些原因无法采用数据预拉取,我们推荐优先从缓存中获取数据来渲染视图,等待网络请求返回后进行更新
- API数据缓存
- 对调用频次高的方法的结果进行缓存,例如对于 tt.getSystemlnfo, tt.getSystemlnfosync 的结果应进行缓存,避免重复调用
- 网络数据缓存
- 图片优化
- 选择合适的图片格式
- 对于不需要透明格式的图片,推荐采用jpeg 格式来代替 png 格式。如果有条件,尽可能使用webp格式图片,能大幅缩小图片体积。
- 进行合理的压缩图片
- 尽可能压缩到200kb以下,压缩的同时也需要兼顾图片的质量
- 使用CDN并开启缓存
- 使用CDN,能大幅减少图片资源的下载速度,开启 HTTP 缓存控制后,下一次加载同样的图片,会直接从缓存读取,大大提升图片加载速度.
- 选择合适的图片格式
- 更多优化手段
- 框架骨架屏
- 通过小程序框架提供骨架屏机制,能比业务中创建的骨架加载时机更靠前,使用这一机制,可以减少用户的白屏等待时长,给用户带来更好的体验,
- 占位组件
- 为自定义组件配置占位组件,可以指定该组件不在小程序启动时立即注入,而是等到项面中其他元素渲染完成后才注入。通过占位组件,能减少启动耗时
- 框架骨架屏
运行时性能体验
- 合理使用setData
- 减少发送频率
- 动画不使用setData
- 合理使用自定义组件
- 合理的拆分组件数量
- 动画不使用setData
- 合理使用自定义组件
- 合理的拆分组件数量
- Page 中的 setData 会触发染层以页面级别进行 diff 操作,如果页面比较复杂且没有使用自定义组件,那么 diff 的成本会很高导致体验比较差(更新卡顿、不粘手等感受)。如果页面转换为若干个组件,如果在组件中 setData,只会触发染层对应组件的diff 操作,diff 成本会降低很多,使用体验也会提升很多
- 只注册当前使用的组件
- 同时在usingComponents 建议只注册当前页面有使用到的自定义组件,在小程序框架会根据 usingComponents 中的自定义组件注册(无论开发者在运行时是否有使用) 。
- 同步修改初始data
- 在 app.json中增加配置 component2: true 后,支持在 created 生命周期中修改自定义组件初始数据,自定义组件将在 created生命周期执行完成后开始渲染。开启后有以下优化点:
- 能够很好的解决依赖计算逻辑导致的 data 频繁变更。避免初始数据依赖大量计算逻辑时,由于 data 变化导致页面渲染内容闪动或频繁变动问题
- 优化 observer 触发方式,减少因数据变更导致的通信,提升小程序性能体验合理的使用方式能进一步提升用户体验
- 合理的拆分组件数量
- 合理监听外理事件
- 合理监听处理 scroll 事件
- 避免在scroll事件中高频执行耗时操作,会明显降低FPS
- 去掉不必要的事件绑定
- 去掉不必要的事件绑定 (ttml中的 bind 和catch)从而减少通信的数据量和次数
- 合理监听处理 scroll 事件
- 内存优化
- 及时解绑事件监听
- 事件监听结束后,应及时解绑监听器
- 及时清理定时器
- 开发者在开发如[秒杀倒计时]等功能时,可能会使用 setlnterval 设置定时器,页面或组件销毁前,需要调用 clearinterval 方法取消定时器。
- 及时解绑事件监听
- 导航栏适配
- 适当开启自定义导航栏
- 避免在app.json中全局开启动态导航栏,仅在需要的页面中配置,降低适配成本。
- 关键信息避开状态栏和胶囊按钮
- 如果开启自定义导航栏,需要通过tt.getCustonBoundingClientRect获取自定义导航栏下不可改变的元素来进行导航栏的适配
- 适当开启自定义导航栏
- X分屏适配
- 通过onResize监听显示区域变化
- 小程序支持组件和页面的生命周期函数onResize用于在显示区域的尺寸发生变化的时候返回当前页面的信息。其中组件需要作为页面配置到app.json中触发事件。
- 不使用JS设置ScrollView高度
- scroll-view中分屏变为全时需要重新设置scrol-view高度,可能会出现空白区域问题,建议使用CSS(vh)完成自适应布局,
- 通过onResize监听显示区域变化