鸿蒙APP引入小程序容器实战:让原生鸿蒙APP拥有运行海量小程序的能力
现在鸿蒙生态的用户越来越多,但对于大部分APP技术负责人的角度看,开发一个新的鸿蒙APP,难的不是如何从零开发一个新的APP,而是如何和已有的安卓、iOS端的APP做好资源协同,共享已有的代码资源,而不是长期维护三个团队来维护三端APP,今天分享一个思路是:基于原生代码搭建一个原生宿主APP,然后引入小程序容器来复用已有业务资产。
现在鸿蒙生态的用户越来越多,但对于大部分APP技术负责人的角度看,开发一个新的鸿蒙APP,难的不是如何从零开发一个新的APP,而是如何和已有的安卓、iOS端的APP做好资源协同,共享已有的代码资源,而不是长期维护三个团队来维护三端APP,一方面是运营成本高,另外协同效率也会变慢。
其实过去很多团队已经沉淀了不少小程序资源:运营活动、客户服务、员工工具、业务查询、预约办理、权益领取等功能,可能早已在微信、企业APP或其他移动入口中稳定运行。现在要进入鸿蒙生态,如果把这些功能全部重新用ArkTS开发一遍,研发周期、测试成本、后续多端维护压力都会上来。
今天分享一个思路是:基于原生代码搭建一个原生宿主APP,然后引入小程序容器来复用已有业务资产。
借助小程序容器,技术团队可以在鸿蒙APP中引入小程序运行时,让APP具备运行小程序的能力。这样一来,已有小程序可以作为业务模块直接接入,鸿蒙APP不必从零重写所有功能。宿主APP重点处理账号体系、入口导航、原生能力、安全边界和基础体验,小程序继续承载具体业务页面和流程。
而且,业务后续迭代也不必完全依赖鸿蒙APP发版。小程序的上架、下架、灰度发布和版本回滚,可以通过小程序管理平台完成。对于技术负责人来说,这意味着鸿蒙APP的建设不再是一次“重写所有业务”的工程,而可以变成一次“复用小程序资产、补齐宿主能力、建立发布治理体系”的架构升级。
接下来从技术实战角度,分享如何借助已有小程序资源,快速开发一个具备小程序运行能力的鸿蒙APP,并通过管理平台完成小程序上下架和灰度发布。
整体流程
一个鸿蒙APP拥有运行小程序的能力,大致可以拆成两条线。
第一条是宿主APP侧:
- 在小程序管理平台创建宿主应用,获取SDKKey和SDKSecret。
- 在鸿蒙工程中引入小程序容器SDK。
- 配置必要权限和AppletAbility。
- 在EntryAbility或页面中初始化小程序运行时客户端。
- 调用startApplet打开线上小程序。
第二条是小程序平台侧:
- 创建小程序并补充基础信息。
- 配置隐私信息和业务域名。
- 使用小程序开发者工具或兼容工具开发、调试、上传代码包。
- 提交审核,通过后发布为线上版本。
- 将小程序与宿主应用关联。
- 后续通过全量发布、比例发布或灰度计划控制版本生效范围。
一、准备凭据:先在平台创建宿主应用
接入前需要先在小程序管理平台里创建宿主应用。平台会为这个宿主应用生成对应的SDKKey和SDKSecret,后面初始化SDK时会用到。
这里有几个信息要提前确认:
- 鸿蒙APP的BundleID/ApplicationID。
- 小程序服务端地址,也就是apiServer。
- SDKKey和SDKSecret。
- 要打开的小程序AppID。
- 这个小程序是否已经上架,并且是否已经关联到当前宿主应用。
在实际项目里,我建议把这些配置放在统一的配置文件或环境配置里,不要散落在页面代码里。尤其是SaaS环境、私有化测试环境、生产环境并存时,apiServer和SDKKey很容易配错。
二、引入SDK依赖
小程序容器鸿蒙SDK支持通过oh-package.json5依赖,也可以使用本地HAR包。线上依赖方式更适合快速验证。
首先,在工程根目录的build-profile.json5中,为app/products添加strictMode配置:
{
"buildOption": {
"strictMode": {
"useNormalizedOHMUrl": true
}
}
}
下面示例保留接入代码中的包名、仓库地址和类型名称,实际项目以当前SDK版本文档为准。
然后在项目根目录创建.ohpmrc,配置小程序容器SDK的OHPM仓库:
registry=https://ohpm.openharmony.cn/ohpm/
@finclip:registry=https://ohpm.finogeeks.com/repos/ohpm
在oh-package.json5里增加依赖:
{
"dependencies": {
"@finclip/sdk": "latest"
}
}
执行安装:
ohpm install
如果项目需要离线集成或版本锁定,也可以把对应的HAR包放到工程目录中,通过本地file方式引入。生产项目更建议锁定明确版本,避免构建环境因为latest变化而产生不可预期差异。
三、配置权限
最小可运行场景下,网络权限是必须的。如果小程序会调用定位、相机、麦克风、蓝牙、通讯录、日历、相册等能力,需要按实际业务补充对应权限。
在module.json5中可以这样配置:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.APPROXIMATELY_LOCATION",
"usedScene": {
"when": "inuse"
}
},
{
"name": "ohos.permission.LOCATION",
"usedScene": {
"when": "inuse"
}
},
{
"name": "ohos.permission.CAMERA"
},
{
"name": "ohos.permission.MICROPHONE"
}
]
}
}
权限不建议一次性全开。小程序容器通常承载多个业务,权限边界应该跟业务能力对应起来:不使用地图就不要申请定位,不使用扫码或拍摄就不要申请相机。这样对上架审核、隐私说明和用户信任都更友好。
四、注册AppletAbility
如果使用Ability方式启动小程序,需要宿主APP注册一个Ability供小程序容器SDK运行小程序。这个方式比较适合让小程序拥有相对独立的窗口和生命周期。
在module.json5中增加Ability配置:
{
"module": {
"abilities": [
{
"name": "AppletAbility",
"srcEntry": "./ets/appletAbility/AppletAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:app_icon",
"launchType": "specified",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:app_icon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"removeMissionAfterTerminate": true
}
]
}
}
这里重要的是launchType。示例中使用specified,可以支持同时打开多个小程序,并避免重复创建不必要的实例。removeMissionAfterTerminate设置为true后,小程序停止时不会继续留在任务列表里。
接着创建AppletAbility.ets:
import { FinAppClient } from '@finclip/sdk';
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
export default class AppletAbility extends UIAbility {
async onWindowStageCreate(windowStage: window.WindowStage) {
const client = FinAppClient.getInstance();
await client?.initContext(this.context, windowStage, this.launchWant.parameters);
}
}
这段代码的作用,是在小程序Ability创建窗口时,把当前Ability上下文和windowStage交给小程序容器SDK,让小程序可以在这个Ability里完成页面渲染和生命周期管理。
如果项目使用Navigation方式启动小程序,则可以跳过注册AppletAbility这一步,把小程序放到宿主页面的NavPathStack里运行。两种方式都可以,实际选择要看宿主APP的架构和窗口管理要求。
五、初始化小程序运行时客户端
SDK初始化建议放在宿主应用入口Ability的onWindowStageCreate中,或者放在业务入口页初始化逻辑中。重点是只初始化一次,不要在多个页面反复初始化。
如果在页面组件中初始化,可以直接使用this.getUIContext()传入uiContext。下面是一个按Ability方式启动小程序的基础写法:
import common from '@ohos.app.ability.common';
import {
EAppletStartMode,
FinAppClient,
IFinAppConfig,
IFinAppStartMode
} from '@finclip/sdk';
@Entry
@Component
struct Index {
private context = getContext(this) as common.UIAbilityContext;
private client?: FinAppClient;
initFinClip() {
const finStoreConfigs: IFinAppConfig.IStoreConfig[] = [
{
apiServer: 'https://YOUR_API_SERVER',
sdkKey: 'YOUR_SDK_KEY',
sdkSecret: 'YOUR_SDK_SECRET',
cryptType: 'sm'
}
];
const finAppConfig: IFinAppConfig.IFinAppConfig = {
finStoreConfigs,
currentUserId: 'user_10001',
appletDebugMode: 'default',
logLevel: 'info',
uiConfig: {
hideShareAppletMenu: true,
hideFavoriteMenu: true,
appletText: '小程序'
}
};
const startMode: IFinAppStartMode = {
startMode: EAppletStartMode.Ability,
uiContext: this.getUIContext()
};
this.client = FinAppClient.init(
finAppConfig,
this.context,
'AppletAbility',
startMode
);
}
build() {
Button('初始化FinClip')
.onClick(() => {
this.initFinClip();
});
}
}
其中:
- finStoreConfigs可以配置多个服务器,用于同时打开不同环境的小程序。
- apiServer、SDKKey、SDKSecret需要和平台中创建的宿主应用一致。
- apmServer是可选项,如果有独立的数据上报服务器再配置;不配置时通常使用apiServer。
- currentUserId建议传入宿主APP的当前登录用户标识,后续做灰度、收藏、日志、行为分析时会更清楚。
- appletDebugMode开发阶段可以临时设为enable,方便调试;提测或上线前建议恢复default。
- uiConfig可以控制小程序右上角胶囊、更多菜单、分享、收藏等交互。
六、在鸿蒙APP里打开一个小程序
当SDK初始化完成、小程序已上架且已关联宿主应用后,就可以通过startApplet打开小程序。
基础打开方式:
import { FinAppClient } from '@finclip/sdk';
async function openApplet() {
const client = FinAppClient.getInstance();
await client?.startApplet({
appId: 'YOUR_APPLET_ID',
apiServer: 'https://YOUR_API_SERVER'
});
}
如果需要打开指定页面,并带上业务参数,可以使用startParams:
async function openOrderApplet(orderId: string) {
const client = FinAppClient.getInstance();
await client?.startApplet({
appId: 'YOUR_APPLET_ID',
apiServer: 'https://YOUR_API_SERVER',
startParams: {
path: '/pages/order/detail',
query: `orderId=${encodeURIComponent(orderId)}&source=harmony`
},
forceUpdate: true
});
}
这里的forceUpdate可以用于需要强制打开最新版本的场景。普通业务不一定要每次都设置,具体取决于小程序版本策略和启动性能要求。
如果是通过平台二维码打开体验版、审核版、开发版或预览版,可以使用二维码方式:
async function openAppletByQrCode(qrCodeContent: string) {
const client = FinAppClient.getInstance();
await client?.startAppletByQrCode({
qrCode: qrCodeContent
});
}
这里示例使用qrCode字段。不同SDK版本的示例文档中可能会出现大小写不同的写法,实际接入时建议以当前安装SDK的类型定义和IDE提示为准。
这个能力很适合测试阶段。运营或测试人员在平台生成二维码后,鸿蒙APP扫码即可打开对应版本,避免每次都等正式上架。
七、给灰度发布补充宿主侧参数
灰度发布通常会在平台侧配置规则,但规则能否细分,取决于宿主APP能提供哪些匹配信息。
比如我们希望某个小程序版本只对指定用户、指定城市、指定渠道或指定组织开放,就需要在SDK获取小程序信息时,把这些扩展参数带给服务端。小程序运行时通常会提供类似GrayReleaseHandler的扩展点,用来补充灰度发布数据。
示例:
import {
FinAppProxyHandlerManager,
IFinProxyHandlerItem
} from '@finclip/sdk';
class AppGrayReleaseHandler extends IFinProxyHandlerItem.GrayReleaseHandler {
getGrayExtension(appId: string, apiServer: string): Record<string, string> {
return {
userId: 'user_10001',
city: 'shenzhen',
channel: 'harmony',
orgCode: 'branch_001'
};
}
}
export function registerGrayReleaseHandler() {
FinAppProxyHandlerManager.grayReleaseHandler = new AppGrayReleaseHandler();
}
实际项目里,这些字段应该来自宿主APP的登录态、组织信息、渠道包配置或设备环境,而不是写死在代码里。这个Handler建议在SDK初始化前后、打开小程序之前完成注册。平台侧配置灰度计划时,可以围绕这些字段做匹配。这样同一个鸿蒙APP内,不同用户可能命中不同的小程序版本。
八、小程序管理平台如何完成上下架
SDK接入解决的是“APP能不能跑小程序”,然后就需要考虑如何进行上下架:
- 在平台创建小程序,补充名称、分类、图标、简介、详细描述等基础信息。
- 配置小程序隐私保护指引,说明小程序如何处理用户信息、是否集成第三方插件或SDK。
- 配置小程序业务域名。网络请求、上传、下载、Socket等域名需要按平台要求提前配置。
- 使用小程序开发者工具或兼容的小程序开发工具完成开发、调试、上传代码包。
- 在小程序详情的版本管理中选择代码包,提交为审核版本。
- 审核通过后,将审核版本发布为线上版本;如果提交审核时勾选了“审核通过后自动上架”,也可以由平台自动发布。
- 在应用管理中创建宿主应用,并把已上架小程序关联到该宿主应用。
小程序上架后,还需要与宿主应用关联。平台会校验SDKKey、BundleID和小程序关联关系。如果小程序没有和当前宿主APP关联,代码里的startApplet参数即使正确,也可能无法正常打开。
九、发布方式:全量、比例、灰度计划
小程序审核通过后,平台通常会提供几种发布方式。
全量发布适合低风险版本,比如文案修复、非核心页面优化、已充分测试的稳定版本。发布后,全体用户都可以打开最新线上版本。
比例发布适合希望先观察一段时间的版本。平台会按发布比例推送给随机用户,并提供观察窗口。这样团队可以先看启动成功率、错误日志、用户反馈,再决定是否扩大范围。
灰度发布计划更适合有明确目标人群的场景。比如:
- 只给内部员工开放新版工作台。
- 只给某个城市试点本地服务。
- 只给某个渠道的鸿蒙用户开放新功能。
- 只给白名单客户体验新的业务流程。
如果宿主侧已经通过GrayReleaseHandler补充了userId、city、channel、orgCode等字段,平台侧灰度规则就能更精细。这样业务团队不用重新打包鸿蒙APP,就可以控制小程序版本在不同人群里的生效范围。
鸿蒙APP引入小程序容器,最直观的变化是“APP可以打开小程序”。但从工程实践看,它带来的价值更接近一种业务架构调整:把一部分原本依赖APP发版的功能,变成可以独立开发、审核、上架、灰度和下架的小程序。
宿主APP仍然负责稳定的入口、账号、权限和原生能力;小程序负责更灵活的业务模块;小程序管理平台负责版本、审核、关联和灰度。
当这套链路跑通后,鸿蒙APP就不只是多了一个容器,而是拥有了一种更轻量的业务发布方式。后续无论是运营活动、内部工具、客户服务,还是第三方生态接入,都可以在不频繁改动宿主APP的前提下,更快地完成上线和迭代。
感兴趣的话可以在Gitee上详细了解:Gitee 代码地址
更多推荐



所有评论(0)