ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

UniApp鸿蒙NEXT深链配置:Scheme与关联链接从0到1

UniApp鸿蒙NEXT深链配置:Scheme与关联链接从0到1 做 App 跳转开发的老哥应该都有同感Deep Linking 本身不算高深技术但坑全部埋在细节里。尤其是 UniApp 这类跨端框架以前在 iOS 配 Universal Link、Android 配 App Links 已经有一套成熟模板可换成“鸿蒙 NEXT”“非 CLI 项目”“Vue3”这一组合之后配置入口、包名、签名校验、参数接收链路全变了。我第一次在 HBuilderX 建的 UniApp Vue3 项目里给鸿蒙加 Deep Linking 时最抓狂的不是不会配而是不知道“到底该改哪个文件”——文档要么只讲 CLI 工程要么只给鸿蒙原生开发中间那一段负责衔接的角色完全缺失。这篇文章的目标很直接帮还没接触过鸿蒙 Deep Linking 的 UniApp Vue3 开发者把自定义 Scheme 和关联链接配通让外部链接能正确唤起 App 并拿到 query 参数。整个流程围绕 HBuilderX 管理的非 CLI 项目展开也就是不通过 Vue CLI / Vite 命令行初始化的那种 uni-app 工程。如果你正在准备把 uni-app 项目转到鸿蒙 NEXT 5.0.0(12)或者项目需要支持链接拉新、App 间跳转、短信或者 Web 页面直达应用内某页面这篇文章可以当作一份能直接照着抄的作业。1. 先把鸿蒙NEXT的Deep Linking模型讲透1.1 从“拦截链接”角度看鸿蒙和安卓本是同源鸿蒙 NEXT 的 Deep Linking 设计底层思想跟安卓 Intent 很接近。它不是让系统直接去找某个页面而是先由应用向系统声明“我能处理哪些链接”当用户点击链接时系统再根据链接的 scheme、host、path 去匹配所有声明的应用最后把完整的跳转意图交给最合适的 App。这个过程大体分三步应用在工程的 module.json5 里声明skillsskills 内部用uris描述自己能识别的链接格式。外部链接被点击后华为系统的 Want 中心会根据 URI 进行匹配。匹配成功后系统把整个链接作为 Want 参数交给应用入口 Ability应用再把链接解析成 uni-app 的页面路由并跳转。所以 Deep Linking 的完整链路本质是“链接 → 系统匹配 → 应用入口 → 业务页面”。UniApp 开发者最容易忽略的恰恰是第四步鸿蒙系统只是把你的 App 拉起来并不负责帮你跳到某个 uni-app 页面。页面跳转逻辑还得自己在应用内部用代码实现。1.2 5.0.0(12) 这个版本号到底该怎么理解很多人看到“HarmonyOS SDK API 12 / 5.0.0(12)”会发懵这里简单做一个对照。鸿蒙 NEXT 对外发布时经常出现两个号码一个是系统版本号一个是 API Level。标题里的 5.0.0(12)意思是系统版本 HarmonyOS 5.0.0SDK 的 API Level 为 12。写配置的时候我们需要区分这层概念targetSdkVersion之类只影响 Android 构建鸿蒙构建基本不看它。鸿蒙工程里更多关心compatibleSdkVersion和 API Level你要支持 Deep Linking 的正式功能应保证开发环境使用的 HarmonyOS SDK 不低于 API 12。如果你在 HBuilderX 里发行鸿蒙应用底层编译器生成鸿蒙工程时通常已经带上了当前 SDK 的默认配置一般不需要手动指定版本。理解版本的意义在于排查问题。很多深链失效的案例不是配置写错而是项目用了老版本的鸿蒙 SDKmodule.json5 里的某些字段不被识别。API 12 之后的版本对uris里字段的校验比之前更严格缺一个 path 属性都有可能导致整条配置静默失败。1.3 非CLI项目存在的意义和它的限制UniApp 有多种工程形态。命令行创建的 CLI 项目内部是 Vue3 Vite可以用 vue.config.js、vite.config.ts 去做一些高度自定义非 CLI 项目则是通过 HBuilderX 可视化界面创建和管理源代码结构更简单打开就能跑。非 CLI 项目的优势是上手快、IDE 集成度高、打包配置直观。对于 Deep Linking 来说需要特别注意的是它没有一个独立的原生工程目录让你直接改真正能被鸿蒙系统识别的 module.json5 也不是写在源码根目录里的而是通过 HBuilderX 编译后对鸿蒙工程产物生成出来的。所以我们的操作顺序必须是“先改 uni-app 配置 → 再编译 → 找生成的鸿蒙工程 → 改 module.json5 → 重新打包”。如果直接改源码里的某个自定义 json不参与编译打包等于白改。有人可能会问非 CLI 项目能不能避开 module.json5直接在 uni-app 层完成 Deep Linking答案是做不到。自定义 Scheme 和关联链接的声明必须存在于鸿蒙原生工程的配置文件里跨端框架再强大也不能绕开系统机制。我们要做的不是抱怨跨界复杂而是接受这个流程一部分配置写在 uni-app 层一部分写在生成的鸿蒙工程里。2. 准备清单与配置模型梳理2.1 两种链接形态先搞清楚你要哪种鸿蒙 Deep Linking 常见两种形态它们的使用场景不同。第一种是自定义 Scheme格式像uni://app/index?fromshare。这种形式最简单不需要域名不要求网络环境适合 App 内部跳转、二维码扫描唤起、短信唤起。缺点是自定义 Scheme 是全局唯一的两个 App 用同一个 Scheme 会造成冲突iOS 上部分浏览器还会拦截自定义 Scheme导致唤起失败。第二种是应用关联链接格式像https://example.com/open/index?id123。它要求你有一个真实可控的 HTTPS 域名并且要在华为开发者后台完成域名校验。它的好处是够正式、够稳定适合市场推广、SEO、用户分享、短信推广坏处是链路更长要配置的环节更多。对于生产项目我的建议是两个一起配对外宣传用 https 连接内部功能和测试用自定义 Scheme。表格对比会更直观一些对比项自定义Scheme应用关联链接格式uni://host/pathhttps://domain/path是否需要域名否是备案/校验无需需要域名所有权校验冲突风险高低系统支持好好使用场景内部跳转、快速测试拉新、推广、短信链接2.2 module.json5 里 skills 和 uris 的关系鸿蒙工程的链接声明最终都写在工程模块的 configuration 文件里通常是module.json5。它区分模块级别而不是应用级别。理解方式可以参照安卓的AndroidManifest.xml中 intent-filter 的配置只是字段命名不同。具体职责skills负责定义唤醒入口相当于一组 Intent-Filter表示当前模块能够处理哪些类型的 Want。uris是 skills 内部的一个数组用来描述 URL 规则。每个 URI 规则由 scheme、host、path、pathStartWith 等字段组成。我们配置时通常会给多个 skill 分场景写。一个负责接收自定义 scheme 的跳转一个负责接收 https 关联链接这样维护起来比较清晰也方便单独封禁某个来源。2.3 非CLI工程里Deep Linking到底会经过哪些文件我们从头梳理一个外部链接到达 uni-app 页面所经过的路径。假设用户点击https://example.com/open/detail?id10088系统先检查所有鸿蒙应用是否声明了这个域名规则。你的 App 的 module.json5 中匹配成功系统就会启动应用入口 Ability。入口 Ability 拿到原始 Uri 后通常把它交给一个统一处理函数这个函数会解析 URL 里携带的页面路径和参数然后调用uni.navigateTo或uni.redirectTo跳转到 uni-app 页面。如果你是用 HBuilderX 生成的鸿蒙工程入口 Ability 的代码一般位于编译产物中默认会把 Want 里的 uri 转化成启动参数。所以我们大部分时候只需要关心两件事第一是 module.json5 里有没有配对第二是 App.vue 或特定页面的 onLoad、onLaunch 是否拿到参数并做了路由处理。需要提前准备的材料包括一个已经注册好的包名例如com.example.app并记录下它的 bundleName。一个可用的域名如果走关联链接需要能上传验证文件。鸿蒙应用的签名信息调试阶段会自动生成正式发布阶段要去申请。HBuilderX、DevEco Studio 或者 hdc 命令行工具测试环节会用到。3. 核心配置实操把链接规则写进鸿蒙工程3.1 自定义Scheme的配置示例假设我们的 uni-app 项目有一个商品详情页路径是/pages/detail/detail我们想通过uni://product/detail?id1这种链接打开它。那么在鸿蒙工程 module.json5 的对应模块配置里需要增加 skills。代码看起来是这样{ module: { name: entry, type: entry, skills: [ { actions: [ ohos.want.action.viewData ], uris: [ { scheme: uni, host: product, path: /detail } ] } ] } }这里简单解释字段含义ohos.want.action.viewData表示当前 ability 可以响应查看数据的请求。scheme是uni所以完整链接前缀是uni://。host是product意味着链接的 host 部分必须匹配 product。path是/detail表示路径部分必须匹配后面接参数不算路径匹配失败例如uni://product/detail?id1依然能匹配。如果你想匹配多个路径除了写多个 uri 对象还能用pathStartWith做前缀匹配{ scheme: uni, host: product, pathStartWith: /detail }这种写法更宽松适合一个模块下挂多个页面比如/detail/list、/detail/detail都能命中的场景。个人建议优先用精确的path等确实需要处理更多页面后再扩大匹配范围。过度匹配会引发后续页面路由判断时的混乱。3.2 支持https关联链接的配置方法如果走应用关联链接module.json5 里我认为最关键的并不是配置本身而是三个环节的联动后台配置、域名校验文件、module.json5。module.json5 中增加一个 https 的 uri 规则核心代码大致如下{ skills: [ { actions: [ ohos.want.action.viewData ], uris: [ { scheme: https, host: example.com, pathStartWith: /open } ] } ] }它表示当用户点击https://example.com/open/xxxxx时会尝试唤起应用。但仅改 module.json5 还不够。你需要在华为开发者后台或对应的 App Linking 控制台配置应用的关联域名并且确保你的网页服务器能提供一个验证文件通常是一个 JSON。验证文件的作用是告诉系统“这个域名的链接确实属于你”相当于占据宣示主权。如果域名校验失败系统绝不会唤起你的 App。这个问题经常发生在测试环境开发者只是改了一个 module.json5然后拼命点击链接不跳转就怨系统不稳定其实十有八九是校验文件路径不对。3.3 在HBuilderX中完成编译并找到鸿蒙工程非 CLI 项目的编译方式很简单直接在 HBuilderX 中点击“运行 → 运行到手机或模拟器 → 运行到鸿蒙手机”或者“发行 → 鸿蒙应用”。编译完成后HBuilderX 会在项目的unpackage目录下生成鸿蒙工程产物。编译产物路径大致是这样的项目目录/unpackage/dist/dev/app-harmony这个 app-harmony 目录里通常能看到 AppScope、entry、build-profile.json5 等文件。我们前面说的 module.json5一般是在entry/src/main/module.json5或类似位置。具体路径可能因为 HBuilderX 版本不同而略有差异但核心查找方法不变进入生成目录后搜索module.json5找到包含skills的模块配置。一定要记住你改的是编译产物里的 module.json5这个文件是给鸿蒙工程用的。源码里的 uni-app 配置并不会直接生成这些 skills除非 HBuilderX 增加了可视化配置项。所以这个过程是“改编译产物 → 重新编译或重新运行”。如果 HBuilderX 重新编译覆盖了这个文件要把自己的链接规则想办法固化避免每次都要手动重贴。3.4 在源码层做一个映射表方便统一维护module.json5 只是声明了“App 收到链接时可以被唤醒”但要把链接对到 uni-app 具体页面还需要在源码里写一个映射逻辑。这个映射表最好抽成一个独立文件避免散落在页面组件里。我自己的做法是在 utils 下创建deepLink.js维护一份链接前缀和页面路径的对应关系const deepLinkRouter [ { prefix: uni://product/detail, page: /pages/detail/detail }, { prefix: https://example.com/open/detail, page: /pages/detail/detail } ] export function resolveDeepLink(rawUri) { const match deepLinkRouter.find(item rawUri.startsWith(item.prefix)) if (!match) return null const queryStr rawUri.split(?)[1] || const params {} new URLSearchParams(queryStr).forEach((value, key) { params[key] value }) return { page: match.page, params } }这样之后加新页面只需要往deepLinkRouter数组里加一条不用到处改代码。如果项目里接入的深链场景很多这个文件还能配合埋点统计每个链接来源和转化率。4. 参数接收与冷热启动处理4.1 在uni-app层接收深链参数的常见套路当鸿蒙系统把链接传给应用入口后最终要落到 uni-app 层。常见方式是在App.vue的 onLaunch 中获取启动参数或者在目标页面的onLoad中获取页面参数。如果你的深链规则被鸿蒙系统直接解析成启动参数那么App.vue可以这样接收export default { onLaunch(launchOptions) { console.log(onLaunch options, JSON.stringify(launchOptions)) // 这里拿到的是应用启动参数不同编译器解析后的字段名可能不同 }, onShow() { // 热启动场景也可能在这里触发 } }页面层的 onLoad 也能接收参数这是 uni-app 所有平台都支持的export default { onLoad(options) { // options 里就是 query 参数 const id options.id || if (id) { this.loadDetail(id) } } }但这里要注意一个常见误导深链的完整 URI 并不一定等于 uni-app 页面参数对象。某些情况下鸿蒙系统只是把 uri 字符串交给应用uri 里的 product/detail 是需要你自己动手拆的。所以不要盲目期望onLoad里直接出现id一定要先在真机上打印一次确认实际拿到的字段结构再写业务逻辑。4.2 冷启动进程被杀后链接也要能找到家冷启动指的是 App 完全不在运行状态用户从短信、浏览器、桌面点击链接系统拉起应用。这是深链最典型也最重要的场景。冷启动时应用进程是全新的此时页面栈是空的你不能直接uni.navigateTo去跳转因为此时 uni-app 根页面都还没完成初始化。我一般会在App.vue的 onLaunch 里先把深链参数存到缓存或者内存对象里等首页onLoad完成后再做路由分发。伪代码export default { onLaunch(options) { const rawUri options.uri || options.url || if (rawUri) { const link resolveDeepLink(rawUri) if (link) { uni.setStorageSync(pendingDeepLink, link) } } } }然后再找一个合适的时机比如首页挂载后// 首页 onLoad() { const pending uni.getStorageSync(pendingDeepLink) if (pending) { uni.removeStorageSync(pendingDeepLink) uni.navigateTo({ url: ${pending.page}?${new URLSearchParams(pending.params).toString()} }) } }这种“先存再跳”的方式能避免在应用框架没有准备好的时候强行跳转导致的白屏或闪退。4.3 热启动App在后台时怎样截获新链接热启动指的是 App 已经在运行只是处于后台用户又点击了一个新链接。这时候 onLaunch 不会再次触发页面栈中可能已经存在多个页面。如果不做处理最常见的现象是页面没有变化用户都不知道自己点击的链接实际上打开了 App。热启动处理建议监听uni.onAppShowApp 回到前台时会触发这个回调。在这个回调里重新读取最新的深链参数uni.onAppShow((res) { const rawUri res.uri || res.url || if (!rawUri) return const link resolveDeepLink(rawUri) if (link) { uni.navigateTo({ url: ${link.page}?${new URLSearchParams(link.params).toString()} }) } })如果目标页面已经在当前页面栈里直接navigateTo会产生重复页面。更好的方案是用uni.getCurrentPages()检查当前页面栈中是否已有目标页如果有就用uni.redirectTo替换当前页或者往页面栈回退到目标页再更新参数。这一块需要配合自己项目的 tabBar 结构来设计没有绝对统一的答案。4.4 参数编码与解码越早处理越省心深链链接里的参数经常包含中文、空格、特殊字符例如商品名称、搜索关键词、邀请人昵称。如果上游生成链接时没有做 URL 编码传递到 App 后非常容易出现乱码或截断。建议在生成链接时统一用encodeURIComponent对每个参数值做编码。例如const url https://example.com/open/detail?name${encodeURIComponent(鸿蒙适配指南)}id10088在resolveDeepLink或者页面 onLoad 中再对参数值做decodeURIComponent解码。不要依赖系统自动解码很多场景下系统给的参数是原始编码状态。还有一个坑是某些链接生成方会把参数直接拼在 path 后面导致pathStartWith匹配时错误截断了 query。建议与运营同学约定一个规范最终落地页面路径永远放在 path 里参数放在 query 里。5. 真机验证与问题定位手段5.1 用浏览器和短信验证最直观配置完成后最快的验证方式有两种。第一种是直接把链接输入到鸿蒙手机的系统浏览器地址栏里比如uni://product/detail?id1回车后看是否弹出选择应用的提示或者直接拉起 App。如果没反应说明 module.json5 里的规则没有生效先检查编译产物里的 module.json5 有没有被正确打包进去。第二种方式是给手机发一条包含链接的短信从短信入口点击。短信唤起更贴近真实用户场景便于验证 scheme 在系统层的可信度。真机上我遇到过浏览器能拉起但短信不能拉起的情况原因通常还是 scheme 冲突或域名校验失败这两种方式都要测。5.2 使用hdc命令行模拟外部跳转摄影头实机不方便时可以借助鸿蒙的 hdc 工具模拟系统发起的跳转。这种验证方法最接近真实场景而且不需要用户手动点击链接。命令行的大致思路是hdc shell aa start -b com.example.app -a EntryAbility -U uni://product/detail?id1选项解释-b目标应用包名即 bundleName。-a目标 Ability 名称具体取决于鸿蒙工程里入口 Ability 的名字。-U要传递的 URI部分 SDK 版本可能使用--uri。如果命令行提示参数不正确可以用hdc shell aa start -h查看当前版本的帮助信息。不同 SDK 版本的 hdc 对参数缩写支持不完全一样建议实际执行之前先看帮助避免浪费时间。执行后观察手机是否拉起应用同时可以配合日志查看 UniApp 的 onLaunch 是否打印了参数。如果aa start本身报错优先检查包名和 Ability 名称是否匹配这是命令行方式最常见的失败点。5.3 用日志确认整个链路的参数流转很多深链问题不是“没跳转”而是“跳转了但参数没拿到”。建议在三个关键位置打日志module.json5 层面的问题没有日志需要通过hdc shell查看包安装状态和配置解析结果。鸿蒙入口 Ability 接收到 Want 之后打印原始 URI。uni-app 的 App.vue onLaunch、目标页面 onLoad 里打印处理后的参数对象。日志格式不要只打印options或单个字符串建议整体JSON.stringify输出保证能看到字段路径。我实测下来最隐蔽的问题是多个来源的字段混合后逻辑层误以为参数对象里没有数据结果实际只是字段名不同。如果 HBuilderX 控制台不打印日志通常是因为编译模式和设备连接问题。UniApp 的 Vue3 鸿蒙调试模式的日志输出位置和常规 H5 调试略有不同优先看鸿蒙侧的 console 输出必要情况下在 DevEco Studio 中打开生成的鸿蒙工程直接看系统运行日志比在 uni-app 层猜要快很多。6. 高频踩坑与排查速查表6.1 module.json5 配了 skills 却不生效先说结论大多数情况是配置写错位置。检查这个文件是否真的存在于编译后的鸿蒙工程中的entry/src/main而不是随手新建一个 json 放在别处。其次确认skills是写在正确的模块对象下面不是写在整个文件的根节点上。还有一个容易被忽略的细节如果 HBuilderX 又重新执行了一次发行操作之前手工修改的 module.json5 很可能会被覆盖。建议改完 module.json5 后立刻做好备份或者研究 HBuilderX 的 hooks 机制把配置写入操作做成自动化。6.2 两个 App 抢同一个 Scheme自定义 Scheme 是全局唯一的如果手机里同时安装了多个都声明了uni://的应用系统会弹出选择框或者直接不弹。鸿蒙的处理策略比安卓更严格同一个 scheme 被多个应用声明时实际唤起成功率会显著下降。解决方案很简单给 scheme 加上应用唯一后缀。比如你的应用是做电商的主包名是com.example.mallscheme 可以考虑写成mallapp而不是mall。越长的、越像品牌名的 scheme 越安全。发布前可以在多台设备上测试确认没有跟常见应用冲突。6.3 关联链接域名校验失败域名校验失败的原因通常是验证文件放置路径不对。鸿蒙的关联链接要求把验证文件放到域名的特定路径下并且要求 HTTPS 可访问不能有跳转。调试时如果使用 http 环境通常无法通过校验。另一个原因是校验文件内容里的包名跟你实际打包的 bundleName 不一致。可以先确认工程最终用的 bundleName 是什么再回头改验证文件里的对应字段。改完以后记得在服务器上清理缓存部分 CDN 节点会把旧文件缓存住导致校验时读到错误内容。6.4 中文参数乱码中文参数乱码的根源几乎都是编码不一致。上游生成链接没有做encodeURIComponent或者做了两次编码下游解码时就容易出错。我建议在入口处统一收口不让原始 uri 字符串直接进入业务层。先写一个解析函数内部固定 encode/decode 一次再输出结构化参数对象。这样即使上游变了格式也只需要改一个文件。6.5 鸿蒙应用市场审核时的材料配合如果你的 App 计划上架鸿蒙应用市场并且使用了 Deep Linking审核时通常需要说明链接的使用场景和唤起规则。尤其是自定义 scheme如果没有任何业务场景支撑审核人员可能会判定为无意义唤起。准备材料时建议主动提交深链触发页面截图。链接格式说明文档。安全合规自查说明说明没有利用深链做违规跳转或敏感能力调用。这一块不属于纯技术问题但一样会影响项目上线时间提前准备会比较省心。6.6 速查表形式总结常见问题症状可能原因排查方式点击链接无反应skills 未配置或配置位置错误查看构建产物中的 module.json5系统弹选择框scheme 冲突换更独特的 scheme 值能拉起但参数为空入口 Ability 未传递 uri 参数打印入口 Want 日志中文乱码编码不一致统一在解析函数内处理只有冷启动能收到热启动监听缺失使用 uni.onAppShow 补充发行后失效HBuilderX 重构覆盖 module.json5脚本固化配置或打包后手动检查7. 实操体会配置链路不难难的是“链路意识”我个人做了这么多平台适配之后最大的体会是Deep Linking 根本不是某一个文件配置完就结束的功能它是一条完整链路。上游运营生成链接的人不一定懂技术下游uni-app页面写业务的人又可能看不到鸿蒙原生工程的细节。作为工程负责人需要把“链接规范 → 系统声明 → 入口接收 → 页面路由 → 埋点统计”全程的规则定下来才不会每次新业务接入后就重新踩一遍老坑。尤其是非 CLI 项目它没有现代前端工程那么灵活的插件机制很多自动化能力也不如 CLI 项目丰富但胜在胜在简单直接。只要把 module.json5 的配置和 uni-app 内部的解析函数沉淀好后续维护成本很低。后续如果想做得更完善可以考虑把深链跟业务二维码结合用户扫一个码就能直接到达商品页也可以跟推送服务打通用户点击推送通知时不再只是打开首页而是跳转到对应的业务详情页。这些功能听着高大上落到技术层面仍然是同一套 Deep Linking 链路。先把基础配通再把参数解析收口后续所有基于链接的玩法都会变得没那么可怕。希望这份总结能给正在鸿蒙适配路上挣扎的 UniApp 开发者一些实际帮助。
返回列表