这是我参与「第五届青训营 」伴学笔记创作活动的第11天
课堂笔记
视频地址 「跨端技术概述」第五届字节跳动青训营 - 前端专场 (juejin.cn)
ppt地址 跨端技术概述.pptx - 飞书云文档 (feishu.cn)
概述
本节课程内容会分为以下几个方面:
- 跨端是什么,给大家介绍跨端产生的背景及解决的问题
- 跨端技术方案介绍,给大家介绍目前主流的跨端技术方案(hybrid 方案/原生渲染方案/自渲染方案/小程序方案)以及对比
- 基于小程序跨端实践,带大家了解如何开发一个小程序以及优化小程序
- 总结与展望
课前
- 了解一门前端开发框架(React/Vue), reactjs.org/, vuejs.org/
- 了解 React Native, reactnative.dev/
- 了解 Flutter, flutter.dev/
- 了解抖音小程序,developer.open-douyin.com/docs/resour…
- 了解前端和客户端通信原理
一、本堂课重点内容:
主流跨端技术方案
小程序开发与实践
二、详细知识点介绍:
跨端是什么?
跨端背景
随着业务的发展,产生了越来越多的业务场景,同时随着技术的发展,产生了越来越多的端,PC端(Windows、Mac),移动端(安卓、iOS)、web端、IoT设备(车载设备、手表)等
痛点:
1.各端功能几乎一致、各端需要单独配置研发人员
2.开发、维护成本高
3.安卓、iOS发版周期长
….
跨端技术方案目标
1.研发效率高
(1)学习成本低
(2)多端一致性高
2.用户体验好
(1)稳定性高
(2)性能体验好
3.动态化
(1)支持动态化下发,满足日益增长的业务需求
跨端技术方案介绍
hybrid方案
基于WebView渲染,通过JS Bridge把一部分系统能力放开给JS调用
WebView容器的工作原理是基于Web技术来实现界面和功能,通过将原生的接口封装、暴露给JavaScripti调用,JavaScript编写的页面可以运行在系统自带的WebView中。这样做的优势是,对于前端开发者比较友好,可以很快地实现页面。
跨端,同时保留调用原生的能力,通过搭建桥接层和原生能力打通。但这种设计,跨端的能力受限于桥接层,当调用之前没有的原生能力时,就需要增加桥。另外,浏览器内核的渲染独立于系统组件,无法保证原生体验,渲染的效果会 差不少。
原生渲染方案
使用JS开发,通过中间层桥接后使用原生组件来渲染UI界面
React Native
如React Native是一个由Facebook于2015年9月发布的一款开源的JavaS框架,它可以让开发者使用Javascript和React来开发跨平台的移动应用。
React Native的思路是最大化地复用前端的生态和Native的生态,和WebView容器的最大区别在于View的渲染体系。React Native抛弃了低效的浏览器内核渲染,转而使用自己的DSL生成中间格式,然后映射到对应的平台,渲染 成平台的组件。相对WebView容器,体验会有一定的提升。不过,渲染时需要lavaScript和原生之间通信,在有些场景可能会导致卡顿。另外就是,渲染还是在Native层,要求开发人员对Native有一定的熟悉度。
React Native是一主要有JSl、Fabric、TurboModules组成,如下图所示:
- JSI JSl是avascript Interfacel的缩写,一个用C++写成的轻量级框架,它作用就是通过JSl,JS对象可以直接获得C++对象(Host Objects)l用,并调用对应方法。 有了JSl,JS和Native就可以直接通信了,调用过程如下:JS->JSl->C++->ObjectC/小ava
JS是整个架构的核心和基石,所有的一切都是建立在它上面。
JSI将支特其他JS引擎: JS1允许线程之间的同步相互执行,不需要JSON序列号等耗费性能的操作 S1是用C++编写,以后如果针对电视、手表等其他系统,也可以很方便地移植:
自此三个线程通信再也不需要通过Bdg,可以直接知道对方的存在,上同步通信成为实。具体的用法可以看官方例子。 另外一个好处就是有了JSl,JS引擎不再局限于JSC,可以自由的替换为V8,Hermes,进一步提高JS解析执行的速度。
- Fabric Fabric是新的渲染系统,它将取代当前的Ul Manager。
- UI Manager: 当App运行时,React会执行你的代码并在JS中创建一个ReactElementTree,基于这棵树渲染器会在C+中创建一个ReactShadowTree。.UI Manager会使用Shadow Tree来计算UI元素的位置,而一旦Layout完成,Shadow Tree就会被转换为由Native Elements组成的HostViewTree(例如:RN里的会变成Android中的ViewGroup和iOS中的UIView)。
而之前线程之间的通信都发生在Bdge上,这就意味着需要在传输和数据复制上耗费时间。通过JSON格式来传递消息,每次都要经历序列化和反序列化。
而得益于前面的JS1,JS可以直接调用Native方法,其实就包括了UI方法,所以JS和UI线程可以同步执行从而提高列表、跳转、手势处理等的性能。
- Turbo Modules 在之前的架构中JS使用的所有Native Modules(例如蓝牙、地理位置、文件存储等)都必须在应用程序打开之前进行初始化,这意味着即使用户不需要某些模块,但是它仍然必须在启动时进行初始化。
Turbo Modules基本上是对这些旧的Native模块的增强,正如在前面介绍的那样,现在JS将能够持有这些模块的引用,所以JS代码可以仅在需要时才加载对应模块,这样可以将显着缩短RN应用的启动时间。
自渲染方案
利用Skia重新实现渲染管线,不依赖原生组件
如Flutter
Flutter
Fultter是Google在2018年世界移动大会公布的开源应用开发框架,仅通过一套代码库,就能构建精美的、原生平台编译的多平台应用
2018年Google推出Flutter,通过Dart语言构建一套跨平台的开发组件,所有组件基于Skia引擎自绘,在性能上可以和Native平台的View相媲美。Flutter站在前人的肩膀上,参考了React的状态管理、Web的自绘制U、儿React Nativel的 HotReload等特点,同时考虑了与Native通信的Channel机制、自渲染、完备的开发工具链。Flutter与上述Recat Native、WebView容器本质上都是不同的,它没有使用WebView、JavaScript解释器或者系统平台自带的原生控件,而是有 套自己专属的Vidget,底层渲染使用自身的高性能C/C++引擎自绘
对于底层操作系统而言,Flutter应用程序的包装方式与其他原生应用相同。在每一个平台上,会包含一个特定的嵌入层,从而提供一个程序入口,程序由此可以与底层操作系统进行协调,访问诸如surface渲染、 辅助功能和输入等服务,并且管理事件循环队列。该嵌入层采用了适合当前平台的语言编写,例如Android使用的是Java和C++,iOS和macOS使用的是Objective-C和Objective-C++,Windows和Linux使用的是C++。Flutter代码可以通过嵌入层,以模块方式集成到现有的应用中,也可以作为应用的主体。Futr本身包含了各个常见平台的嵌入层,同时也存在一些其他的嵌入层。
Flutter引擎毫无疑问是Flutter的核心,它主要使用C++编写,并提供了Flutter应用所需的原语。当需要绘制新一帧的内容时,引擎将负责对需要合成的场景进行栅格化。它提供了Flutter核心API的底层实现,包括图形(通过Skia)、文本布局、文件及网络IO、辅助功能支持、插件架构和Dat运行环境及编译环境的工具链。
引擎将底层C++代码包装成Dat代码,通过dart:ui暴露给Flutter框架层。该库暴露了最底层的原语,包括用于驱动输入、图形、和文本渲染的子系统的类。 通常,开发者可以通过Flutter框架层与Flutter交互,该框架提供了以Dat语言编写的现代响应式框架。它包括由一系列层组成的一组丰富的平台,布局和基础库。从下层到上层,依次有:
基础的foundational类及一些基层之上的构建块服务,如animation、painting和gestures,它们可以提供上层常用的抽象。 渲染层用于提供操作布局的抽象。有了渲染层,你可以构建一棵可渲染对象的树。在你动态更新这些对象时,渲染树也会自动根据你的变更来更新布局。
widget层是一种组合的抽象。每一个渲染层中的渲染对象,都在widgets层中有一个对应的类。此外,widgets层让你可以自由组合你需要复用的各种类。响应式编程模型就在该层级中被引入。
Material和Cupertino库提供了全面的widgets层的原语组合,这套组合分别实现了Material和iOS设计规范。
Flutter框架相对较小,因为一些开发者可能会使用到的更高层级的功能已经被拆分到不同的软件包中,使用Dart和Flutter的核心库实现,其中包括平台插件,例如camera和webview;与平台无关的功能,例如characters、.http和 animations。还有一些软件包来自于更为宽泛的生态系统中,例如应用内支付、Apple认证和Lottie动画。
小程序方案
使用小程序DSL+JS开发,通过中间层桥接后调用原生能力,使用webview来渲染UI界面
字节小程序
字节小程序是一种全新的连接用户与服务的方式,它可以在宿主内被便捷地获取和传播,同时具有出色的使用体验。
首先,我们来简单了解下小程序的运行环境。小程序的运行环境分成渲染层和逻辑层,其中t模板和tss样式工作在渲染层,js脚本工作在逻辑层。
小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView进行渲染;逻辑层采用JSC线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由客户端(下文中也会采用Native来代指客户端)做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型如图所示
跨端技术方案对比
基于小程序跨端实践
快速开发一个小程序
2.小程序开发、调试
3.小程序上传
小程序性能优化
小程序性能是指小程序在字节系APP(抖音、今日头条等客户端)中加载和呈现的速度,以及用户交互的响应程度。性能问题是多种多样的,情况好点的,有的小程序会产生一些正识,这些征尺会给用户带来一些不好的交互体。也有极其糟糕的情况,那就是小程序完全无法使用,对用户输入没有反应,或两者兼而有之。这些问题将在不同程度上影响用户体验,从而导致用户流失」
为了能够衡量小程序性能,我们需要一系列指标来描述小程序启动过程中的关键阶段。 以懂车帝小程序为例,从小程序启动到完成我们分为以下几个关键环节
- 小程序启动后会显示一个loading view在这个阶段会初始化小程序环境
- 初始化完成后开始加载小程序,然后开始第二个阶段首次绘制,这个时机开始渲染小程序的首帧
- 第三个阶段是【最大内容会制】,在这时已经展示页面中的最大元素,也是比较接近用户视觉感知效果
- 最后呢是主要元素加载完成,并且达到了可交互的状态
- 通过以上启动阶段,可以将用户体验数字化表达
当然,启动环节耗时并不能完全真实反映用户体验,我们也借助实际用户行为,异常监控,作为辅助指标 包含:取消率、白屏率、LCP到达率
- 取消率是指用户在加载过程中点击返回或右上角关闭的占比,用于描述小程序的启动性能,用户取消率越低,启动性能越好。
- 白屏率是指从启动到退出白屏的占比,出现白屏表示页面渲染失败,我们目标是尽可能降低白屏率
- LCP到达率,如果较多用户在LCP到达前离开,也表示小程序可能出现异常,或用户体验较差
- 借助启动阶段指标及辅助指标,我们可以较为准确的描绘出小程序的体验感知
优化意义
1.留住用户
2.提升转化率
3.提升用户体验
性能指标
-
错误日志
-
性能体验
-
小程序加载耗时
-
取消率
-
重启率
-
卡死次数占比
-
LCP到达率
-
白屏率
-
JS影响用户率
-
优化手段
一、启动性能体验
1.1较少包体积
- 合理使用分包
使用分包加载是优化小程序启动耗时效果最明显的手段。建议开发者按照功能划分,将小程序的功按使用频率和场景拆分成分包,实现代码包的按需加载。同时需要注意控制分包数量,避免过多拆包
- 移除无用文件
目前小程序打包是会将工程下所有文件都附打入代码包内,在开发迭代过程中,如果不及时清理无用的资源,会使得包体积越来越大,开发过程中要养成良好的习惯-及时清理没有使用到的资源,防止资源冗余。
- 控制包内静态资源
避免在代码包,中包含或在t$5中内联过多、过大的代码包内的图片,应尽量采用网络图片。代码包内的图片一般应只包含一些体积较小的图标。声音、视频等其他类型的资源应尽量避免放到代码包中。 小程序代码包在下载时会使用GZ叩算法进行压缩,降低下载时传输的数据量。这些资源文件会占用大量代码包体积,并且通常难以进一步被压缩,对于下载耗时的影响比代码文件要大很多。
1.2减少同步逻辑
- 优先使用异步API
在小程序启动流程中,会注入开发者代码并顺序同步执行App.onLaunch,App.onShow,Page.onLoad,Page.onShow。在小程序初始化代码(Page,App定义之外的内容)和启动相关的几个生命周期中,应避免过度使用Sync结尾的同步API
- 避免启动时运行过多同步代码
在小程序初始化代码(Pge,App定义之外的内容)和启动相关的几个生命周期中,应避免执行复杂的计算逻辑。
1.3更早的展示首屏数据
- 尽早调用关键API和请求
首屏绘制可能会依赖API数据和网络请求,尽早的调用相关八PI,发送相关网络请求,能提前数据准备时间。
- 接入数据预取
大部分小程序在渲染首页时,需要依赖服务端的接口据,小程序为开发者提供了数据预取,方便开发者在小程序冷启动时提前发起请求,并缓存请求内容
- 避免非必要的reLaunch
reLaunch会先关闭所有页面,非必要的reLaunch会导致首页白屏时间明显增长,
1.4合理缓存数据
- 网络数据缓存
小程序提供了tt.getStorage、t.setStorage等读写本地缓存的能力,数据存储在本地,返回的会比网络请求快。如果开发者基于某些原因无法采用数据预拉取,我们推荐优先从缓存中获取数据来渲染视图,等待网络请求返回后进行更新。
- API数据缓存
对调用频次高的方法的结果进行缓存,例如对于tt.getSystemInfo,tt.getSystemlnfoSync的结果应进行缓存,避免重复调用。
1.5图片优化
- 选择合适的图片格式
对于不需要透明格式的图片,推荐采用jpeg格式来代替png格式。如果有条件,尽可能使用wbp格式图片,能大幅缩小图片体积。
- 进行合理的压缩
图片尽可能压缩到200kb以下,压缩的同时也需要兼顾图片的质量。
- 使用CDN并开启缓存
使用CDN,能大幅减少图片资源的下载速度。开启HTTP缓存控制后,下一次加载同样的图片,会直接从缓存读取,大大提升图片加载速度
1.6更多优化手段
- 框架骨架屏
通过小程序框架提供骨架屏机制,能比业务中创建的骨架屏加载时机更靠前,使用这一机制,可以减少用户的白屏等待时长,给用户带来更好的体验。
- 占位组件
为自定义组件配置占位组件,可以指定该组件不在小程序启动时立即注入,而是等到页面中其他元素渲染完成后才注入。通过占位组件,能减少启动耗时。
二、运行时性能体验
2.1合理使用setData
- 减少发送频率
- 动画不使用setData
2.2合理使用自定义组件
- 合理的拆分组件数量
会触发渲染层对应组件的d仟操作,d什成本会降低很多,使用体验也会提升很多。
- 只注册当前使用的组件
同时在usingComponents建议只注册当前页面有使用到的自定义组件,在小程序框架会根据usingComponents中的自定义组件注册(无论开发者在运行时是否有使用)。
- 同步修改初始data
在app.json中增加配置componenta2:true后,支持在created生命周期中修改自定义组件初始数据,自定义组件将在created生命周期执行完成后开始渲染。开启后有以下优化点:
- 能够很好的解决依赖计算逻辑导致的data频繁变更。避免初始数据依赖大量计算逻辑时,由于data变化导致页面渲染内容闪动或频繁变动问题
- 优化observer触发方式,减少因数据变更导致的通信,提升小程序性能体验 合理的使用方式能进一步提升用户体验
2.3合理监听处理事件
- 合理监听处理scro事件
避免在scro事件中高频执行耗时操作,会明显降低FPS
- 去掉不必要的事件绑定
去掉不必要的事件绑定(ttml中的bind和catch),从而减少通信的数据量和次数
2.4内存优化
- 及时解绑事件监听
事件监听结束后,应及时解绑监听器
- 及时清理定时器
开发者在开发如「秒杀倒计时」等功能时,可能会使用setlnterval设置定时器,页面或组件销毁前,需要调用clearlnterval方法取消定时器。
2.5导航栏适配
- 适当开启自定义导航栏
避免在āop.json中全局开启动态导航栏,仅在需要的页面中配置,降低适配成本。
- 关键信息避开状态栏和胶囊按钮
如果开启自定义导航栏,需要通过tt.getCustomButtonBoundingClientRect获取自定义导航栏下不可改变的元素来进行导航栏的适配
2.6 X分屏适配
- 通过onResizel监听显示区域变化
小程序支持组件和页面的生命周期函数onResize用于在显示区域的尺寸发生变化的时候返回当前页面的信息。其中组件需要作为页面配置到pp,json中触发事件。
- 不使用S设置ScrollViewi高度
scroll-view中分屏变为全屏时需要重新设置scroll-iew高度,可能会出现空白区域问题,建议使用CSS(vh)完成自适应布局。
性能评分工具
性能分析工具
三、引用参考:
四、课后
- 跨端解决了什么问题?
- 常见的跨端技术方案有哪些?各方案的技术原理是什么?他们之间有什么区别?
- 跨端技术方案中有哪几个核心部分?
- 抖音开发者工具提供了哪些能力?
- 抖音小程序开发与 web 开发有哪些异同?
- 小程序性能优化会关注哪些性能指标?分为哪几个方向进行性能优化?可以使用哪些工具来提升效率?
- 小程序 setData 如何优化?
五、补充资料
补充资料: