跳到主要内容

Shiply Harmony 应用内升级 SDK 集成

一、隐私安全说明​

SDK 名称:Shiply Harmony SDK(应用内升级)
版本:2.5.7
更新时间:2026-09-21
SDK 介绍:为移动开发者提供专业的应用内灰度升级能力,复用 Shiply 配置下发协议拉取升级策略(systemId=10016)。
服务提供方:深圳市腾讯计算机系统有限公司
接入指引:《Shiply Harmony 应用内升级 SDK 接入指引》
隐私保护规则:《Shiply 灰度升级SDK隐私保护规则》

前置概念:接入前请先阅读 通用概念,了解各平台共用的 appId/appKey/userId/deviceId/custom_properties 等核心概念,避免跨平台混淆。

二、接入 SDK​

应用内升级能力集成在 @shiply/shiply HAR 包中,无需额外依赖:

ohpm install @shiply/shiply
// entry/oh-package.json5
{
"dependencies": {
"@shiply/shiply": "^2.5.7"
}
}

三、版本相关参数(必读)​

升级策略按「应用版本」匹配,因此版本参数填错是最常见的接入问题:轻则匹配不到策略,重则升级完成后仍反复弹窗、激活事件不上报。

3.1 需要宿主传入的字段​

字段类型必填说明
appVersionstring是应用版本名(versionName)
versionCodestring是应用版本号(versionCode),必须为真实值
buildNumberstring是应用构建号(buildNo),必须为真实值

三个版本字段均为宿主应用自身的版本信息,缺一不可。

必读:versionCode / buildNumber 必须传真实值

「已更新」判定会拿本机三元组(versionName + versionCode + buildNo)与策略下发值逐字段比较,任一字段不匹配即判不出「已更新」→ 升级完成后仍会持续弹窗,且激活事件不会上报。

  • versionCode 不传时 SDK 会回退读本机(bundleManager),但这仅是兑底,不应依赖;
  • buildNumber 运行时无法自动获取:鸿蒙构建号配置在 build-profile.json5 的 buildVersion,由 hvigor 编译期注入,运行时系统 API 读不到(Android 的 PackageInfo 同样无此字段)。宿主需在编译期把注入值写入常量(如 BuildProfile)后传入;
  • buildNumber 未传(恒为 "0")时与策略下发的构建号不等,「已更新」判定失效 + 激活上报不触发。SDK 初始化时会对未传 buildNumber 打告警日志,接入后请检查日志。

versionCode 可从 bundleManager 获取:

import { bundleManager } from '@kit.AbilityKit';

const bundleInfo = bundleManager.getBundleInfoForSelfSync(
bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT);
let versionName: string = bundleInfo.versionName; // 如 "6.4.50"
let versionCode: string = bundleInfo.versionCode.toString(); // 如 "6450"

3.2 参数取值示例​

import { ShiplyUpgradeConfig } from '@shiply/shiply';

let config: ShiplyUpgradeConfig = new ShiplyUpgradeConfig();

// versionName:对应 BundleInfo.versionName
config.appVersion = "6.4.50";
// versionCode:必填真实值,对应 BundleInfo.versionCode
config.versionCode = "6450";
// buildNo:必填真实值。鸿蒙构建号在 build-profile.json5 的 buildVersion(编译期注入),
// 运行时读不到,需在编译期写入常量后传入
config.buildNumber = "634";

3.3 三个版本字段的作用与影响​

  • appVersion(versionName):决定能否拉到对应版本的策略,并参与「是否已更新」判定。若与策略登记的版本不一致,策略匹配不到,或升级装完新包后仍被判为「未更新」而继续弹窗。
  • versionCode(版本号):同一版本名下的不同版本靠它区分,是「已更新」判定的必比对字段。
  • buildNumber(构建号):同一版本名、同一版本号下的不同构建靠它区分。与策略侧构建号不一致时,升级完成后的「已更新」判定失效——装完新包仍会继续弹窗,且激活事件不上报。

三个字段都会与策略下发的 apkBasicInfo 对应字段逐一比对,任一不匹配即判定为「未更新」。

四、初始化 SDK​

import { ShiplyUpgradeConfig, ShiplyUpgrade } from '@shiply/shiply';
import { common } from '@kit.AbilityKit';
import { bundleManager } from '@kit.AbilityKit';
import hilog from '@ohos.hilog';

// 版本参数(见第三节)
const bundleInfo = bundleManager.getBundleInfoForSelfSync(
bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT);

let config: ShiplyUpgradeConfig = new ShiplyUpgradeConfig();
// 从 Shiply 平台应用信息获取
config.appId = "your_app_id";
config.appKey = "your_app_key";
config.bundleId = "com.your.app";

