前端概述 | 青训营笔记

55 阅读16分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天 冲冲冲!

跨端

跨端是什么

跨端背景

随着业务的发展,产生了越来越多的业务背景,同时随着技术的发展,产生了越来越多的端,PC端(Windows、Mac),移动端(安卓、IOS) 、web端,IoT设备(车载设备、手表)等

常见痛点:

  • 各端功能几乎一致,需要单独配置研发人员
  • 开发、维护成本高
  • 安卓、IOS发版周期长
  • ...

跨端技术方案目标

  • 研发效率高
    • 学习成本低
    • 多端一致性高
  • 用户体验好
    • 稳定性高
    • 性能体验好
  • 动态化
    • 支持动态化下发,满足日益增长的业务需求

跨端技术方案介绍

Hybrid方案

基于WebView渲染,通过JS Bridge把一部分系统能力开发给JS调用

01.png

02.png

WebView容器的工作原理是基于Web技术来实现界面和功能,通过将原生的接口封装、暴露给JavaScript调用,JavaScript 编写的页面可以运行在系统自带的WebView中。这样做的优势是,对于前端开发者比较友好,可以很快地实现页面跨端,同时保留调用原生的能力,通过搭建桥接层和原生能力打通。 但这种设计,跨端的能力受限于桥接层,当调用之前没有的原生能力时,就需要增加桥。另外,浏览器内核的渲染独立于系统组件,无法保证原生体验,渲染的效果会差不少。

原生渲染方案

使用JS开发,通过中间层桥接后使用原生组件来渲染UI界面

在 Android 开发中是使用KotlinJava来编写视图; 在iOS开发中是使用SwiftObjective-C来编写视图。 在React Native中,则使用React组件通过JavaScript来调用这些视图。在运行时React Native为这些组件创建相应的AndroidiOS视图。 由于React Native组件就是对原生视图的封装,因此使用React Native编写的应用外观、感觉和性能与其他任何原生应用一样。 我们将这些平台支持的组件称为原生组件。

  • 原生渲染方案---React Native
    • React Native是一个由Facebook于 2015 年 9 月发布的一款开源的JavaScript框架,它可以让开发者使用JavaScriptReact来开发跨平台的移动应用

03.png React Native的思路是最大化地复用前端的生态和Native的生态,和WebView容器的最大区别在于View 的渲染体系。React Native抛弃了低效的浏览器内核渲染,转而使用自己的DSL 生成中间格式,然后映射到对应的平台,渲染成平台的组件。相对WebView容器,体验会有一定的提升。不过,渲染时需要JavaScript 和原生之间通信,在有些场景可能会导致卡顿。另外就是,渲染还是在Native层,要求开发人员对Native有一定的熟悉度。

React Native是一主要有JSI、Fabric、TurboModules组成。

JSI JSIJavascript Interface的缩写,一个用C++写成的轻量级框架,它作用就是通过JSIJS对象可以直接获得C++对象(Host Objects)引用,并调用对应方法。 有了JSIJSNative就可以直接通信了,调用过程如下:JS->JSI->C++->ObjectC/Java

JSI是整个架构的核心和基石,所有的一切都是建立在它上面。

04.png

自渲染方案

  • 自渲染方案---flutter

利用Skia重新渲染管线,不依赖原生组件 FlutterGoogle在2018年世界移动大会公布的开源应用开发框架,仅通过一套代码库,就能构建精美的、原生平台编译的多平台应用

05.png 2018年Google推出Flutter,通过Dart语言构建一套跨平台的开发组件,所有组件基于Skia引擎自绘,在性能上可以和Native 平台的View相媲美。Flutter站在前人的肩膀上,参考了React的状态管理、Web的自绘制UIReact NativeHotReload 等特点,同时考虑了与Native通信的Channel机制、自渲染、完备的开发工具链。Flutter与上述React NativeWebView 容器本质上都是不同的,它没有使用WebViewJavaScript解释器或者系统平台自带的原生控件,而是有一套自己专属的Widget ,底层渲染使用自身的高性能C/C++引擎自绘。

