本文基于真实工程 sample_in_harmonyos_zhike 的实际代码撰写。文中所有目录结构、配置片段、命令行与实现细节均出自该项目,可直接对照源码验证。适合想用命令行工具链完整走通「HarmonyOS 应用开发 → 调试 → 打包 → 上架」全流程的开发者阅读。
工作台
客户
分析
「至客」(全称:至客客户订单管理系统,bundleName:com.xiaobingkj.zhike)是一款运行在 HarmonyOS 手机/平板上的轻量级客户、产品与订单管理工具,面向个体经营者与小微商户:
客户管理:列表 + 模糊搜索,新增/编辑/删除(删除二次确认);
产品管理:商品目录,记录销售价与采购价;
订单管理:选客户 + 选产品生成订单,免费版限 3 条,Premium 会员解除限制;
订单到期提醒:写入系统日历,到期当天 09:00 提醒,提前量可选 1/3/7 天;
至客 Premium:基于华为应用内购买(IAP)的一次性永久会员;
服务卡片:桌面卡片展示「最近到期客户 + 累计毛利」。
工程的关键决策是:不重复造轮子,基于华为官方开源的「HMOS代码工坊」(Apache-2.0 协议,GitCode 仓库 HarmonyOS_Samples/sample_in_harmonyos)的多端工程骨架做二次开发,把官方示例中已经打磨好的「一多三层架构、路由封装、MVVM 基类、卡片框架、账号体系」直接复用,把精力集中在业务实现与上架合规上。
开发环境与版本基线(来自软著申请资料与本机实际环境):
项
值
开发机 | Apple M2 / 24GB 内存 / macOS |
IDE | DevEco Studio 6.1.0 Release 及以上(内置 hvigor、ohpm、node、hdc 命令行工具链) |
compatibleSdkVersion | 6.0.1(21) |
targetSdkVersion | 6.1.0(23) |
语言 | ArkTS(声明式 UI) |
应用版本 | versionName 1.1.5 / versionCode 1000015 |
源码规模 | 约 3.1 万行 |
整个项目从 0 到上架划分为七个阶段,每个阶段有明确的交付物:
阶段
内容
关键交付物
一、工程初始化 | CLI 工具链确认、骨架选取、工程结构搭建 | 可 Sync、可运行的空壳工程 |
二、架构设计 | 一多三层、路由体系、MVVM 基类 | 模块依赖图、PageEnum + router_map |
三、核心功能实现 | RDB 存储、日历提醒、IAP 会员、服务卡片 | 可完整体验的业务闭环 |
四、多环境与签名 | 7 套 product、调试/发布证书分离 | build-profile.json5 签名矩阵 |
五、调试与验证 | hdc 设备调试、日志体系、内存调优 | 稳定的 debug 包 |
六、命令行打包 | hvigorw assembleApp、代码混淆、产物校验 | *-prod-signed.app 上架包 |
七、应用市场上架 | 隐私合规、权限最小化、软著、AGC 提审 | AppGallery 正式上架 |
八、下载链接 | AppGallery |
DevEco Studio 安装后自带完整命令行工具链(本机位于 /Applications/DevEco-Studio.app/Contents/tools/):
工具
作用
本项目中的典型用法
hvigor / hvigorw | 构建任务编排(对标 Gradle) | 编译、签名、打 App 包 |
ohpm | 包管理(对标 npm) | 安装 HAR 依赖如 @ohos/imageknife |
hdc | 设备调试(对标 adb) | 安装 HAP、看日志、传文件 |
node | hvigor 的运行时 | 执行 hmosword-build 集成脚本 |
日常开发可以在 IDE 里点按钮,但打包与持续集成建议走命令行——这是后面阶段六能一键出上架包的前提。
新建工程用 IDE 模板即可,但「至客」选了更省力的路线:拉取开源的「HMOS代码工坊」作为骨架。这个工程的价值在于它把一个多端 HarmonyOS 应用的工程组织方式完整示范了出来:
text代码解读复制代码sample_in_harmonyos_zhike/ ├── AppScope/
# 应用级配置(app.json5:bundleName、版本、图标、应用名) │ └── resources/
# 应用级多语言资源(应用名"至客"就在这里) ├── common/
# 通用 HAR:路由、存储、账号、埋点、网络、通用组件 ├── features/
# 业务 HAR 层 │ ├── abilitycommon/
# 手机/平板/PC 共用主框架:Splash、HomeView、Tab 容器
│
├── commonbusiness/
# Banner、详情容器、列表加载等业务公共层 │
├── componentlibrary/
# 组件库业务 │ ├── devpractices/
# Sample 业务 │ ├── exploration/
# 实践文章业务 │ ├── mine/
# 我的页 —— 至客业务的主阵地(订单/产品/会员) │
└── widgetcommon/
# 服务卡片公共能力 ├── products/
# entry 层(不同设备形态的入口) │ ├── phone/
# 手机/平板入口(当前上架形态) │
├── pc/ tv/ wearable/
# 其他端入口(暂缓,见 2.3)
├── build-profile.json5
# 工程级:模块、产品形态、SDK、签名配置 ├── oh-package.json5
# 工程级依赖入口 ├── VersionFile.json5
# 依赖版本参数化 └── hvigorfile.ts
# hvigor 插件声明
三层职责一句话讲清:products 管入口,features 管业务,common 沉淀能力。
骨架默认支持手机、PC、TV、穿戴四种形态,但「至客」首版只上手机/平板。做法很朴素——直接在 build-profile.json5 的 modules 里注释掉 wearable、tv、pc 三个 entry,只保留 phone:
json5代码解读复制代码modules: [ { name: 'phone', srcPath: './products/phone', targets:
[/* 7 套 product 映射 */] }, { name: 'mine', srcPath: './features/mine' },
// { name: 'wearable', srcPath: './products/wearable', ... }, // { name: 'tv', srcPath: '
./products/tv', ... }, // { name: 'pc', srcPath: './products/pc', ... },
{ name: 'abilitycommon', srcPath: './features/abilitycommon' }, // ... ]
多端能力保留在代码里(后续版本可以随时恢复编译),但发布形态按节奏裁剪。裁剪后编译产物更小、审核面更窄、上架更快。
上架前要核对的应用级身份信息集中在 AppScope/app.json5:
json5代码解读复制代码{ "app": { "bundleName": "com.xiaobingkj.zhike", // 包名:
发布后不可更改 "vendor": "xiaobingkj", "versionCode": 1000015,
// 每次提审要递增 "versionName": "1.1.5", "icon": "$media:hmos_layered_image",
"label": "$string:hmos_app_name" // 指向 AppScope 资源中的"至客" } }
这里有个容易吃亏的点:bundleName 一旦上架就终身绑定,起名时就要用自己持有的域名倒序(xiaobingkj.com → com.xiaobingkj.*);versionCode 用「主版本×1000000 + 递增序号」这类规则化管理,避免上架时忘记加版本号被 AGC 打回。
各模块类型与职责(可直接对照 build-profile.json5):
模块
类型
职责
products/phone | entry | 手机/平板入口,含 EntryAbility、PhoneFormAbility(卡片)、liveForm 扩展 |
features/abilitycommon | har | Splash、HomeView、Tab 主框架、生命周期复用助手 |
features/mine | har | 至客核心业务:订单列表/详情、产品管理、会员、设置、关于 |
features/commonbusiness | har | Banner、详情容器、加载更多、Tab 状态 |
common | har | 路由、存储、RDB、日历提醒、IAP 会员服务、工具组件 |
业务代码(客户/订单/产品)尽量下沉到 HAR 而不是塞进 entry 模块,好处有三个:entry 保持轻、业务可以被未来恢复的 pc/tv 端复用、HAR 可以单独做单元测试。
骨架的路由体系是三件套配合:
页面声明:每个 HAR 的 router_map.json 声明可被导航打开的页面;
名称常量:所有路由名统一收敛在 common/.../model/PageEnum.ets,不用魔法字符串;
导航封装:routermanager 里的 PageContext 包装 NavPathStack,提供 openPage() 统一入口。
业务侧打开页面的写法(摘自 MinePageVM.ets,真实代码):
typescript代码解读复制代码pageContext?.openPage({ routerName: PageEnum.ORDER_LIST_VIEW }, true);
路由名集中管理后,「加一个页面」变成三步固定动作——建 View、登记 router_map、在 PageEnum 加枚举,不容易漏配。
common 模块提供了 viewmodel/BaseVM.ets 等基类,所有页面 VM 继承它,事件驱动刷新状态:
typescript代码解读复制代码export class MinePageVM extends BaseHomeViewModel<MinePageState> {
private static instance: MinePageVM; // 页面数据源:登录项、Premium 卡片、设置、订单列表、产品管理
public listGroupData: ListGroup[] = [ /* ... */ ]; public sendEvent<T>(event:
MineEventParam<T>): void | boolean { /* ... */ } }
VM 一律单例(getInstance()),避免 Tab 切换反复重建数据源;页面状态(MinePageState)与 VM 分文件维护,状态结构一目了然。
EntryAbility 本身只有 60 行,全部生命周期委托给 features/abilitycommon 的 BaseAbilityHelper:
typescript代码解读复制代码export default class EntryAbility extends UIAbility {
private baseAbilityHelper: BaseAbilityHelper = new BaseAbilityHelper(); onCreate(want: Want,
launchParam: AbilityConstant.LaunchParam): void { AppStorage.setOrCreate<boolean>(StorageKey.
IS_SIDEBAR_LAYOUT, false); this.baseAbilityHelper.doOnCreate(this.context, want, launchParam,
true); } onWindowStageCreate(windowStage: window.WindowStage): void {
this.baseAbilityHelper.doOnWindowStageCreate(windowStage); }
// onNewWant/onForeground/onBackground... 同样委托 }
这正是「一次开发、多端部署」的关键:PC 端的 PcAbility 委托同一个 Helper,只是初始化参数不同(比如 IS_SIDEBAR_LAYOUT),UI 层自动切换侧栏/底部 Tab 布局。
这一阶段业务量最大,坑也最容易集中在这里。逐个拆解四大核心实现。
数据层位于 common/src/main/ets/database/BusinessRdbHelper.ets,设计要点直接看代码:
typescript代码解读复制代码const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'zhike_business.db', securityLevel: relationalStore.SecurityLevel.S3, // 数据库安全等级 S3
encrypt: true, // 落盘加密 };
const CREATE_ORDER_TABLE: string = 'CREATE TABLE IF NOT EXISTS OrderTable(' +
'id TEXT PRIMARY KEY, customerId TEXT NOT NULL, customerName TEXT NOT NULL, ' +
'productId TEXT NOT NULL, productName TEXT NOT NULL, salePrice REAL NOT NULL, ' + '
purchasePrice REAL NOT NULL, orderDate TEXT NOT NULL, ' + 'createdAt INTEGER NOT NULL,
updatedAt INTEGER NOT NULL)';
三张表(CustomerTable / ProductTable / OrderTable),订单表冗余了 customerName/productName,避免列表查询跨表 JOIN。写入统一用 ON_CONFLICT_REPLACE,天然支持「保存即 upsert」:
typescript代码解读复制代码public async insertOrder(order: OrderData): Promise<void> {
const store: relationalStore.RdbStore = await this.getStore(); await store.insert(ORDER_TABLE,
this.orderBucket(order), relationalStore.ConflictResolution.ON_CONFLICT_REPLACE); }
查询侧统一模式:RdbPredicates 排序 → query → 游标遍历 → finally 中 resultSet.close()。这个 finally 在全文件里严格执行,是防游标泄漏的好习惯。
数据层几条可以直接照搬的做法:
RDB 帮助类做成单例(getInstance(context)),配合 initialize() 预热;
涉及经营数据(客户、价格、毛利)记得 encrypt: true + S3,上架审核与用户信任都加分;
表设计冗余展示字段,列表页一次查询拿全数据;
所有时间戳字段用 INTEGER 毫秒值(createdAt/updatedAt),排序和增量同步都靠它。
common/src/main/ets/service/OrderReminderService.ets 实现了「订单到期写入系统日历」,有几个细节处理得比较讲究:
① 提前量可配置且白名单校验:
typescript代码解读复制代码const SUPPORTED_ADVANCE_DAYS: number[] = [1, 3, 7]; // 只允许这三档
const DEFAULT_ADVANCE_DAYS: number = 7; public static setAdvanceDays(days: number): void {
const validDays: number = SUPPORTED_ADVANCE_DAYS.includes(days) ? days : DEFAULT_ADVANCE_DAYS;
PreferenceManager.getInstance().setValue<number>(ADVANCE_DAYS_KEY, validDays); }
配置持久化用轻量 PreferenceManager,读取时非法值一律回退默认档——设置项不信任输入,先过白名单。
② 权限异步生效问题用轮询解决:requestPermissionsFromUser 返回后权限状态可能尚未同步生效,代码里做了 20 次 × 100ms 的轮询确认,日历句柄获取也做了 5 次 × 200ms 重试:
typescript代码解读复制代码const PERMISSION_CHECK_INTERVAL: number = 100; const
PERMISSION_CHECK_ATTEMPTS: number = 20; const CALENDAR_RETRY_INTERVAL: number = 200;
const CALENDAR_RETRY_ATTEMPTS: number = 5;
③ 失败降级而不是崩溃:权限被拒、日历不可用时只记日志并返回 undefined,订单照常保存——辅助能力不能阻塞主流程:
typescript代码解读复制代码if (!hasPermission) { Logger.info(TAG, 'Skip creating calendar event
because calendar permission was not granted.'); return undefined; }
common/src/main/ets/service/PremiumMembershipService.ets 对接华为应用内购买,商品是非消耗型(NONCONSUMABLE)永久会员:
typescript代码解读复制代码export const LIFETIME_PRODUCT_ID: string = 'com.xiaobingkj.zhike.lifetime
'; public static async load(context: common.UIAbilityContext): Promise<PremiumMembershipStatus> {
if (!PremiumMembershipService.isAvailable()) {
return PremiumMembershipService.unavailableStatus(); // 设备不支持 IAP 时优雅降级 }
await iap.queryEnvironmentStatus(context); const result: iap.QueryPurchaseResult = await iap.
queryPurchases(context, { productType: iap.ProductType.NONCONSUMABLE,
queryType: iap.PurchaseQueryType.CURRENT_ENTITLEMENT, // 只查当前有效权益 });
return PremiumMembershipService.hasLifetimeEntitlement(result.purchaseDataList ?? []) ? ... :
...; }
两个容易忽略的细节:
① JWS 凭证本地解析:purchaseDataList 是 JWS 字符串,代码手动做 base64url 解码并解析 payload,校验 productId 与撤销标记:
typescript代码解读复制代码private static decodeJwsPayload(jws: string): string { const parts:
string[] = jws.split('.'); if (parts.length !== 3) { throw new Error('Invalid JWS'); }
const bytes: Uint8Array = new util.Base64Helper() .decodeSync(parts[1], util.Type.BASIC_URL_SAFE)
; return util.TextDecoder.create('utf-8', { ignoreBOM: true }) .decodeToString(bytes,
{ stream: false }); }
② 错误码逐条转译成用户话术:ACCOUNT_NOT_LOGGED_IN → 请先登录华为账号、NETWORK_ERROR → 网络连接异常…、801 → 当前设备暂不支持应用内购买。付费链路上每个失败分支都有明确的用户提示,而不是把错误码直接抛给用户。
UI 侧的会员入口是 features/mine 的 PremiumCard 组件(渐变图标 + 按压缩放动效),点击后走购买流程;「我的」页每次进入调用 refreshPremiumMembership() 重新查权益,会员状态始终以 IAP 服务端查询为准,不做本地永久标记——换机、重装、多设备都能正确恢复。
桌面卡片是至客的差异化功能(products/phone/src/main/ets/widget/pages/WidgetCard.ets + PhoneFormAbility.ets):
① 卡片 UI 用 LocalStorageProp 接收数据,卡片与 FormAbility 之间解耦:
typescript代码解读复制代码@Entry(businessWidgetStorage) @Component struct WidgetCard {
@LocalStorageProp('nearestCustomerName1') nearestCustomerName1: string = '';
@LocalStorageProp('nearestCustomerName2') nearestCustomerName2: string = '';
@LocalStorageProp('totalProfit') totalProfit: string = '¥0.00';
// 蓝色渐变背景:最近到期客户 ×2 + 累计毛利 }
② 点击卡片拉起应用用 postCardAction router 动作直达 EntryAbility:
typescript代码解读复制代码.onClick(() => { postCardAction(this, { action: 'router', abilityName:
'EntryAbility' }); })
③ 数据刷新链路:PhoneFormAbility.onAddForm / onUpdateForm → 识别卡片类型(formName 持久化在 FormRdbHelper)→ 从 BusinessRdbHelper 拉全量客户/产品/订单 → BusinessWidgetManager.updateForm() 计算最近到期客户与累计毛利 → formBindingData 推给卡片:
typescript代码解读复制代码private async refreshBusinessWidget(formId: string): Promise<void> {
const helper: BusinessRdbHelper = BusinessRdbHelper.getInstance(this.context);
await helper.initialize(); const customers = await helper.queryCustomers();
const products = await helper.queryProducts(); const orders = await helper.queryOrders();
await BusinessWidgetManager.updateForm(formId, customers, products, orders); }
卡片数据从同一份 RDB 读取,主应用与卡片天然一致;卡片实例信息(formId/name/dimension)单独建表持久化,onRemoveForm 时同步清理,避免脏数据。
build-profile.json5 是命令行构建的「总开关」,至客在这里配置了 7 个 product:
product
签名配置
用途
default | device(DevEco 自动生成的调试证书,位于 ~/.ohos/config/) | 本地真机调试 |
dev / uat / uat_mirror / icsl / beta | default(正式证书) | 各测试环境 |
prod | default(正式证书) | 上架正式包 |
json5代码解读复制代码"signingConfigs": [ { "name": "device", "type": "HarmonyOS", "material":
{ /* 调试证书,~/.ohos/config/ 自动生成 */ } }, { "name": "default", "type": "HarmonyOS", "material
": { "certpath": "./dis.cer", "keyAlias": "key", "profile": "./disRelease.p7b",
"signAlg": "SHA256withECDSA", "storeFile": "./dis.p12" /* keyPassword /
storePassword 已脱敏,实际在文件中以密文存储 */ } } ], "products": [ { name: 'default',
signingConfig: "device", compatibleSdkVersion: '6.0.1(21)', targetSdkVersion: '6.1.0(23)',
runtimeOS: 'HarmonyOS', buildOption: { strictMode: { useNormalizedOHMUrl: true } } },
{ name: 'prod', signingConfig: 'default', /* 同 SDK 配置 */ }, /* dev / uat /
uat_mirror / icsl / beta ... */ ]
正式签名材料(dis.cer / dis.p12 / disRelease.p7b)放在工程根目录,从 AGC(AppGallery Connect)后台申请生成。
这套配置有几个值得注意的地方:
调试与发布证书分开:device 走 DevEco 自动签名,default 走 AGC 发布证书,不要混用;
useNormalizedOHMUrl: true:规范化模块导入 URL,工程内 HAR 互引用统一走 @ohos/common 别名,模块迁移不破;
环境差异靠 product 而不是改代码:同一份代码,hvigorw --mode module -p product=prod 一条命令切换环境;
证书文件不要提交明文密码,p12/p7b 只保留在本地(建议后续加入 .gitignore 管理)。
bash代码解读复制代码# 查看设备 hdc list targets # 安装调试包(debug 签名) hdc install
entry-default-signed.hap # 实时过滤应用日志(Logger TAG 规范:'[BusinessRdbHelper]' '
[OrderReminderService]' ...) hdc hilog | grep -E "zhike|EntryAbility|PhoneFormAbility"
common 模块统一封装 Logger,所有类顶部声明常量 TAG(如 const TAG = '[OrderReminderService]'),错误路径一律 Logger.error 带上下文(orderId、formId、err.code、err.message)。卡片、IAP、日历这类后台链路,没有结构化日志排障会非常被动。
hvigor/hvigor-config.json5 中针对构建内存做了显式调优,对中等规模工程(3 万行 + 8 个模块)很有参考价值:
json5代码解读复制代码"properties": { "hvigor.pool.cache.capacity": 0, // 关闭内存缓存
"hvigor.pool.maxSize": 5, // 限制并行池规模 "ohos.arkCompile.maxSize": 3,
// 限制 ArkTS 编译并行度 "hvigor.enableMemoryCache": false // 关闭内存缓存,
降低峰值内存 }
默认配置下 daemon 进程 maxOldSpaceSize 为 8192MB,内存吃紧时会出现构建进程被杀,按需收紧上述参数即可稳定增量编译。
这是「DevEco CLI 实战」的核心环节:不打开 IDE,纯命令行产出上架 .app 包。
bash代码解读复制代码# 1. 安装工程依赖(ohpm 会读取 oh-package.json5 + VersionFile.json5 参数化版本)
ohpm install --all # 2. 命令行打出 prod 环境的 Release App 包 hvigorw assembleApp --mode module -p
product=prod -p buildMode=release
VersionFile.json5 把依赖版本参数化(hypium 1.0.19、imageknife 3.2.8 等),升版本只改这一个文件。
构建产物输出在 build/outputs/prod/:
text代码解读复制代码build/outputs/prod/ ├── sample_in_harmonyos_zhike-prod-signed.app
# ← 上架包(已签名) ├── sample_in_harmonyos_zhike-prod-unsigned.app # 未签名包(留档比对)
├── app-symbol.zip # 符号表(崩溃分析用,记得归档!)
├── pac.json / pack.info # 打包元信息
提醒一句:app-symbol.zip 是混淆后崩溃堆栈还原的唯一依据,每次发版都要和 .app 一起归档,否则线上崩溃无法定位。
products/phone/obfuscation-rules.txt 启用了两级混淆:
text代码解读复制代码-enable-property-obfuscation # 属性名混淆 -enable-toplevel-obfuscation
# 顶层作用域名混淆
混淆后上线前逐项过一遍:
RDB 表名/列名是字符串常量,未受属性混淆影响(建表 SQL 独立于对象属性,安全);
postCardAction、router_map、module.json5 中的字符串引用未混淆;
IAP 的 JWS 解析基于 JSON.parse,接口字段(jwsPurchaseOrder、productId)未被属性混淆破坏——涉及跨进程/跨系统数据结构要在 keep 列表中保护;
混淆后全功能回归一遍:卡片、日历、IAP 三条链路。
上架成败一半取决于 module.json5 的细节,至客的配置可以直接参考:
json5代码解读复制代码"metadata": [ { "name": "client_id", "value": "6917610957443786045" },
// AGC 后台申请,账号/推送等 Kit 需要 { "name": "appgallery_privacy_hosted", "value":
"1" }, // 隐私政策托管在华为服务器 { "name": "appgallery_privacy_link_
privacy_statement", "value": "https://agreement-drcn.hispace.dbankcloud.cn/..." }
// 隐私声明必须是 https ]
审核中最容易被拒的就是「权限滥用」。至客的做法很克制——把骨架中继承来的 INTERNET、GET_NETWORK_INFO 权限直接注释掉(当前版本数据全本地化 + 华为云备份由系统通道完成,不需要应用自己持有网络权限),只保留四个:
json5代码解读复制代码"requestPermissions": [ { "name": "ohos.permission.VIBRATE",
"reason": "$string:vibrator_reason" }, { "name": "ohos.permission.GYROSCOPE" },
{ "name": "ohos.permission.READ_CALENDAR", "reason": "$string:calendar_reason", "usedScene":
{ "when": "inuse" } }, { "name": "ohos.permission.WRITE_CALENDAR", "reason":
"$string:calendar_reason", "usedScene": { "when": "inuse" } } ]
几个细节:
每个权限都有 reason 字符串资源(且提供英文)+ usedScene.when: "inuse";
用不到的权限宁可注释掉等用到再恢复,也不要「先申请着」;
二次开发开源骨架时,把继承来的权限清单逐条重审——原工程的联网需求不代表你的业务需要。
材料
来源
备注
上架包 | hvigorw assembleApp 产物 prod-signed.app | versionCode 递增后重新构建 |
符号表 | app-symbol.zip | 归档,接入崩溃分析 |
隐私声明 | AGC 托管(appgallery_privacy_hosted=1) | https 链接写入 metadata |
截图/介绍 | 真机截图 + 功能说明 | 手机/平板两套尺寸 |
软件著作权 | 《至客客户订单管理系统》登记 | 源程序量 31031 行,含 60 页源码文档与操作手册 |
内购商品 | com.xiaobingkj.zhike.lifetime(非消耗型) | AGC「我的内购」配置 |
软著材料(代码前 30 页 + 后 30 页、操作手册、申请表)直接从工程与真机导出生成,「代码 → 文档 → 权属证明」一气呵成,对个人开发者上架与后期维权都有实际帮助。
#
经验
依据
1 | 站在官方开源骨架上二次开发,架构问题官方已趟过坑 | 基于 Apache-2.0「HMOS代码工坊」 |
2 | products/features/common 三层分离,业务下沉 HAR,entry 保持薄 | 工程模块表 |
3 | 路由名集中 PageEnum + router_map,不用魔法字符串 | 路由三件套 |
4 | Ability 生命周期委托共享 Helper,多端只差初始化参数 | EntryAbility → BaseAbilityHelper |
5 | 发布形态按节奏裁剪(注释多余 entry),后续版本再放开 | build-profile modules |
#
经验
依据
6 | 经营数据 RDB 记得 encrypt: true + S3 | BusinessRdbHelper |
7 | 订单表冗余客户/产品名称,列表免 JOIN | OrderTable DDL |
8 | 游标遍历 try/finally close | 全部查询方法 |
9 | 系统能力(日历)失败只降级不阻塞主流程 | OrderReminderService |
10 | 权限异步生效要轮询确认,不要假设同步 | 20×100ms 重试设计 |
11 | 设置项读取先过白名单再回退默认值 | setAdvanceDays |
12 | IAP 权益每次进入页面重新查服务端,不做本地永久标记 | refreshPremiumMembership |
13 | 付费链路每个错误码都转译成人话 | PremiumMembershipService.errorMessage |
14 | 卡片数据与主应用共用同一份数据库,天然一致 | refreshBusinessWidget |
#
经验
依据
15 | 环境切换靠 -p product=xxx,不靠改代码 | 7 套 product 矩阵 |
16 | 调试证书与发布证书分开 | device / default 双签名配置 |
17 | 依赖版本参数化到 VersionFile.json5 | ohpm 工作流 |
18 | 发版要归档 app-symbol.zip | 混淆崩溃还原 |
19 | 权限最小化:继承来的权限逐条重审,用不到就注释 | module.json5 |
20 | 隐私政策托管 AGC + https 链接写入 metadata | appgallery_privacy_hosted |
21 | 软著材料与工程同步生成,形成权属闭环 | 软著申请资料目录 |
回头看这七个阶段,真正决定项目能不能顺利上架的,往往不是写代码的那几天,而是架构分层、签名矩阵、权限清单这些早期决策——代码可以改,bundleName 和权限声明一旦提交审核就很难回头。把这几个环节当重点对待,剩下的就是按部就班。
bash代码解读复制代码# 依赖 ohpm install --all # 调试构建(真机) hvigorw assembleHap
--mode module -p product=default -p buildMode=debug # 上架构建 hvigorw assembleApp --mode module
-p product=prod -p buildMode=release # 设备 hdc list targets hdc install build/outputs/default/entry-
default-signed.hap hdc hilog | grep zhike # 清理 hvigorw clean
本文源码基线:sample_in_harmonyos_zhike(versionCode 1000015 / versionName 1.1.5),HarmonyOS SDK 6.0.1(21) 兼容 / 6.1.0(23) 目标。
作者:范小兵
链接:https://juejin.cn/post/7685938903426547750
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。