config.userId = "your_user_id"; // 用户 ID(协议中的 guid)
config.deviceId = "your_device_id"; // 设备 ID(协议中的 qimei)
config.appVersion = bundleInfo.versionName; // versionName
config.versionCode = bundleInfo.versionCode.toString(); // versionCode(必填真实值)
// buildNumber:必填真实值,从编译期注入的构建号常量取(见 3.1,运行时读不到)
config.buildNumber = "634";
config.osVersion = "6.1.0"; // 系统版本
config.custom_properties = { channel: "appstore", user_tag: "vip" };
config.ignoreFrequencyLimit = false; // 是否忽略限频,默认 false
// 独立部署场景(需联系 Shiply 团队开通)
config.customServerUrl = "";
config.businessSetName = "";
// 多环境联调(独立部署场景):切逻辑环境,不改包
config.envId = "";
// debug 包可拉取未正式发布的策略(提测阶段自测灰度升级用)
config.isDebugPackage = false;

let logger = (level: number, log: string) => {
hilog.info(0x0000, 'ShiplyLog', '%{public}s', log);
};

let context = getContext(this) as common.UIAbilityContext;
let dbPath = context.filesDir;
let upgradeSDK = new ShiplyUpgrade(dbPath, config, logger);

构造即完成初始化,并自动执行一次激活检测上报(每次冷启动一次)。

ArkTS 严格模式:custom_properties 的类型

ShiplyUpgradeConfig.custom_properties 类型为 Record<string, string>。ArkTS 严格模式下不能直接用对象字面量赋值,需先声明接口:

interface CustomProperties extends Record<string, string> {
channel: string;
user_tag: string;
}
const props: CustomProperties = { channel: 'appstore', user_tag: 'vip' };
config.custom_properties = props;

ShiplyUpgradeConfig 参数​

字段类型必填说明
appId / appKeystring是应用凭证
userIdstring是用户 ID(协议中的 guid)
deviceIdstring是设备 ID(协议中的 qimei)
bundleIdstring是包名,与策略登记值比对
appVersionstring是versionName
versionCodestring是versionCode,必须为真实值(见第三节)
buildNumberstring是buildNo(构建号),必须为真实值(见第三节;编译期从 buildVersion 取)
osVersionstring否系统版本,用于匹配下发条件
custom_propertiesRecord\<string, string>否自定义属性,用于匹配下发条件
ignoreFrequencyLimitboolean否忽略限频,默认 false
customServerUrlstring否独立部署域名,传裸域名(/business 由编译宏统一补)
businessSetNamestring否独立部署子业务集名
envIdstring否环境 ID(独立部署/多环境联调),默认空串走正式环境
isDebugPackageboolean否是否 debug 包;true 时可拉取未正式发布的策略(提测自测用),默认 false
隐私合规

设备型号、系统版本等由业务注入,SDK 不主动采集。详见 合规使用指南。

五、检查更新​

初始化后,在业务时机调用 checkGrayUpgrade(拉取 + 判定一体):

import {
ShiplyGrayTacticsEvent,
ShiplyGrayTacticsErrorCode,
ShiplyGrayTacticsRsp
} from '@shiply/shiply';

let onResult = (event: ShiplyGrayTacticsEvent,
errorCode: ShiplyGrayTacticsErrorCode,
grayTactics: ShiplyGrayTacticsRsp | null) => {
if (!grayTactics) {
return;
}
switch (event) {
case ShiplyGrayTacticsEvent.NewTactic: {
// 有可生效的新策略,业务自行弹窗
const title = grayTactics.clientInfo.title;
const desc = grayTactics.clientInfo.theDescription;
const downloadUrl = grayTactics.apkBasicInfo.downloadUrl; // 商店/更新页链接,业务打开它跳转更新
// 弹窗前建议上报一次曝光
upgradeSDK.reportEventPopupExposure();
// 业务打开链接跳转应用商店更新(示例)
let want: Want = { uri: downloadUrl, action: 'ohos.want.action.viewData' };
getContext(this).startAbility(want);
break;
}
case ShiplyGrayTacticsEvent.NoTactic:
// 服务端未下发策略
break;
case ShiplyGrayTacticsEvent.Error:
// errorCode 见 ShiplyGrayTacticsErrorCode
break;
}
};

upgradeSDK.checkGrayUpgrade(onResult);
升级跳转由业务负责

SDK 不下载、不安装,只把 apkBasicInfo.downloadUrl(商店/更新页链接)交给业务。业务打开该链接跳转应用商店更新;装完新包后,下次冷启动 SDK 会自动检测到「本机版本 == 上次交付的目标版本」并上报激活事件。