对于底层操作系统而言,Flutter 应用程序的包装方式与其他原生应用相同。在每一个平台上,会包含一个特定的嵌入层,从而提供一个程序入口,程序由此可以与底层操作系统进行协调,访问诸如surface 渲染、辅助功能和输入等服务,并且管理事件循环队列。该嵌入层采用了适合当前平台的语言编写,例如Android使用的是JavaC++iOSmacOS使用的是Objective-CObjective-C++``WindowsLinux使用的是C++Flutter 代码可以通过嵌入层,以模块方式集成到现有的应用中,也可以作为应用的主体。Flutter本身包含了各个常见平台的嵌入层,同时也存在一些其他的嵌入层。

Flutter引擎 毫无疑问是Flutter的核心,它主要使用C++编写,并提供了Flutter 应用所需的原语。当需要绘制新一帧的内容时,引擎将负责对需要合成的场景进行栅格化。它提供了Flutter核心API 的底层实现,包括图形(通过Skia)、文本布局、文件及网络IO、辅助功能支持、插件架构和Dart运行环境及编译环境的工具链。

引擎将底层C++代码包装成Dart代码,通过dart:ui暴露给Flutter框架层。该库暴露了最底层的原语,包括用于驱动输入、图形、和文本渲染的子系统的类。 通常,开发者可以通过Flutter框架层与Flutter交互,该框架提供了以Dart语言编写的现代响应式框架。它包括由一系列层组成的一组丰富的平台,布局和基础库。从下层到上层,依次有:

基础的foundational类及一些基层之上的构建块服务,如animationpaintinggestures,它们可以提供上层常用的抽象。

渲染层 用于提供操作布局的抽象。有了渲染层,你可以构建一棵可渲染对象的树。在你动态更新这些对象时,渲染树也会自动根据你的变更来更新布局。

widge层是一种组合的抽象。每一个渲染层中的渲染对象,都在widgets层中有一个对应的类。此外,widgets 层让你可以自由组合你需要复用的各种类。响应式编程模型就在该层级中被引入。

MaterialCupertino库提供了全面的widgets层的原语组合,这套组合分别实现了MaterialiOS设计规范。

Flutter框架相对较小,因为一些开发者可能会使用到的更高层级的功能已经被拆分到不同的软件包中,使用DartFlutter 的核心库实现,其中包括平台插件,例如camerawebview;与平台无关的功能,例如charactershttpanimations 。还有一些软件包来自更为宽泛的生态系统中,例如:应用内支付、Apple认证和Lottie动画。

06.png

小程序方案

使用小程序DSL+JS开发,通过中间层桥接后调用原生能力,使用webview来渲染UI界面

  • 小程序方案---字节小程序
    • 字节小程序是一种全新的连接用户与服务的方式,它可以在宿主(抖音。头条等App)内被便捷地获取和传播,同时具有出色的使用体验

首先,我们来简单了解下小程序的运行环境。小程序的运行环境分成渲染层和逻辑层,其中ttml模板和ttss样式工作在渲染层,js 脚本工作在逻辑层。 小程序的渲染层和逻辑层分别由 2 个线程管理:渲染层的界面使用了WebView进行渲染;逻辑层采用JSC线程运行JS脚本。 一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由客户端(下文中也会采用 Native 来代指客户端)做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型如图所示 07.png

08.png

跨端技术方案对比

09.png

基于小程序跨端实践

小程序开发文档 小程序开发者工具

快速开发一个小程序

  • 下载小程序开发者工具
  • 小程序开发、调试
  • 小程序上传 010.png

011.png

012.png

小程序性能优化-优化意义

  • 留住用户
  • 提升转化率
  • 提升用户体验

小程序性能是指小程序在字节系APP (抖音、今日头条等客户端)中加载和呈现的速度,以及用户交互的响应程度。性能问题是多种多样的,情况好点的,有的小程序会产生一些延迟,这些延迟会给用户带来一些不好的交互体验。也有极其糟糕的情况,那就是小程序完全无法使用,对用户输入没有反应,或两者兼而有之。这些问题将在不同程度上影响用户体验,从而导致用户流失。

