当前位置: 首页 > news >正文

疏通经脉: Bridge 联通逻辑层和渲染层

本节概述

经过前面两节的开发,我们已经完成了小程序逻辑线程和 UI 线程的启动引擎准备,这节开始,我们将完善 native bridge 层的搭建,构建起逻辑线程和UI线程之间的桥梁。

开始之前我们先来回顾一下逻辑引擎小节相关的流程图:

在这里插入图片描述

一次小程序的启动过程,我们在创建好小程序的 逻辑引擎worker 和 绘制引擎webview 之后,从启动到渲染依次会经过:

  1. 通知 webview 加载小程序资源,如果是首次启动,还需要通知逻辑线程加载资源(非首次启动则不用,一个小程序的逻辑 worker 层是公用的)
  2. 资源加载完毕后,开始通知逻辑线程创建应用实例
  3. 实例初始化完毕,请求 worker 线程获取小程序初始化渲染数据
  4. bridge 将worker层获取到的初始化数据发送给ui线程,ui线程启动渲染
  5. ui渲染完毕通过bridge 通知给逻辑worker,触发小程序的生命周期函数

在前面的双线程结构的小节中,我们已经完成了前置的: 创建worker创建webview 的准备。现在我们继续在其基础上连接起逻辑线程引擎ui线程引擎,打通经脉,启动小程序渲染

环境准备

在开始之前,我们先在之前小节的基础上调整下代码环境: 当时我们创建 webview 的时候,是模拟的小程序的配置信息。现在我们来模拟一个小程序的配置文件,然后通过网络请求读取配置信息后再注入

先创建一个小程序的编译后的配置文件,放在public目录下方便直接通过服务加载:

// config.json,这个配置文件的内容也是和我们上两节模拟的小程序逻辑代码和页面代码一致对应的
{"app": {"entryPagePath": "pages/home/index","pages": ["pages/home/index"],"window": {"navigationBarBackgroundColor": "#ffffff","navigationBarTextStyle": "black","navigationBarTitleText": "微信接口功能演示","backgroundColor": "#eeeeee"},"tabBar": [],"networkTimeout": {},"debug": true},"modules": {"pages/home/index": {"navigationBarBackgroundColor": "#ffd200","navigationBarTextStyle": "black","navigationBarTitleText": "美团","backgroundColor": "#fff","usingComponents": {}}}
}

src/native/miniApp.ts 文件夹下的 init 方法中,我们进行下调整:

async init() {// 模拟读取小程序配置文件信息
+ const configPath = `/${this.app.appId}/config.json`;
+ const res = await fetch(configPath).then(res => res.text());
+ this.appConfig = JSON.parse(res);// 获取小程序入口文件配置: 传入的path 或者 配置文件中的 entryPagePath
+ const entryPagePath = this.app.path || this.appConfig!.app.entryPagePath;// 入口页面对应的页面配置信息
+ const pageConfig = this.appConfig!.modules?.[entryPagePath];const entryPageBridge = await this.createBridge({jscore: this.jscore,isRoot: true,appId: this.app.appId,pagePath: this.app.path,pages: this.appConfig!.app?.pages,query: this.app.query,scene: this.app.scene,configInfo: mergePageConfig(this.appConfig!.app, pageConfig), // 合并配置信息,主要是页面配置和全局window配置信息的合并});
}
export function mergePageConfig(appConfig: Record<string, any>, pageConfig: Record<string, any>) {const result: Record<string, any> = {};const appWindowConfig = appConfig.window || {}; // 全局window配置信息const pagePrivateConfig = pageConfig || {};     // 页面对应的配置信息result.navigationBarTitleText = pagePrivateConfig.navigationBarTitleText || appWindowConfig.navigationBarTitleText || '';result.navigationBarBackgroundColor = pagePrivateConfig.navigationBarBackgroundColor || appWindowConfig.navigationBarBackgroundColor || '#000';result.navigationBarTextStyle = pagePrivateConfig.navigationBarTextStyle || appWindowConfig.navigationBarTextStyle || 'white';result.backgroundColor = pagePrivateConfig.backgroundColor || appWindowConfig.backgroundColor || '#fff';result.navigationStyle = pagePrivateConfig.navigationStyle || appWindowConfig.navigationStyle || 'default';return result;
}

完善webview消息通信

在前面实现webview管理模块的时候,我们预留了消息通信相关的实现,经过上一小节 UI 引擎的实现我们可以知道,bridge 侧和ui线程的通信我们直接通过挂载ui全局window上的 JSBridge 对象来完成。bridge 侧需要添加 onReceiveUIMessage API给ui线程侧调用,来发送消息到bridge 侧

src/native/webview/index.ts 文件中我们来完善通信的逻辑;

async init(callback: () => void) {// 等待frame 加载完成await this.frameLoaded();const iframeWindow = window.frames[this.iframe.name];// 给webview内部的JSBridge对象添加 onReceiveUIMessage 方法iframeWindow.JSBridge.onReceiveUIMessage = (message: IMessage) => {this.event.emit('message', message);}callback && callback();
}postMessage(message: IMessage) {const iframeWindow = (window.frames as any)[this.iframe.name];if (iframeWindow) {// 触发webview内部 JSBridge对象上的 onReceiveNativeMessage 方法完成通信iframeWindow.JSBridge.onReceiveNativeMessage(message);}
}

启动页面渲染

从上面分析的流程中我们可以发现,启动过程的触发点只需要通知两个线程加载资源即可,后续的过程将有两个线程的消息来持续推进。

现在我们来实现一个启动渲染的方法,开始让两个线程工作:

// src/native/bridge/index.ts
/*** bridge 通知逻辑线程和UI线程加载小程序资源*/
start(loadLogicSource = true) {// 通知UI线程加载资源this.webview?.postMessage({type: 'loadResource',body: {appId: this.opts.appId,pagePath: this.opts.pagePath,}});// 初始化触发一次小程序逻辑资源加载if (loadLogicSource) {this.jscore.postMessage({type: 'loadResource',body: {appId: this.opts.appId,bridgeId: this.id,pages: this.opts.pages,}});} else {this.status++;}
}

这里有个参数是是否需要逻辑线程加载资源,经过前面小节的介绍其实我们可以快速的知道,因为一个小程序的逻辑线程worker是公用的,在初次启动后,后面就可以不用再继续加载了。

同时逻辑中还有一个 status 字段,这个状态字段是用于判断小程序进行到哪一步了,是否可以进行某一个等;

比如小程序要启动创建App实例,就需要两侧线程的资源都加载准备完毕,此时 status 的状态就需要变到 2 才能继续往下进行(ui线程资源加载完毕+1 和 逻辑线程资源加载完毕+1)

现在启动的契机开始之后,后续就是完成bridge监听两侧线程的消息,来推进逻辑的渲染:

逻辑线程消息监听

逻辑线程的启动事件通知包括:

  • logicResourceLoaded 逻辑线程资源加载完毕,如果此时 status 为 2,及ui侧也完毕时,启动App实例创建
  • appIsCreated 逻辑线程App创建完毕,后面要开始通知逻辑线程初始化渲染数据
  • initialDataReady 初始化渲染数据创建完毕返回,bridge 要通知 ui 线程挂载页面了
  • updateModule 逻辑线程侧调用了 setData api更新了数据,需要把新的数据发送个ui线程重新渲染
jscoreMessageHandler(message: IMessage) {console.log('接收到来自于逻辑线程的消息: ', message);const { type, body } = message;// 判断 bridgeId 是否对应if (body.bridgeId !== this.id) return;switch (type) {case 'logicResourceLoaded':this.status++;this.createApp(); // 逻辑线程和UI准备好之后就可以开始创建App了break;case 'appIsCreated':this.status++;this.notifyMakeInitialData(); // 通知逻辑线程初始化小程序渲染数据break;case 'initialDataReady':this.status++;this.setInitialData(body); // 把逻辑线程的初始化数据设置给UI线程,UI线程开始渲染页面break;case 'updateModule':this.updateModule(body); // 逻辑线程调用setData 更新数据,通知UI渲染}
}// 通知逻辑线程创建小程序App实例
createApp() {// 只有logic和ui线程的loadResource 都完毕后,才能开始创建,此时status会变成2if (this.status !== 2) return;this.jscore.postMessage({type: 'createApp',body: {bridgeId: this.id,scene: this.opts.scene,pagePath: this.opts.pagePath,query: this.opts.query,}});
}
// 通知逻辑线程初始化渲染数据
notifyMakeInitialData() {this.jscore.postMessage({type: 'makePageInitialData',body: {bridgeId: this.id,pagePath: this.opts.pagePath,}});
}
// 将逻辑线程初始化好的渲染数据发送给ui线程渲染页面
setInitialData(data) {const { initialData } = data;this.webview?.postMessage({type: 'setInitialData',body: {initialData,bridgeId: this.id,pagePath: this.opts.pagePath,}});
}
// 逻辑线程数据更新,通知ui线程重新渲染
updateModule(payload) {const { id, data } = payload;this.webview?.postMessage({type: 'updateModule',body: {id,data,}})
}
UI 线程启动消息处理

ui 线程启动过程主要包括的事件节点有:

  • uiResourceLoaded ui线程资源加载完毕,如果 status 为2,及逻辑线程也加载完毕,可以启动创建 App 实例
  • moduleCreated ui线程模块创建完毕(在绘制过程了),此时需要通知逻辑线程创建页面实例 PageModule
  • moduleMounted ui线程页面已经挂载好了,此时通知逻辑线程触发 ready 事件
  • triggerEvent ui线程事件交互,通知逻辑线程触发相应的处理函数
uiMessageHandler(message: IMessage) {console.log('接收到来自UI线程的消息: ', message);const { type, body } = message;switch (type) {case 'uiResourceLoaded':this.status++;this.createApp();break;case 'moduleCreated':this.uiInstanceCreated(body);break;case 'moduleMounted':this.uiInstanceMounted(body);break;case 'triggerEvent':this.triggerEvent(body);break;}
}
// ui线程模块创建好,通知逻辑线程可以创建页面实例了
// 这里后面真实触发的时机回调整为 vue created 状态时执行
uiInstanceCreated(payload) {const { path, id } = payload;this.jscore.postMessage({type: 'createInstance',body: {id,path,bridgeId: this.id,query: this.opts.query,}});
}
// ui挂载完毕,通知逻辑线程触发 ready
uiInstanceMounted(payload) {const { id } = payload;this.jscore.postMessage({type: 'moduleMounted',body: { id }});
}
// 用户事件,通知逻辑线程触发处理函数
triggerEvent(payload) {const { id, methodName, paramsList } = payload;this.jscore.postMessage({type: 'triggerEvent',body: {id,methodName,paramsList}})
}

经过上面的步骤之后,我们的启动过程就连接好了,此时运行项目点击美团小程序可以看到如下效果:

录屏2025-06-29 18.05.39

本小节的代码已发布至github仓库,可前往查看完整代码: mini-wx-app

http://www.lqws.cn/news/572491.html

相关文章:

  • 模拟多维物理过程与基于云的数值分析-AI云计算数值分析和代码验证
  • 生物实验室安全、化学品安全
  • 【notes2】并发,IO,内存
  • 30套精品论文答辩开题报告PPT模版
  • Gemini cli Quickstart
  • 数据结构复习4
  • 常用指令合集(DOS/Linux/git/Maven等)
  • debug的计算表达式
  • 《平行宇宙思维如何让前端错误处理无懈可击》
  • 2025年渗透测试面试题总结-2025年HW(护网面试) 20(题目+回答)
  • 各种常用的串口助手工具分享
  • 第10篇 图像语义分割和目标检测介绍
  • 循环神经网络的概念和案例
  • 带读YOLOv13,HyperACE | FullPAD到底是什么
  • 个人计算机系统安全、网络安全、数字加密与认证
  • 数据库中的 DDL(Data Definition Language,数据定义语言) 用于定义或修改数据库结构(如库、表、索引、约束等)。
  • 机器学习-02(深度学习的基本概念)
  • 智能新纪元:大语言模型如何重塑电商“人货场”经典范式
  • 【QT】信号和槽(1) 使用 || 定义
  • 深入学习 GORM:记录插入与数据检索
  • MySQL技巧
  • 【ad-hoc】# P12414 「YLLOI-R1-T3」一路向北|普及+
  • Requests源码分析:面试考察角度梳理
  • MySQL 架构
  • 理解 Confluent Schema Registry:Kafka 生态中的结构化数据守护者
  • 第10.4篇 使用预训练的目标检测网络
  • 学习使用Visual Studio分析.net内存转储文件的基本用法
  • C# 委托(调用带引用参数的委托)
  • 计算机组成原理与体系结构-实验四 微程序控制器 (Proteus 8.15)
  • 【硬核数学】3. AI如何应对不确定性?概率论为模型注入“灵魂”《从零构建机器学习、深度学习到LLM的数学认知》