冷启动与多端同步,是自我管理应用的第一道技术门槛
“轻规划”(AeroPlan)是一套鸿蒙原生应用,思路是“认清方向,只管去做”。原文指出,自我管理类应用常见三类痛点:计划假大空、习惯培养痛苦难熬、缺乏行动抓手。对应到产品上,它用 AI 导师“阿轻”的五感引导帮助用户描绘五年后的愿景,再把宏大目标拆到每日行动。技术开篇重点不是 UI,而是两个基础能力:HarmonyOS 6 NEXT 的 Account Kit 一键登录,以及分布式 KVStore 的多端实时同步。目标是让用户少输入、少等待,并让手机和平板之间的愿景数据无感同步。
Account Kit 一键登录:用 @kit.IDKit 换取 OpenID
为避免账号密码或短信验证码消耗“改变冲动”,登录页直接接入 HarmonyOS NEXT 原生 Account Kit。原文说明,在 NEXT 版本中华为一键登录相关 API 归属于 IDKit。核心流程是导入 @kit.IDKit,调用 huaweiIDProvider,申请 profile 权限,拿到 authCode、openId、displayName、avatarUrl,再把用户信息写入 AppStorage,随后跳转到创建愿景页。关键代码可精简为:
- import { huaweiIDProvider } from '@kit.IDKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- @Component
- export struct LoginPanel {
- private loginWithHuaweiAccount() {
- let logonService = new huaweiIDProvider.HuaweiIDLogonService();
- let request = new huaweiIDProvider.HuaweiIDLogonRequest();
- request.scopes = ['profile'];
- logonService.logon(request).then((logonResult) => {
- const authCode = logonResult.authCode;
- const openId = logonResult.openId;
- const displayName = logonResult.displayName;
- const avatarUrl = logonResult.avatarUrl;
- AppStorage.setOrCreate('userId', openId);
- AppStorage.setOrCreate('userName', displayName);
- AppStorage.setOrCreate('userAvatar', avatarUrl);
- console.info('LoginPanel', 'Huawei Account authorized successfully.');
- this.navigateToVisionGuide();
- }).catch((error: BusinessError) => {
- console.error('LoginPanel', `Logon failed, code: ${error.code}, message: ${error.message}`);
- });
- }
- }
复制代码
这里真正影响首次安装转化留存的是系统级原生授权链路:用户一次点击确认,应用获取安全 OpenID,后续业务用 OpenID 做用户标识。这段逻辑不需要自建账号体系,也不需要在冷启动阶段插入冗长表单。
分布式 KVStore:按 userId 分库,autoSync 托管多端同步
多设备场景来自原文:用户在通勤路上用手机录入闪念灵感,回家后在鸿蒙平板上继续精调“曼陀罗九宫格”。如果走传统云同步,需要服务器且延迟不可控;轻规划选择集成分布式键值数据库 Distributed KVStore。手机与平板登录同一华为账号,并处于同一近场或分布式网络时,设备间同步由鸿蒙系统底层在端侧自动完成。原文把管理逻辑封装在 DistributedDatabaseManager.ets 单例中,核心初始化和保存逻辑如下:
- import { distributedKVStore } from '@kit.ArkData';
- import { BusinessError } from '@kit.BasicServicesKit';
- export class DistributedDatabaseManager {
- private static instance: DistributedDatabaseManager | null = null;
- private kvStore: distributedKVStore.SingleKVStore | null = null;
- private kvManager: distributedKVStore.KVManager | null = null;
- private constructor() {}
- public static getInstance(): DistributedDatabaseManager {
- if (!DistributedDatabaseManager.instance) {
- DistributedDatabaseManager.instance = new DistributedDatabaseManager();
- }
- return DistributedDatabaseManager.instance;
- }
- public async initStore(context: Context): Promise<void> {
- const userId = AppStorage.get<string>('userId') || 'default_user';
- const kvManagerConfig: distributedKVStore.KVManagerConfig = {
- bundleName: 'com.aeroplan.liteplan',
- context: context
- };
- try {
- this.kvManager = distributedKVStore.createKVManager(kvManagerConfig);
- const options: distributedKVStore.Options = {
- createIfMissing: true,
- encrypt: true,
- backup: true,
- autoSync: true,
- kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
- securityLevel: distributedKVStore.SecurityLevel.S2
- };
- this.kvStore = await this.kvManager.getKVStore<distributedKVStore.SingleKVStore>(
- `store_${userId}`,
- options
- );
- this.kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE, (data) => {
- this.handleRemoteDataChange(data);
- });
- console.info('DistributedDatabaseManager', 'Distributed KVStore initialized and subscribed');
- } catch (err) {
- console.error('DistributedDatabaseManager', `Init failed: ${(err as BusinessError).message}`);
- }
- }
- public async saveVisionLetter(year: string, content: string): Promise<void> {
- if (!this.kvStore) return;
- try {
- await this.kvStore.put(`vision_${year}`, content);
- } catch (err) {
- console.error('DistributedDatabaseManager', 'Put vision letter failed', err);
- }
- }
- private handleRemoteDataChange(data: distributedKVStore.ChangeNotification) {
- console.info('DistributedDatabaseManager', 'Remote data changed, syncing to AppStorage...');
- const insertEntries = data.insertEntries;
- const updateEntries = data.updateEntries;
- [...insertEntries, ...updateEntries].forEach(entry => {
- if (entry.key.startsWith('vision_')) {
- const year = entry.key.replace('vision_', '');
- AppStorage.setOrCreate(`vision_${year}`, entry.value.value as string);
- }
- });
- }
- }
复制代码
初始化时要申请发现和连接附近设备权限。这里有两个工程要点:第一,数据库句柄使用 `store_${userId}`,按账号物理隔离;第二,options 中 autoSync: true 开启系统级无感自动同步,encrypt: true 做加密存储,securityLevel 使用 S2。远端变更通过 SUBSCRIBE_TYPE_REMOTE 监听,收到 dataChange 后从 insertEntries 和 updateEntries 中筛选 vision_ 前缀,再更新 AppStorage,让 UI 立即重绘。
避坑:账号隔离和 UI 回写不能省
按 userId 物理分库隔离是必须的。原文明确提醒,如果不为每个登录华为账号创建独立数据库文件,例如 store_${userId},切换华为账号时本地数据库可能发生越权交叉污染。另一个容易忽略的点是 autoSync 虽然托管同步,但应用侧仍要处理远程变更回调,把对端写入的数据同步到 AppStorage;否则底层数据已经对齐,界面却不会刷新。对于“轻规划”这类愿景数据,dataChange 中按 vision_ 键前缀过滤并回写,是把分布式能力接到 ArkUI 状态上的关键一步。
小结
轻规划在第一阶段用 Account Kit 完成极速冷启动,用 Distributed KVStore 摆脱中心化服务器,打通手机和平板的跨设备数据交互。原文也给出了下一阶段方向:在用户完成一键登录后,进入曼陀罗九宫格,将愿景拆解到人生 8 个象限;后续还会涉及 Markdown 语法树合并引擎与金箔微光卡片高精绘制。对于准备做鸿蒙多端应用的开发者,这条链路可以先跑通“登录标识 + 分布式 KV 存储 + 远端变更回写 UI”三个基础环节,再叠加复杂 UI 和业务模型。 |