留住用户: 我们希望用户通过小程序进行有意义的交互。如果是新闻资讯小程序,我们希望用户能更方便获取信息。如果是电商小程序,我们希望将潜在的购物者变成买家。如果它是一个社交小程序,我们希望访问者写帖子,上传照片,并与其他人互动

为了能够衡量小程序性能,我们需要一系列指标来描述小程序启动过程中的关键阶段。 以懂车帝小程序为例,从小程序启动到完成我们分为以下几个关键环节

  • 小程序启动后会显示一个loading view在这个阶段会初始化小程序环境
  • 初始化完成后开始加载小程序,然后开始第二个阶段-首次绘制,这个时机开始渲染小程序的首帧
  • 第三个阶段是【最大内容绘制】,在这时已经展示页面中的最大元素,也是比较接近用户视觉感知效果
  • 最后呢是主要元素加载完成,并且达到了可交互的状态 通过以上启动阶段,可以将用户体验数字化表达

当然,启动环节耗时并不能完全真实反映用户体验,我们也借助实际用户行为,异常监控,作为辅助指标 包含:取消率、白屏率、LCP到达率

  • 取消率是指用户在加载过程中点击返回或右上角关闭的占比,用于描述小程序的启动性能,用户取消率越低,启动性能越好。
  • 白屏率是指从启动到退出白屏的pv占比,出现白屏表示页面渲染失败,我们目标是尽可能降低白屏率
  • LCP到达率,如果较多用户在LCP到达前离开,也表示小程序可能出现异常,或用户体验较差

借助启动阶段指标及辅助指标,我们可以较为准确的描绘出小程序的体验感知

013.png

小程序-性能优化

平台指标看板

014.png

015.png

小程序性能优化-优化手段

较少包体积

  • 合理使用分包 使用分包加载是优化小程序启动耗时效果最明显的手段。建议开发者按照功能划分,将小程序的功能按使用频率和场景拆分成分包,实现代码包的按需加载。同时需要注意控制分包数量,避免过多拆包
  • 移除无用文件 目前小程序打包是会将工程下所有文件都打入代码包内,在开发迭代过程中,如果不及时清理无用的资源,会使得包体积越来越大,开发过程中要养成良好的习惯 -- 及时清理没有使用到的资源,防止资源冗余。
  • 控制包内静态资源 避免在代码包中包含或在ttss中内联过多、过大的代码包内的图片,应尽量采用网络图片。代码包内的图片一般应只包含一些体积较小的图标。声音、视频等其他类型的资源应尽量避免放到代码包中。 小程序代码包在下载时会使用Gzip算法进行压缩,降低下载时传输的数据量。这些资源文件会占用大量代码包体积,并且通常难以进一步被压缩,对于下载耗时的影响比代码文件要大很多。

减少同步逻辑

  • 优先使用异步API 在小程序启动流程中,会注入开发者代码并顺序同步执行App.onLaunch, App.onShow,Page.onLoad,Page.onShow 。在小程序初始化代码(Page,App 定义之外的内容)和启动相关的几个生命周期中,应避免过度使用Sync结尾的同步API
  • 避免启动时运行过多同步代码 在小程序初始化代码(Page,App 定义之外的内容)和启动相关的几个生命周期中,应避免执行复杂的计算逻辑。

更早的展示首屏数据

  • 尽早调用关键API和请求 首屏绘制可能会依赖API数据和网络请求,尽早调用相关API,发送相关网络请求,能提前数据准备时间。
  • 接入数据预取 大部分小程序在渲染首页时,需要依赖服务端的接口数据,小程序为开发者提供了数据预取,方便开发者在小程序冷启动时提前发起请求,并缓存请求内容。
  • 避免非必要的reLaunch reLaunch会先关闭所有页面,非必要的reLaunch会导致首页白屏时间明显增长。