因此同一个策略在宿主实际完成更新前会继续按限频策略弹窗,更新成功后自动停止。

downloadUrl 链接格式与打开方式​

downloadUrl 由平台在策略中配置,业务直接打开即可:

场景downloadUrl 格式打开方式
正式版(AppGallery)store://appgallery.huawei.com/app/detail?id=<bundleId>openLink 跳转应用市场详情页

接入方通常只需用 context.openLink(downloadUrl) 打开链接;不要自行拼接或改写 downloadUrl。

拉取失败与空策略的区分​

拉取结果有三种不同形态,错误码已分开:

形态回调含义
本地有可用策略NewTactic即使本次拉取失败,也优先交付本地缓存策略,不会因网络抖动丢弃
服务端未下发策略NoTactic(7)平台本来就没配策略,检查平台配置
拉取失败且本地无缓存FetchFailed(8)网络/服务端不可达,宿主可据此做重试

事件与错误码​

ShiplyGrayTacticsEvent值说明
Error-1策略下发出错 / 弹窗条件不满足
NoTactic0没有拉到策略
NewTactic1有可生效的新策略
ShiplyGrayTacticsErrorCode值说明
None0无错误
AlreadyUpdated1已经是策略的版本
InvalidType2策略类型不正确(非鸿蒙灰度包)
BundleId3bundleId 与策略不匹配
InvalidURL4downloadUrl 为空
PopTimes5弹窗次数超过上限
TimeInterval6弹窗时间小于最小间隔
NoTactic7没有拉到策略(服务端未下发)
FetchFailed8拉取失败且本地无可用策略(网络/服务端不可达,可重试)
100 及以上—缓存/网络相关错误码(对齐 GARErrorCode)

弹窗判定链​

判定顺序(任一不通过即拦截):

  1. downloadUrl 为空 → InvalidURL
  2. bundleId 与宿主不匹配 → BundleId
  3. pkgType 不是鸿蒙包(6/7/8)→ InvalidType
  4. 策略版本 == 本机版本 → AlreadyUpdated
  5. remindType == 红点(2) → 放行(不受限频)
  6. 限频:popTimes / popInterval(ignoreFrequencyLimit = true 时豁免)
版本比较口径

「已更新」判定用逐字段比较(versionName + versionCode + buildNo),不做字符串拼接。策略侧 buildNoStr 优先,为空时回退整型 buildNo;versionCode 缺省归一到 "0"。

这也正是 versionCode 必填的原因——少传一个字段就判不出「已更新」。

六、事件上报​

// 弹窗曝光
upgradeSDK.reportEventPopupExposure();

// 通用上报
upgradeSDK.reportEvent(ShiplyUpgradeEventType.Active, ShiplyUpgradeEventResult.Success);
ShiplyUpgradeEventType说明
Active激活事件(SDK 自动上报)
PopupExposure弹窗曝光
ShiplyUpgradeEventResult值说明
Unknown-1触发
Success0成功
Fail1失败
激活事件由 SDK 自动上报

业务无需手动上报激活事件。SDK 在冷启动时比对「本机版本」与「上次交付的目标版本」,命中即上报并清除记录。

激活判定不依赖本地缓存策略:即使服务端已撤回策略(本地缓存被清),已装完新包的用户仍会正常上报激活。激活上报的归因(tacticsId / updateType)取交付时刻的策略快照,不受后续账号切换、策略重发影响。待激活记录在上报成功后才清除:HTTP 失败时保留,下次冷启动自动重试(代价是可能重复上报,对漏斗统计比漏报更友好)。

七、获取策略信息​

策略数据由 checkGrayUpgrade 的回调返回(ShiplyGrayTacticsRsp):

字段类型说明
title / newFeaturestring更新标题 / 版本描述
publishTime / updateTimenumber策略发布 / 更新时间(毫秒)
updateStrategyShiplyGrayUpdateStrategy建议(1) / 强制(2)
grayTypeShiplyGrayUpgradeType灰度(1) / 全量(2) / 热更新(3)
popTimes / popIntervalnumber最大弹窗次数 / 最小弹窗间隔(毫秒)
remindTypeShiplyGrayRemindType弹窗(1) / 红点(2) / 全部(3)
tacticsIdstring策略 ID
statusnumber策略状态
apkBasicInfoShiplyApkBasicInfo安装包信息
clientInfoShiplyClientInfo弹窗文案(title / theDescription)
extraRecord\<string, any>自定义下发字段

