移动前端混合开发技术演进之路
本文是azuo和萌妹俩技术创作之旅的第15篇原创文章,内容创作@azuo😄,精神支持@大头萌妹😂
前言:本文主要探讨了移动混合开发( Hybrid APP) 开发的技术演进历程,将阐述了webview(H5)、React Native、小程序技术等在其中所扮演的关键角色及带来的变革。原生能力缺失、长时间白屏、用户操作响应不及时等web开发的问题是如何被解决的?
一、诞生背景
早期移动应用开发,由于机器硬件性能的方面影响,为了更好的用户体验(操作响应、流畅度和原生的能力),主要集中在原生应用开发上。
1.1 原生开发的缺点
原生应用开发周期和更新周期长,也逐渐在快速的迭代的互联网产品产生矛盾。
缺点:
- 开发周期长:开发调试需要编译打包,动辄就需要几分钟甚至十几分钟,相比H5的亚秒级别的热更能力,是在太长了;
- 更新周期长:正常的发版需要用户手动更新,无法做到H5这种发布即更新的效率。
- 使用前需要安装;
- 需要多端开发;(Android和iOS两端开发人力成本高)
1.2 web开发的缺点
原生应用的研发效率问题,也逐渐在快速的迭代的互联网产品产生矛盾。这时候,开发人就自然而然的想到web技术能力,快速开发和发版生效和跨平台能力。
web技术开发的H5界面,相比原生应用,缺点也很明显:
- 缺少系统的提供原生能力;
- 页面白屏时间长(原生基本可以做到1秒内,h5普遍在2秒以上);
- 用户操作响应不及时(动画卡、点击没有反应);
把Native开发和web开发的优缺点整合一下,就诞生了Hybrid App。Hybrid App技术从诞生到现在一直在解决这3个问题。
二、 提供原生能力
JSBridge技术是由 Hybrid 鼻祖框架phoneGap带到开发者的视野中,解决了第一个问题。它通过webview桥接(JSBridge)的方式层解决web开发能力不足的问题,让web页面可以用系统提供原生能力。
2.1 技术原理
Android原生开发提供了各种view控件(类比Dom元素:div、canvas、iframe),其中就用一个webview(类比iframe)。JSBridge 就像其名称中的『Bridge』的意义一样,是 Native 和非 Native 之间的桥梁,它的核心是 构建 Native 和非 Native 间消息通信的通道,而且是 双向通信的通道。
双向通信的通道:
- JS 向 Native 发送消息 : 调用相关功能、通知 Native 当前 JS 的相关状态等。
- Native 向 JS 发送消息 : 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。
2.2 实现细节
Android可以通过webview将一些原生的Java方法注入到window上供Javascript调用。Javascript也可以直接在window上挂着全局对象给webview执行。
2.2.1 JavaScript 调用 Native
Android 可以采用下面的方式:
public class JSBridgeActivity extends Activity{
private WebView Wv;
@Override
publicvoidonCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Wv = (WebView)findViewById(R.id.webView);
Wv.getSettings().setJavaScriptEnabled(true);
// 4.2 使用 @JavascriptInterface
Wv.addJavascriptInterface(new JavaScriptInterface(this), "nativeBridge");
// TODO 显示 WebView
}
}
public class JavaScriptInterface{
@JavascriptInterface
public void postMessage(String webMessage){
// Native 逻辑
}
}
前端调用方式:
// android会在window上注入nativeBridge对象
window.nativeBridge.postMessage(message);
native层除了上述方式被Javascript调用,还有可以拦截alert、confirm、console的日志输出、请求URL(伪协议)等方式,来的获取到Javascript调用native的意图。
2.2.2 Native 调用 JavaScript
相比于 JavaScript 调用 Native, Native 调用 JavaScript 较为简单, WebView 组件,都以子组件的形式存在于 View/Activity 中,直接调用相应的 API 即可(类比浏览器的window中的原生方法)。
// android 4.4之前
webView.loadUrl("javascript:"+javascriptString)
// android 4.4之后
webView.evaluateJavascript(
javaScriptString, // js表达式
new ValueCallback<String>() { // 表达式的值通过回调给native
@Override
public void onReceiveValue(String value){
// 鉴权拦截,一般估计页面域名白名单的方式
JSONObject json = new JSONObject(value)
switch(json.bridgeName){
// 处理
}
}
}
);
2.3 JSBridge 接口
JSBridge 技术是对JavaScript 和 Native之间的封装成JS SDK方便前端JS调用,主要功能有两个:调用 Native和 接收Native 被调。
(function () {
var id = 0,
callbacks = {};
window.JSBridge = {
// 调用 Native
invoke: function(bridgeName, callback, data) {
// 判断环境,获取不同的 nativeBridge
var thisId = id ++; // 获取唯一 id
callbacks[thisId] = callback; // 存储 Callback
nativeBridge.postMessage(JSON.stringify{
bridgeName: bridgeName,
data: data || {},
callbackId: thisId // 传到 Native 端
});
},
receiveMessage: function(msg) {
var bridgeName = msg.bridgeName,
data = msg.data || {},
callbackId = msg.callbackId; // Native 将 callbackId 原封不动传回
// 具体逻辑
// bridgeName 和 callbackId 不会同时存在
if (callbackId) {
if (callbacks[callbackId]) { // 找到相应句柄
callbacks[callbackId](msg.data); // 执行调用
}
} elseif (bridgeName) {
}
}
};
})();
JSBridge通过建立一个通信桥梁,使得JavaScript和原生代码可以相互调用,实现高效的数据传输和交互。这个过程是跨线程异步调用的,数据传输一般会经过两次序列化(还有提升的空间)
三、解决白屏
3.1 白屏产生的原因
原生APP安装后启动页面,在正常情况是不用再从网络获取资源,只需要请求后端接口获取数据就可以完成渲染了,网页不需要安装才,每次打开web页面都会从远程服务加载资源后,再请求后端数据后才能渲染。在用户等待资源加载过程和浏览器渲染未完成中,就会出现白屏。造成白屏的主要原因 -- 资源网络加载;
3.2 离线包技术
离线包主要是识别特定url地址(通常是url参数=离线批次id,即:_bid=1221)后保存到用户手机硬盘。用户下次打开H5页面就可以不用走网络请求。离线包一包也会提供预下载能力,保证首次打开H5页面也可以获得收益。
离线包是完整的资源分发系统,需要一个完整的技术团队来建设和维护的。
3.2.1 离线包分发过程
分发流程中主要涉及4种角色:
- 离线配置平台:配置平台可以提供离线配置能力、离线包管理(上传、禁用、清空)、离线包使用统计、离线包准入审核(自动(包大小限制)+人工(解决特殊case))
- 离线配置服务: 配置服务主要提供服务层能力,实现离线配置服务,离线包更新服务,离线资源长传下载服务、离线资源使用统计服务
- 离线SDK: 端内接入离线SDK,SDK主要与离线配置服务进行交互,完成离线资源的管理和接入配置能力
- Native侧 : 实现拦截请求在特定的协议下接入离线资源
3.2.2 离线包加载过程
离线包的加载流程
3.2.3 拦截实现细节
实现WebViewClient: 继承WebViewClient类,并重写shouldInterceptRequest方法。这个方法会在WebView尝试加载一个URL时被调用,你可以在这里检查请求的URL,并决定是否拦截这个请求。
public class MyWebViewClient extends WebViewClient {
private InputStream getOfflineResource(String url) {
// ... 你的实现代码 ...
return null; // 示例返回null,实际中应该返回InputStream
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
// 检查这个URL是否在你的离线包中
InputStream inputStream = getOfflineResource(url);
if (inputStream != null) {
// 如果在离线包中找到了资源,就返回一个WebResourceResponse对象
return new WebResourceResponse(
"text/html", // MIME类型,这里以HTML为例
"UTF-8", // 编码
inputStream
);
}
// 如果没有在离线包中找到资源,就返回null,让WebView按照默认的方式去加载这个URL
// 走网络请求获取
}
}
// 在你的Activity或Fragment中
WebView webView = findViewById(R.id.webview);
webView.setWebViewClient(new MyWebViewClient());
3.3 服务端渲染(SSR )
在3.1 白屏产生的原因,影响白屏的因素是JS和CSS资源和数据请求。如果,html请求得到的内容中直接包含首屏内容所需要内联的CSS和Dom结构。
SSR通过在服务端(BFF)直接完成有内容的HTML组装。webview获取到html内容就可以直接渲染。减少白屏时间和不可交互时间。
3.3.1 增量更新和并行请求
SSR将本来一个简单框架HTML,增加了首屏内容所需要的完整CSS和Dom内容。这样的话,HTML请求的包体积就增大了多。其中:
- 跟版本相关的样式文件CSS (变更频率低)
- 跟用户信息相关的Dom内容(变更频率高)
HTML根据内容变更频率进行页面分割如下:
<!DOCTYPE html>
<html lang="en">
<head>
<title>OPPO用户体验评价</title>
<meta charset="UTF-8">
<script content="head">window._time = Date.now()</script>
<meta name="renderer" content="webkit|chrome">
<meta name="format-detection" content="telephone=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="x5-orientation" content="portrait">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-COMPATIBLE" content="IE=Edge,chrome=1">
<meta name="nightmode" content="disable">
<meta name="color-scheme" content="light">
<!-- css内联内容开始 -->
<style>
/*http://www.xxx.com/wj-prod/style.css*/
/**
* 替换url的css内容,内容比较多
*/
</style>
<!-- css内联内容结束 -->
</head>
<body>
<!-- dom内容开始 -->
<div id="app">
<!-- 拼接好的html结果 -->
<div>
<span></span>
</div>
</div>
<!-- dom内容结束 -->
<!-- 数据内容开始 -->
<script content="page-data">
// 直出的数据,方便vue、react等框架回填状态,声明式UI才必须
window.syncData = {/**服务端获取的数据**/}
</script>
<!-- 数据内容结束 -->
<script crossorigin="anonymous" src="//cdn.xxx.com/wj-prod/client.bundle.js?_t=1"></script>
</body>
</html>
客户端和BFF层大概工作流程如下:
手机QQ将这套方案开源了:github.com/Tencent/Vas… (我曾经也是这套方案的参与者和使用者)
3.4 总结
为了更快的渲染出页面,发展了离线包技术、服务器端渲染(SSR)、Webview启动并行等一系列的技术方案,这些技术可以单个使用,也可以组合使用。
- 对于首次加载的页面,使用服务器端渲染(SSR)和Webview启动并行,是可以很好的解决白屏问题,适用H5活动页面。
- 对于二次加载的页面,使用离线包技术、服务器端渲染(SSR)和Webview启动并行,可以在不经过网络请求也可以展示页面,适用固定入口客户端页面;
四、解决卡顿
使用过程发现H5网页相比于原生页面,更容卡顿,甚至造成页面卡死的问题。这个章节就主要解决为啥浏览器渲染的H5会比原生卡?Hybrid开发用哪些技术如何解决这个问题?
4.1 浏览器渲染的慢
浏览器技术的发展历程已有超过30年的历史,Chrome内核有超过2400万行代码,有很重的历史包袱。
4.1.2 渲染流程
浏览器渲染页面使用了多线程的架构,发生卡顿的主要原因在:渲染线程和JS引擎线程,他两是互斥的,Javascript长时间执行会导致渲染线程无法工作。
GUI渲染线程(GUI Thread):
- 负责渲染浏览器界面。
- 解析HTML、CSS,构建DOM树和CSS规则树,并合成渲染树。
- 布局(Layout)和渲染(Paint)页面内容。
- 与JS引擎线程互斥,当JS引擎线程执行时GUI渲染线程被挂起,GUI更新会被保存在一个队列中,等JS引擎空闲时立即执行。
JS引擎线程(JS Engine Thread):
- 也称为JS内核(在Chrome中为V8)。
- 负责解析和执行JavaScript代码。
- 单线程设计,JS运行过长会阻塞GUI渲染。
事件触发线程(Event Dispatch Thread):
- 用于控制事件循环。
- 当事件(如点击、鼠标移动等)被触发时,该线程会将事件放到对应的事件队列中,等待JS引擎线程处理。
合成器线程(Compositor Thread)和光栅线程(Raster Thread):
- 这两个线程在渲染器进程中运行,以高效流畅地渲染页面。
- 合成器线程负责将不同的图层组合成最终用户看到的页面。
- 光栅线程则负责将图层内容转换为位图,以便在屏幕上显示。
以用户点击操作为例:
如果界面的刷新帧率是60帧,在不掉帧的情况。执行时间只有 1000 ms / 60 = 16.66 ms。上图中间的JS引擎线程和渲染线程的执行是串行,而且不能超过16.66 ms。(留给JS引擎和渲染线程执行的时间本身不多,60帧只有有16ms,120帧只有8ms)这就是浏览器为啥比原生渲染卡。
4.2 声明式UI
浏览器渲染慢的主要原因是JS引擎线程和渲染进程的执行互斥, 那么,最简单解决方式就是将渲染线程改造按照帧率来调度,不再等JS引擎线程全部执行完再去渲染。但是,由于浏览器最初涉及的JS引擎线程是为了应对命令式UI渲染方案,命令式UI对界面的修改是不可预测。
4.2.1 命令式UI
命令式UI关注于如何达到某个特定的用户界面状态,通过编写具体的操作指令来直接操纵界面元素。关注于操作步骤和过程,需要编写具体的代码来实现每个步骤。
// dom找到需要变更的节点
const list = document.querySelector('#content')
// 修改样式
list.style.display = 'none'
// 增加内容
list.innerHTML += `<div class="item">列表内容</div>`
优点: 是入门简单,讲究一个精确控制和直接操作。
缺点: 直接操作界面,带来对UI界面渲染的不可以预测性;
4.2.1 声明式UI
声明式UI(Declarative UI)是一种用户界面编程范式,它关注于描述UI的期望状态,而不是直接编写用于改变UI的命令。在声明式UI中,开发者通过声明性的方式定义UI的结构、样式和行为,而具体的渲染和更新工作则由框架或库自动完成。
声明式UI编程范式:
function List(people) {
const listItems = people.map(person =>
<li key={person.id}>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}</b>
{' ' + person.profession + ' '}
known for {person.accomplishment}
</p>
</li>
);
return <ul>{listItems}</ul>;
}
优点: 入门难度有所增加,代码更加简洁,带来更高和可维护性,可以直接根据数据预测UI更新 。
缺点: 入门难度有所增加,灵活性没有命令式UI高;
4.2.1 虚拟DOM
声明式UI强调数据驱动UI更新,一般声明式UI框架中,都还会引入虚拟DOM技术。虚拟DOM(Virtual DOM)是一种在前端开发中广泛使用的技术,它通过JavaScript对象来模拟真实的DOM结构,从而优化Web应用程序的性能和渲染效率。
- 核心思想:将页面的状态抽象为JavaScript对象表示,避免直接操作真实的DOM,从而提高性能和渲染效率。
- 工作流程:
- 初始渲染:首先,通过JavaScript对象(虚拟DOM)表示整个页面的结构。这个虚拟DOM是一个轻量级的映射,保存着真实DOM的层次结构和信息。
- 更新状态:当应用程序的状态发生变化时,如用户交互或数据更新,虚拟DOM会被修改。这个过程操作的是内存中的JavaScript对象,而不是直接操作真实的DOM。
- 生成新的虚拟DOM:状态变化后,会生成一个新的虚拟DOM,反映更新后的状态。
- 对比和更新:通过算法(如Diff算法)将新的虚拟DOM与旧的虚拟DOM进行对比,找出它们之间的差异。
- 生成变更操作:根据对比结果,找出需要更新的部分,并生成相应的DOM操作(如添加、删除、修改节点等)。
- 应用变更:将生成的DOM操作应用到真实的DOM上,只更新需要变更的部分,而不是整个页面重新渲染。
以virtual-dom为例,虚拟Dom的渲染流程大致如下:
import h from 'virtual-dom/h'
import diff from 'virtual-dom/diff'
import patch from 'virtual-dom/patch'
// 第一步:定义渲染函数,UI = F( state)中的f,
// 开发人员编写渲染模版(react对于是jsx,vue对应的template),由构建工具生成;
function render(count) {
return h('text', { attributes: { count } }, [String(count)])
}
// 第二步:初始化vtree
let tree = render(count) // We need an initial tree
// UI变更
setTimeout(function () {
// 第三步:更新state,重新生成vtree
count++
const newTree = render(count)
// 第四步:对比新旧vtree的差异
const patches = diff(tree, newTree)
console.info('patches', patches)
// 第五步:增量更新dom
// patch(rootNode, patches)
tree = newTree
}, 1000)
相比于命令式UI的开发,声明式UI和虚拟DOM技术结合后,UI渲染过程表示用简单的数据结构就可以表述(第四步骤得到结果序列化),能序列化的好处就是可以很简单完成跨线程处理。
4.3 React Native
声明式UI和虚拟DOM是由React带到开发的视野中。虚拟DOM除了提供声明式UI的高性能渲染能力,它还有一个强大的能力--抽象能力。
![5f1b31388f0574dd8e8397cabee53bf9.jpg](https://www.imgeek.net/uploads/article/20240628/5f1b31388f0574dd8e8397cabee53bf9.jpg)
4.3.1 组件抽象
在开发者的代码与实际的渲染之间加入一个抽象层,这就可以带来很多可能性。对于React Native 渲染实现:
- 在IOS平台中则调用Objective-C 的API 去渲染iOS 组件;
- 在Android平台则调用Java API 去渲染Android 组件,而不是渲染到浏览器DOM 上。
React Native的渲染是使用不同的平台UI Manager 来渲染UI。因此,React Native对UI开发的基础组件进行整合和对应
React Native | Android View | IOS View | Web Dom |
---|---|---|---|
<view> | <ViewGr0up> | <UIView | <div> |
<Text> | <TextView> | <UITextView> | <p> |
<Image> | <ImageView> | <UIImageView> | <img> |
4.3.2 样式渲染
组件结构通过抽象的基础可以完成每个平台的转换。UI界面开发出来结构还需要样式编写。React Native引用了Yoga。Yoga是 C语言写的一个 CSS3/Flexbox 的跨平台 实现的Flexbox布局引擎,意在打造一个跨iOS、Android、Windows平台在内的布局引擎,兼容Flexbox布局方式,让界面布局更加简单。
4.3.3 线程模型
在React Native中,渲染由一个JS线程和原生线程。JS线程负责解析和执行JavaScript代码,而原生线程则负责渲染界面和执行原生操作。JS执行的结果(dom diff)异步通知原生层。
4.3.3 总结
React Native借助虚拟DOM的抽象能力,把逻辑层的JS代码执行单独抽到JS引擎中执行,不再与UI渲染互斥,可以留更多时间给UI渲染线程。
UI渲染相比浏览器渲染性能提升主要在两点:
- JS层不再互斥UI渲染;
- UI渲染由浏览器渲染改成原生渲染;
UI放到Natie层渲染,逻辑放在JS层执行,Natice层与JS层通过JSBridge(24年底会默认替换成JSI,以提高数据通信性能,有兴趣可以去了解)进行通信。
Weex和快应用的实现原理跟React Native类似,主要的差异是在编写声明式UI的DSL,这里就不一一讲解
4.4 微信小程序
微信小程序是从公众号的H5演变而来的。2015年微信对外发布JS-SDK(JS Bridge)提供微信的原生能力(类似早期的phoneGap的),解决了移动网页能力不足的问题。但是,页面加载白屏、网页安全和卡顿问题依旧没被解决。
微信在2017年设计一个全新的系统来解决这些问题,它需要使得所有的开发者都能做到:
- 快速的加载
- 更强大的能力
- 原生的体验
- 易用且安全的微信数据开放
- 高效和简单的开发
4.4.1 双线程架构
有了虚拟DOM这个抽象层,UI界面开发的的逻辑层和视图层可以分离。小程序的渲染层和逻辑层分别由两个线程管理(视图层是 WebView,逻辑层是 JS 引擎
- 视图层主要负责页面的渲染,每一个页面Page View对应一个Webview(不能超过10个页面栈)。
- 逻辑层负责js的执行,一个JS执行的沙箱环境;
微信小程序的双线程有如下主要优点:
- javascript脚本执行不会抢占ui渲染资源,使整体页面渲染更快;
- 每个PageView是由一个webview单独渲染,页面切换效果上更接近原生,比公众号h5网页浏览体验要好;
- 安全管控,独立的沙箱环境运行javascript逻辑代码,避免了浏览器的开放api操作dom、跳转页面等,更加安全。
4.4.2 开发的DSL
小程序包含一个描述整体程序的 app 和多个描述各自页面的 page。
个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:
文件 | 必需 | 作用 |
---|---|---|
app.js | 是 | 小程序逻辑 |
app.json | 是 | 小程序公共配置 |
app.wxss | 否 | 小程序公共样式表 |
一个小程序页面由四个文件组成,分别是:
文件类型 | 必需 | 作用 |
---|---|---|
js | 是 | 页面逻辑 |
wxml | 是 | 页面结构 |
json | 否 | 页面配置 |
wxss | 否 | 页面样式表 |
WXML和WXSS是微信官方创造的DSL,需要进行编译后才能被Webview解析执行。可以从微信开发者工具包文件中找到 wcc 和 wcsc 两个编译工具
- wcc 编译器可以将 wxml 文件编译成 JS 文件
- wcsc 编译器可以将 wxss 文件编译成 JS 文件。
WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。(类比虚拟DOM中的Render函数)
<!--wxml-->
<view>
<text class="text">{{message}}</text>
</view>
将wcc拷贝到当前的index.wxml同级目录, 执行
./wcc -js index.wxml >> wxml.js
将wxml.js的内容复制到浏览器的console中执行后,输入:
$gwx('index.wxml')({
message: 'hello world'
})
可以获得vtree:
{
"tag": "wx-page",
"children": [
{
"tag": "wx-view",
"attr": {},
"children": [
{
"tag": "wx-text",
"attr": {
"class": "text"
},
"children": [
"hello world"
],
"raw": {},
"generics": {}
}
],
"raw": {},
"generics": {}
}
]
}
WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。(跟CSS类似,增加了rpx相对尺寸,可以参考REM的响应式布局)
page{
display:flex;
background-color: #fff;
}
.wrap{
width:320rpx;
height: 200rpx;
}
.text{
color:red;
font-size:12px
}
将wcsc拷贝到当前的index.wxss同级目录, 执行
./wcsc -js index.wxss >> wxss.js
最后将wxss.js的内容拷贝到浏览器去运行,即可得到:
(page的样式转化成了body,rpx转成px)
4.4.3 逻辑层和渲染层
逻辑层主要执行app.js和每个页面Page构造器。最终将Page中data修改后的结果通过setData同步给渲染进程。
逻辑层是一个沙箱的执行环境,该环境不存在DOM API、window、document等对象API和全局对象。换句话来说,小程序相比传统H5是更加安全。小程序中访问用户相关信息是不能像H5直接调用浏览器API,需要经过用户授权才或者由用户操作触发才可以被调用。
小程序的渲染层是在webview执行的,主要将运行wxml和wxss编译后的代码;
- wxss文件编译成js,之后后会往head中插入style样式
- wxml编译成声明式UI的render函数,接受逻辑层的data来更新vtree,dom diff ,增量更新dom
render函数中的data由逻辑层调用setData跨线程传给渲染层, 渲染层相比传统的浏览器渲染页面少了渲染前的data生成。相比React Native,渲染层仍然会执行JS(主要虚拟Dom更新)。
逻辑层和渲染层的在不同平台的实现方式:
运行环境 | 逻辑层 | 渲染层 |
---|---|---|
iOS | JavaScriptCore | WKWebView |
Android | V8 | XWeb(腾讯自研,基于Mobile Chrome内核) |
PC | Chrome内核 | Chrome内核 |
小程序开发工具 | NW.js | Chrome WebView |
4.4.4 Skyline渲染引擎
小程序早期的渲染层是使用webview,每个PageView对一个webview,内存开销是很多。
Skyline渲染引擎其实可以被看作一个被优化后的webview,并在其内置了更加优秀的动画系统、跨线程传说方案
微信增加了渲染引擎 Skyline,其使用更精简高效的渲染管线,并带来诸多增强特性,让 Skyline 拥有更接近原生渲染的性能体验。
Skyline 创建了一条渲染线程来负责 Layout, Composite 和 Paint 等渲染任务,并在 AppService 中划出一个独立的上下文,来运行之前 WebView 承担的 JS 逻辑、DOM 树创建等逻辑。这种新的架构相比原有的 WebView 架构,有以下特点:
- 界面更不容易被逻辑阻塞,进一步减少卡顿
- 无需为每个页面新建一个 JS 引擎实例(WebView),减少了内存、时间开销
- 框架可以在页面之间共享更多的资源,进一步减少运行时内存、时间开销
- 框架的代码之间无需再通过 JSBridge 进行数据交换,减少了大量通信时间开销
Skyline 的首屏时间比 WebView 快 66%
Skyline 的内存占用比 WebView 减少 50%
详细可以参考:developers.weixin.qq.com/miniprogram…
4.4.5 总结
微信小程序采用双线程的架构方案,即解决web困扰已久的安全问题,而且也在一定程度上优化了页面渲染性能。虚拟DOM的抽象能力,使得PageView可以是WebView、React-Native-Like、Flutter 等来渲染
微信小程序也有类似离线包的技术,将用户访问的小程序缓存在微信APP的安装目录中,来解决页面白屏问题。首次加载白屏问题通过native层loading页面来遮盖,因此,小程序首次使用也会有2到3秒的加载过程(小程序分包要求,加载包不能超过2M,加载时间可以做到可控😄)。。
4.5 总结
React Native、Weex、微信小程序、快应用等技术,提供了一整套开发完备的技术和工具来实现混合开发。包括不限于:
- 平台提供基础UI组件为基础;
- 声明式UI作为首选,虚拟DOM的抽象能力,UI渲染框架可以多层级多语言实现;
- 双线程和JSBridge(JSI),使得JS逻辑执行和UI渲染分离;
- 完整工具类,编译、打包、HMR;
- 分包,一个应用可以由多个模块包组成;
- 亚秒级别的热更新能力;
后面出现的Flutter、ArkUI框架也基本围绕这些技术理念进行整合(当然还有编译技术的优化JIT向AOT,带来更快的启动速度)。
(Flutter、ArkTS带来更快的启动速度的技术方案后面再补到文章内吧)
五、发展历程
混合开发的发展史是一段技术革新和演进的过程,它标志着移动应用开发从单一平台向跨平台、高效率的方向转变。
- JSBridge让JavaScript拥有原生能力,JSI等技术让JavaScript直面C++,带来更加高效的传输速度;
- 离线包技术,兼顾加载和留存,SRR仍是很有效优化首屏速度的手段;
- 分包技术是提高加载速度和开发效率;
- 声明式U开发范式,加上虚拟Dom抽象能力,解偶上层开发与底层渲染框架,新的渲染框架不断涌现;
- JSCore引擎的双线程架构,打破逻辑层和UI层间的互斥,即解决Web困扰已久的安全问题,也缓解浏览器渲染性能问题;
来源:juejin.cn/post/7382051737362284559