# arkui-webview-study **Repository Path**: sugarnine/arkui-webview-study ## Basic Information - **Project Name**: arkui-webview-study - **Description**: 学习arkui的webview本地部署 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2024-04-28 - **Last Updated**: 2024-06-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### Cocos 项目如何在鸿蒙 Webview 中打包部署 哈喽大家好,我是九弓子 我又来参加 Cocos 征稿活动啦。 随着鸿蒙 Next 星河版系统的推进,相信很多写前端写 JS/TS 的小伙伴都跃跃欲试在新的操作系统中写自己的 App 啦。 那么作为 Cocos 的常年老粉丝, 不请自来的分享一波鸿蒙 Web 组件+Cocos3D 项目混合开发的示例。 先来看一些我之前已经做好的鸿蒙 webview 的例子吧。 ![alt text](混合开发示例2.gif) ![alt text](混合开发示例.gif) WebView 是应用开发中非常重要的组件之一, 尤其是对于我们 Cocos 游戏开发者来说,如果能将 Cocos 打包的 Web 项目。 直接本地部署到客户端软件内,不需要远程资源请求。 就不再需要分包优化,网络优化这些工作, 也能让用户在最快速度启动我们的项目。 这让我们开发 Cocos 的 Web3D 项目时候,可以解放很多大体积的美术素材直接部署。 --- #### 1.新建鸿蒙项目 因为并不是所有小伙伴具备 Next 真机, 我准备的分享内容尽量靠近消费者版本。 必备清单: 1.HarmonyOS 4.0 及以上操作系统 2.DevEco Studio 3.1.1 Release 3.HarmonyOS SDK 3.1.0(API9) 4.Cocos Creator 3.8.1(可选) 5.任意一个 Cocos Creator 构建的 Web 项目都可以 以上清单相信手里只要有近 3 年购买的华为手机,都能下载和安装 我们开始新建项目吧。 ![alt text](image.png) ![alt text](image-1.png) **需要注意的是,在新建项目的时候【Model】模型选择** **Stage 模型** 到这一步继续点击 Finish,说明你已经具备鸿蒙 app 的开发环境了。 具体鸿蒙应用开发入门,参考官方文档: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/start-with-ets-stage-0000001477980905-V2 这里就不展开了。 #### 2.添加 Web 组件 搭建 ArkUI+Cocos 混合开发热更新工作流程 ![alt text](image-3.png) 在这里我们在 Index 页面中,添加了 Web 组件(webview)。 我们为其设置了宽度 100%,高度 50%的矩形区域。 在鸿蒙中的 Web 组件其实就是我们在安卓 IOS 等客户端软件开发中的 weview 能力。 即为在屏幕中空出一块区域去显示 Chromium 内核展示的网页, 所以我们在 Web 组件的 src 参数中输入 Cocos Creator 的预览地址, 就能在真机中看到 Cocos 项目了。 ![alt text](SVID_20240427_195624_1-1.gif) 一些需要注意的点: ![alt text](image-4.png) 1.webview 导包 import webview from '@ohos.web.webview'; 2.webview 控制类 webController: webview.WebviewController = new webview.WebviewController(); 具体的使用文档: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/ts-basic-components-web-0000001477981205-V2 实现以上操作,我们在电脑端操作 ccc 点击保存等刷新操作的时候。 手机 App 也一样能够接收到 creator 的热更新推送, cocos creator NB! --- #### 3.打包 cocos 项目导入鸿蒙项目 这里打包 cocos 项目要注意,我们期望的是手机页面内的一部分区分显示 cocos 游戏图形。 所以我们选择打包的发布平台为:Web 手机端 ![alt text](image-5.png) 哦对了,手机中的交互均为触摸。 将 ccc 打包完的项目包直接复制粘贴到鸿蒙项目路径下: entry\src\main\resources\rawfile 如图所示: ![alt text](image-6.png) 在这里的文件将可以所谓鸿蒙项目的资源直接调用, 后续我们会详细操作这些鸿蒙本地资源。 **注意**: 如果你的 DevEco Studio 版本是 3.1,并且你的 ccc 项目体积特别大。 比如我的示例项目中有一个 blender 用随机数据制作的城市模型,80mb 左右。 那么你需要等待 DevEco Studio 读取完这些文件。 ![alt text](image-7.png) 等待上图中的进度走完才能继续操作,否则无法打包 hap 到手机。 这个问题已经在 DevEco Studio 5.x 中得到解决, 开发鸿蒙 Next 星河版手机应用可以说是非常快啦。 --- #### 4.了解 webview 请求拦截防止 cors 跨域 聪明的小伙伴通过官方文档,可能已经想通过资源文件路径打开 web 项目了。 ![alt text](image-9.png) 比如这样? 这样是无法运行的,因为现代前端项目的资源请求基本不可以通过 file://协议直接打开或运行。 这是因为 cors 跨域拦截,需要我们将资源做服务端部署。 但是我们目标是 App 内部资源本地部署啊, 所以鸿蒙 webview 组件提供了请求拦截事件。 ![alt text](image-10.png) 我们可以通过 onInterceptRequest 事件,拦截 http 请求中的每一个细节。 从而返回我们需要的 Web 资源数据。 官方示例:https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/ts-basic-components-web-0000001477981205-V2#ZH-CN_TOPIC_0000001523968730__oninterceptrequest9 官方示例中,我们看到可以自定义 html 字符串数据做返回信息。 ![alt text](image-11.png) 并且在这里我们还可以伪造几乎所有 HTTP 所需要的 Response 头信息。 返回我们 DevEco 编辑器,仔细查看。 ![alt text](image-12.png) 关于 setResponseData 这个关键 api 的参数,我们不仅可以放入字符串。 我们还可以放入文件的 fd 描述用的 number 整型。 所以我们需要在整个 Web 组件启动请求的那一刻之前,拿到所有 cocos 项目文件在鸿蒙 hap 资源中的 fd 文件描述。 但是问题就来了,cocos creator 打包的项目文件很多啊。 所以我们需要先写一个文件夹遍历脚本 #### 5.Web 前端项目文件遍历脚本 废话不多说,直接贴脚本。 const fs = require('fs'); const path = require('path'); function traverseDirectory(dir) { const files = fs.readdirSync(dir); const webResourceList = []; for (const file of files) { const filePath = path.join(dir, file); const stats = fs.statSync(filePath); if (stats.isDirectory()) { // 如果是文件夹,则递归遍历 const subList = traverseDirectory(filePath); webResourceList.push(...subList); } else { // 如果是文件,则处理路径并根据文件类型设置 mimeType const relativePath = filePath.replace('.\\web-mobile\\', ''); // 去掉前缀 let mimeType = ''; if (filePath.endsWith('.html')) { mimeType = 'text/html'; } else if (filePath.endsWith('.css')) { mimeType = 'text/css'; } else if (filePath.endsWith('.js')) { mimeType = 'application/javascript'; } else if (filePath.endsWith('.svg')) { mimeType = 'image/svg+xml'; }else if (filePath.endsWith('.png')) { mimeType = 'image/png'; }else if (filePath.endsWith('.jpg')) { mimeType = 'image/jpeg'; }else if (filePath.endsWith('.map')) { mimeType = 'application/js'; }else if (filePath.endsWith('.json')) { mimeType = 'application/json'; }else if (filePath.endsWith('.png')) { mimeType = 'image/png'; }else if (filePath.endsWith('.bin')) { mimeType = 'application/octet-stream'; }else if (filePath.endsWith('.wasm')) { mimeType = 'application/wasm'; } // 添加到 webResourceList webResourceList.push({ path: relativePath.replace(/\\/g, '/'), // 将 \ 替换为 / fd: null, // 你可以根据需要设置文件描述符 mimeType: mimeType, }); } } return webResourceList; } // 指定目标文件夹的路径 const targetDirectory = './web-mobile/'; // 生成 TypeScript 代码 const tsCode = ` export interface WebResource { path: string; fd: number | null; mimeType: "text/html" | "text/css" | "application/javascript" | "image/svg+xml" | "image/png" | "image/jpeg" | "application/json" | "application/octet-stream" | "application/wasm"; } export const webResourceList: WebResource[] = ${JSON.stringify(traverseDirectory(targetDirectory), null, 2)}; `; // 将 TypeScript 代码写入 .ts 文件 fs.writeFileSync('webResourceList.ts', tsCode); // 打印结果 console.log('webResourceList.ts 文件已生成'); 在这里这个临时脚本的意思就是遍历目标文件夹下的所有文件, 然后生产一个结构为这样的列表数据: export interface WebResource { path: string; fd: number | null; mimeType: "text/html" | "text/css" | "application/javascript" | "image/svg+xml" | "image/png" | "image/jpeg" | "application/json" | "application/octet-stream" | "application/wasm"; } 方便我们后续在鸿蒙项目中,读取 ccc 文件资源的 fd。 具体用法: ![alt text](image-13.png) 将上述脚本保存到 ccc 项目中的 build 文件夹, 然后执行 node filepath.cjs 我们就得到了一个名为 webResourceList.ts 的文件。 我们将 webResourceList.ts 文件中的内容全部复制到鸿蒙项目中: ![alt text](image-14.png) 如果你的数据中,path 字段中有 web-mobile 文件夹名称。 批量替换如图即可。 我们要做的很简单,就是要获取在 resources\rawfile 下面所有文件的相对路径。 方便我们后续通过鸿蒙的文件读取 api 去获取 fd 数据 --- #### 6.网页资源初始化 ![alt text](image-15.png) 在页面 build 函数之外,我们可以利用的生命周期 aboutToAppear 中去提前读取所有文件的 fd 存入到一个列表中。 这个列表我们定义在了 Index 页面组件中: CocosResourceList:WebResource[] = webResourceList // 图中所有代码的类型和数据来源,就是我们上一步利用脚本产出的文件。 ![alt text](image-16.png) **注意** aboutToAppear 是鸿蒙应用开发框架 ArkUI 提供给页面的生命周期之一。 完整的生命周期如下: ![alt text](image-17.png) 官方文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-page-custom-components-lifecycle-0000001524296665-V2 可知,aboutToAppear 执行在了用户看到页面之前。 也符合我们的需要,我们同时利用 async+await 的异步等待方法可以阻塞该流程。保证我们的资源在应用启动的时候,完成资源的加载。 当然你可以在 onPageShow 之后做用户交互事件去触发,具体的这个实现是自由的。 #### 7.自定义资源请求拦截 贴代码: Web({ src: "http://xxx.dweb.club/rawfile/index.html", controller: this.webController }) .width("100.1%") .height("50%") .backgroundColor("#2B2F3B") .visibility(Visibility.Visible) .position({ x: 0, y: 0 }) .onTitleReceive(() => { console.log("----web title receive----") }) .onResourceLoad(async (event) => { console.log('onResourceLoad: ' + event.url) if (event.url == "http://xxx.dweb.club/favicon.ico") { this.webController.refresh() } }) .onInterceptRequest((event) => { console.log('onInterceptRequest: ' + event.request.getRequestUrl()); let path = new url.URL(event.request.getRequestUrl()); let rawFilePath = path.pathname; rawFilePath = path.pathname.replace("rawfile/", ""); rawFilePath = rawFilePath.replace("/", ""); console.log(JSON.stringify(rawFilePath)); // 创建一个 WebResourceResponse 对象 const webResourceResponse = new WebResourceResponse(); this.CocosResourceList.forEach((item,index)=>{ if (item.path == rawFilePath){ console.log(JSON.stringify(item)) webResourceResponse.setResponseData(item.fd); webResourceResponse.setResponseMimeType(item.mimeType); } }) webResourceResponse.setResponseEncoding("UTF-8"); webResourceResponse.setResponseCode(200); return webResourceResponse }); 以上就是完整的自定义本地资源请求拦截返回的Web组件写法, 这里可以看到,我们可以利用的事件还有很多。 比如:onTitleReceive onResourceLoad 这些都可以在后续开发的复杂场景中,进行更多的混合开发操作。 **这里有一个坑,注意** 新的web内核在请求网页的时候,如果页面没有定义标题栏的ico。 那么它会自动请求根目录下的,favicon.ico。 而ccc编译的web项目,默认并没有这个ico,所以我们需要自定义新增这个资源。然后在html中,最好再多加一行。 ![alt text](image-18.png) #### 8.总结 我们提前写好的主页路由: src: "http://xxx.dweb.club/rawfile/index.html" //xxx.dweb.club这个域是不存在的 //我们全部自定义了请求拦截,rawfile作为拦截信息用来分割 如果你完全按照上述操作,并理解。你应该看到了如下日志: ![alt text](image-19.png) 恭喜,我们完成了cocos creator项目在鸿蒙ArkUI中的webview本地部署 --- 现在我们只是完成了项目的部署,只是可以运行了而已。 并没有真正实现Web组件与鸿蒙操作系统的混合开发。 后续还有更多鸿蒙应用开发Web组件的混合开发详细内容: 1.JSBridge脚本注入 (类似Electron的上下文桥) 2.ArkUI与Web项目双向通信 小伙伴们可以通过官方文档来了解: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/web-component-overview-0000001508249461-V2 当然啦,我也有不少实战经验,比如: ArkUI+Vue+Cocos的混合开发, ![alt text](混合开发示例2.gif) 如果小伙伴们需要的话,留言评论三连啦! B站主页:https://space.bilibili.com/22690066 个人主页:https://www.dweb.club