安装包信息(ShiplyApkBasicInfo):pkgName、md5、version(versionName)、versionCode、buildNo、buildNoStr、downloadUrl、pkgSize、bundleId、plistUrl、pkgType。

pkgType 取值:1=APK、2=TestFlight、3=Plist、4=AppStore、5=补丁包、6=鸿蒙灰度包、7=鸿蒙企业包、8=鸿蒙应用尝鲜灰度包(其余值为其他平台包类型)。

八、其他接口​

// 设置自定义属性(下次拉取生效)
upgradeSDK.setCustomProperties(props);

// 修改用户 / 设备标识(真切换,异步完成)
upgradeSDK.setUserIdentifier("new_user_id");
upgradeSDK.setDeviceId("new_device_id");
upgradeSDK.setAppId("new_app_id");

// 重新读取本地策略(不触发网络)
upgradeSDK.reloadLocalGrayStrategy();

// 清空所有缓存
upgradeSDK.removeLocalGrayStrategy();

// SDK 版本号
let version = upgradeSDK.sdkVersion();
切换账号后需自行重新拉取

调用 setUserIdentifier / setDeviceId / setAppId 后,请再调一次 checkGrayUpgrade 拉取配置。

切换是异步完成的(内核走线程池切换存储),需等切换动作完成后再拉取;连续同步调用 setUserIdentifier(...) 后立即 checkGrayUpgrade(...) 可能仍用旧身份的本地策略。建议在切换后稍作延时或监听完成时机后再拉取。

九、注意事项​

  1. 版本参数是接入第一坑:appVersion / versionCode / buildNumber 需与策略登记值一致,任一不匹配都会导致匹配不到策略或装完新包仍弹窗。详见第三节。
  2. versionCode / buildNumber 必填真实值:任一字段不匹配策略下发值都会导致「已更新」判不出,升级完成后持续弹窗且激活上报不触发。buildNumber 运行时读不到(buildVersion 编译期注入),需编译期写入常量后传入,详见第三节。
  3. SDK 不下载不安装:只下发 downloadUrl(商店/更新页链接),业务打开该链接跳转应用商店完成更新;激活状态在下次冷启动时判定。
  4. dbPath 必填且需持久目录:new ShiplyUpgrade(dbPath, config, logger) 第一个参数是数据目录,传 context.filesDir。传空串或 cache 类可清理目录会导致限频状态丢失、用户被反复弹窗。
  5. customServerUrl 传裸域名:不要自行拼 /business,该前缀由 native 编译宏统一补,两侧各拼一次会产出 /business/business 而 404。
  6. ArkTS 严格模式:custom_properties 需用 extends Record<string, string> 的接口声明后再赋值,字符串字面量索引签名会编译报错。
  7. 限频状态落盘在 dbPath 目录:按 tacticsId 维度,30 天过期;同一 tacticsId 且 publishTime 变化视为服务端重发策略,会清零计数。
  8. ignoreFrequencyLimit 仅影响弹窗:不影响拉取,也不绕过版本/bundleId/pkgType 校验。

十、常见问题​

Q:升级完成后还在反复弹窗,且激活事件没上报? 检查三个版本字段是否都传了真实值:appVersion 应对应策略侧 apkBasicInfo.version,versionCode 应对应 apkBasicInfo.versionCode,buildNumber 应对应 apkBasicInfo.buildNoStr(或 buildNo)。任一字段为默认值("0"/空串)都会同时导致弹窗不止 + 激活不上报。尤其确认 buildNumber 不是默认空串——它运行时读不到,需编译期从 buildVersion 取,这是鸿蒙侧最常见的原因。

Q:checkGrayUpgrade 一直回调 NoTactic? 依次检查:① 平台是否已创建并发布灰度任务;② 策略状态是否为「灰度中」;③ bundleId 是否与策略登记值一致;④ pkgType 是否为鸿蒙包类型(6/7/8);⑤ 下发条件(版本号、自定义属性)是否命中。

Q:拉不到策略? 依次检查:① 平台是否已创建并发布灰度任务;② 策略状态是否为「灰度中」;③ bundleId 是否与策略登记值一致;④ pkgType 是否为鸿蒙包类型(6/7/8);⑤ 下发条件(版本号、自定义属性)是否命中。

Q:回调 errorCode=8(FetchFailed)? 拉取请求失败(网络不通/服务端不可达)且本地无可用缓存策略。与 NoTactic(7) 的区别:7 是服务端正常返回但未下发策略;8 是请求本身失败。可据此做重试或提示网络异常。

Q:切换账号后配置不更新? 切换类接口只改标识,不会自动拉取,需自行再调一次 checkGrayUpgrade。

更多问题见 常见问题。

这篇文档对您有帮助吗?