合理缓存数据

  • 网络数据缓存 小程序提供了tt.getStoragett.setStorage 等读写本地缓存的能力,数据存储在本地,返回的会比网络请求快。如果开发者基于某些原因无法采用数据预拉取,我们推荐优先从缓存中获取数据来渲染视图,等待网络请求返回后进行更新。
  • API数据缓存 对调用频次高的方法的结果进行缓存,例如对于tt.getSystemInfo,tt.getSystemInfoSync的结果应进行缓存,避免重复调用。

图片优化

  • 选择合适的图片格式 对于不需要透明格式的图片,推荐采用jpeg格式来代替png格式。如果有条件,尽可能使用webp格式图片,能大幅缩小图片体积。
  • 进行合理的压缩 图片尽可能压缩到200kb以下,压缩的同时也需要兼顾图片的质量。
  • 使用CDN并开启缓存 使用CDN,能大幅减少图片资源的下载速度。开启HTTP缓存控制后,下一次加载同样的图片,会直接从缓存读取,大大提升图片加载速度。

更多优化手段

  • 框架骨架屏 通过小程序框架提供骨架屏机制,能比业务中创建的骨架屏加载时机更靠前,使用这一机制,可以减少用户的白屏等待时长,给用户带来更好的体验。
  • 占位组件 为自定义组件配置占位组件,可以指定该组件不在小程序启动时立即注入,而是等到页面中其他元素渲染完成后才注入。通过占位组件,能减少启动耗时。

合理使用setData

  • 减少发送频率 动画不使用setData

合理使用自定义组件

  • 合理的拆分组件数量 Page中的setData会触发渲染层以页面级别进行diff操作,如果页面比较复杂且没有使用自定义组件,那么diff 的成本会很高,导致体验比较差(更新卡顿、不粘手等感受)。如果页面转换为若干个组件,如果在组件中setData, 只会触发渲染层对应组件的diff操作,diff成本会降低很多,使用体验也会提升很多。
  • 只注册当前使用的组件 同时在usingComponents建议只注册当前页面有使用到的自定义组件,在小程序框架会根据usingComponents 中的自定义组件注册(无论开发者在运行时是否有使用)。
  • 同步修改初始dataapp.json中增加配置component2: true后,支持在created生命周期中修改自定义组件初始数据,自定义组件将在created 生命周期执行完成后开始渲染。开启后有以下优化点:
  1. 能够很好的解决依赖计算逻辑导致的data频繁变更。避免初始数据依赖大量计算逻辑时,由于data变化导致页面渲染内容闪动或频繁变动问题
  2. 优化observer触发方式,减少因数据变更导致的通信,提升小程序性能体验 合理的使用方式能进一步提升用户体验

合理监听处理事件

  • 合理监听处理scroll事件 避免在scroll事件中高频执行耗时操作,会明显降低FPS
  • 去掉不必要的事件绑定 去掉不必要的事件绑定(ttml 中的 bind 和 catch),从而减少通信的数据量和次数

内存优化

  • 及时解绑事件监听 事件监听结束后,应及时解绑监听器
  • 及时清理定时器 开发者在开发如「秒杀倒计时」等功能时,可能会使用setInterval设置定时器,页面或组件销毁前,需要调用clearInterval方法取消定时器。

导航栏适配

  • 适当开启自定义导航栏 避免在app.json中全局开启动态导航栏,仅在需要的页面中配置,降低适配成本。
  • 关键信息避开状态栏和胶囊按钮 如果开启自定义导航栏,需要通过tt.getCustomButtonBoundingClientRect获取自定义导航栏下不可改变的元素来进行导航栏的适配

X分屏适配

  • 通过onResize监听显示区域变化 小程序支持组件和页面的生命周期函数onResize 用于在显示区域的尺寸发生变化的时候返回当前页面的信息。其中组件需要作为页面配置到app.json中触发事件。
  • 不使用JS设置ScrollView高度 scroll-view中分屏变为全屏时需要重新设置scroll-view高度,可能会出现空白区域问题,建议使用CSS(vh)完成自适应布局。

016.png

小程序性能优化-性能评分工具

017.png

小程序性能优化-性能优化工具

018.png