deveco_hmigbot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +50 -0
  3. package/agents/hmigbot-worker.md +61 -0
  4. package/agents/hmigbot.md +22 -0
  5. package/agents/workflow-subagent.md +55 -0
  6. package/commands/hmigbot.md +17 -0
  7. package/dist/index.js +1 -0
  8. package/manifest.json +19 -0
  9. package/package.json +29 -0
  10. package/skills/migrate-core/FILES.md +26 -0
  11. package/skills/migrate-core/SKILL.md +484 -0
  12. package/skills/migrate-core/references/README.md +64 -0
  13. package/skills/migrate-core/references/flow/arkts-vector-gate.md +55 -0
  14. package/skills/migrate-core/references/flow/build-error-patterns.md +52 -0
  15. package/skills/migrate-core/references/flow/conventions-template.md +244 -0
  16. package/skills/migrate-core/references/flow/navigation-migration.md +42 -0
  17. package/skills/migrate-core/references/flow/platform-api-guards.md +59 -0
  18. package/skills/migrate-core/references/flow/platform-model-gaps.md +43 -0
  19. package/skills/migrate-core/references/flow/resource-conversion.md +46 -0
  20. package/skills/migrate-core/references/flow/ui-layout-semantics.md +124 -0
  21. package/skills/migrate-core/references/flow/unit-breakdown.md +42 -0
  22. package/skills/migrate-core/references/host-capabilities.md +24 -0
  23. package/skills/migrate-core/references/topics/app-identity.md +214 -0
  24. package/skills/migrate-core/references/topics/env-doctor.md +245 -0
  25. package/skills/migrate-core/references/topics/i18n/README.md +458 -0
  26. package/skills/migrate-core/references/topics/i18n/references/code-examples.md +304 -0
  27. package/skills/migrate-core/references/topics/i18n/references/common-pitfalls.md +354 -0
  28. package/skills/migrate-core/references/topics/i18n/references/dynamic-language-switch.md +464 -0
  29. package/skills/migrate-core/references/topics/i18n/references/language-codes.md +104 -0
  30. package/skills/migrate-core/references/topics/icon-sizing.md +98 -0
  31. package/skills/migrate-core/references/topics/library-migration/README.md +234 -0
  32. package/skills/migrate-core/references/topics/library-migration/closed-source-sdk.md +128 -0
  33. package/skills/migrate-core/references/topics/library-migration/download-api-decision.md +84 -0
  34. package/skills/migrate-core/references/topics/library-migration/library-mapping-table.md +100 -0
  35. package/skills/migrate-core/references/topics/library-migration/napi-compile-guide.md +84 -0
  36. package/skills/migrate-core/references/topics/library-migration/ohpm-search-guide.md +73 -0
  37. package/skills/migrate-core/references/topics/library-migration/stdlib-mapping-table.md +34 -0
  38. package/skills/migrate-core/references/topics/resources/aar-decompile.md +25 -0
  39. package/skills/migrate-core/references/topics/resources/conversion-rules.md +625 -0
  40. package/skills/migrate-core/references/topics/resources/dependency-analysis-rules.md +328 -0
  41. package/skills/migrate-core/references/topics/resources/material-design-icons.md +173 -0
  42. package/skills/migrate-core/references/topics/resources/svg-fix-patterns.md +175 -0
  43. package/skills/migrate-core/references/topics/resources/xml-drawable-to-svg-rules.md +513 -0
  44. package/skills/migrate-core/references/topics/system-capabilities/README.md +331 -0
  45. package/skills/migrate-core/references/topics/system-capabilities/avplayer-guide.md +161 -0
  46. package/skills/migrate-core/references/topics/system-capabilities/background-tasks.md +403 -0
  47. package/skills/migrate-core/references/topics/system-capabilities/browser-intent.md +121 -0
  48. package/skills/migrate-core/references/topics/system-capabilities/camera-picker.md +118 -0
  49. package/skills/migrate-core/references/topics/system-capabilities/document-picker.md +246 -0
  50. package/skills/migrate-core/references/topics/system-capabilities/file-utils.md +131 -0
  51. package/skills/migrate-core/references/topics/system-capabilities/permission-helper.md +112 -0
  52. package/skills/migrate-core/references/topics/system-capabilities/photo-access-helper.md +208 -0
  53. package/skills/migrate-core/references/topics/system-capabilities/print-management.md +213 -0
  54. package/skills/migrate-core/references/topics/system-capabilities/share-panel.md +177 -0
  55. package/skills/migrate-core/references/topics/system-capabilities/system-settings.md +322 -0
  56. package/skills/migrate-core/references/topics/system-capabilities/telephony-dial.md +49 -0
  57. package/skills/migrate-core/references/topics/system-capabilities/video-playback.md +42 -0
  58. package/skills/migrate-core/references/topics/system-capabilities/webview-patterns.md +38 -0
  59. package/skills/migrate-core/references/topics/ui-alignment/README.md +344 -0
  60. package/skills/migrate-core/references/topics/ui-alignment/references/dark-mode.md +47 -0
  61. package/skills/migrate-core/references/topics/ui-alignment/references/layout-mapping.md +301 -0
  62. package/skills/migrate-core/references/topics/ui-alignment/references/visual-patterns.md +411 -0
  63. package/skills/migrate-core/scripts/closure/check-anchors.mjs +186 -0
  64. package/skills/migrate-core/scripts/closure/check-api-guards.mjs +175 -0
  65. package/skills/migrate-core/scripts/closure/check-consumers.mjs +301 -0
  66. package/skills/migrate-core/scripts/closure/check-permissions.mjs +165 -0
  67. package/skills/migrate-core/scripts/closure/check-resources.mjs +130 -0
  68. package/skills/migrate-core/scripts/closure/check-routes.mjs +527 -0
  69. package/skills/migrate-core/scripts/closure/check-safearea.mjs +122 -0
  70. package/skills/migrate-core/scripts/closure/check-stubs.mjs +69 -0
  71. package/skills/migrate-core/scripts/closure/closure-suite.mjs +256 -0
  72. package/skills/migrate-core/scripts/closure/idioms.json +105 -0
  73. package/skills/migrate-core/scripts/convert/convert-resources.mjs +437 -0
  74. package/skills/migrate-core/scripts/feasibility/feasibility.mjs +235 -0
  75. package/skills/migrate-core/scripts/feasibility/tables/cross-platform.json +11 -0
  76. package/skills/migrate-core/scripts/feasibility/tables/deprecated-api.json +10 -0
  77. package/skills/migrate-core/scripts/feasibility/tables/imported-arkts-core.json +425 -0
  78. package/skills/migrate-core/scripts/feasibility/tables/lib-equivalence.json +206 -0
  79. package/skills/migrate-core/scripts/feasibility/tables/system-capabilities.json +22 -0
  80. package/skills/migrate-core/scripts/front.mjs +107 -0
  81. package/skills/migrate-core/scripts/interface/ark-extract.mjs +172 -0
  82. package/skills/migrate-core/scripts/interface/interface.mjs +152 -0
  83. package/skills/migrate-core/scripts/ledger/ledger.mjs +383 -0
  84. package/skills/migrate-core/scripts/ledger/parse-cards.mjs +98 -0
  85. package/skills/migrate-core/scripts/lib/literals.mjs +37 -0
  86. package/skills/migrate-core/scripts/lib/scan.mjs +315 -0
  87. package/skills/migrate-core/scripts/smoke/align-sdk.mjs +118 -0
  88. package/skills/migrate-core/scripts/smoke/ensure-sign.mjs +53 -0
  89. package/skills/migrate-core/scripts/smoke/smoke.mjs +238 -0
  90. package/skills/migrate-core/scripts/smoke/verdict.mjs +31 -0
  91. package/skills/migrate-core/scripts/smoke/walk.mjs +480 -0
  92. package/skills/migrate-core/scripts/transpile/mapping.json +76 -0
  93. package/skills/migrate-core/scripts/transpile/transpile-layout.mjs +404 -0
  94. package/skills/migrate-core/scripts/vectors/run-arkts-vectors.mjs +107 -0
  95. package/skills/migrate-core/scripts/vectors/setup-arkts-test.mjs +90 -0
  96. package/skills/migrate-core/scripts/wire/extractors.mjs +258 -0
  97. package/skills/migrate-core/scripts/wire/wire-routes.mjs +507 -0
  98. package/skills/migrate-core/templates/acceptance.js +365 -0
  99. package/skills/migrate-core/templates/explore.js +86 -0
  100. package/skills/migrate-core/templates/implement.js +211 -0
  101. package/skills/migrate-core/templates/mig_slices.js +491 -0
@@ -0,0 +1,403 @@
1
+ # 后台任务指南
2
+
3
+ > workScheduler(延迟任务)、ContinuousTask(长时任务)、AVSession(媒体会话)的使用指南。
4
+
5
+ ---
6
+
7
+ ## 后台任务类型选择
8
+
9
+ ```
10
+ 需要什么后台能力?
11
+
12
+ ├─ 定时/延迟执行任务(如定时更新数据)
13
+ │ └─ workScheduler(见下方)
14
+ │ ⚠️ 最短间隔比 Android WorkManager 更保守
15
+
16
+ ├─ 持续运行(如后台播放音乐)
17
+ │ └─ ContinuousTask(长时任务)
18
+ │ 需要 module.json5 声明 backgroundModes
19
+
20
+ └─ 媒体播放控制(通知栏/锁屏控制)
21
+ └─ AVSession(媒体会话)
22
+ 配合 ContinuousTask 使用
23
+ ```
24
+
25
+ ---
26
+
27
+ ## workScheduler(延迟任务)
28
+
29
+ ```typescript
30
+ import { workScheduler } from '@kit.BackgroundTasksKit'
31
+
32
+ // 注册延迟任务
33
+ const workInfo: workScheduler.WorkInfo = {
34
+ workId: 1,
35
+ bundleName: 'com.example.myapp',
36
+ abilityName: 'UpdateWorker',
37
+ isPersisted: true,
38
+ repeatCycleTime: 7200000, // 2 小时(官方循环任务最短间隔,不是 30 分钟)
39
+ isRepeat: true,
40
+ networkType: workScheduler.NetworkType.NETWORK_TYPE_ANY
41
+ }
42
+
43
+ workScheduler.startWork(workInfo)
44
+ ```
45
+
46
+ **注意**:
47
+ - ⚠️ HarmonyOS `workScheduler` 最短重复间隔比 Android `WorkManager` 更保守
48
+ - 不要依赖精确间隔,系统会根据电量和资源情况调整
49
+ - 如需频繁更新,改为用户手动触发 + 后台半定时
50
+
51
+ ---
52
+
53
+ ## ContinuousTask(长时任务)
54
+
55
+ 后台播放音乐等场景必须使用长时任务:
56
+
57
+ > 官方 AUDIO_PLAYBACK 类型本身覆盖"音频、视频在后台播放"。但系统会做一致性校验:申请了 AUDIO_PLAYBACK 却检测不到实际播放(典型是无声/静音的视频页)会被判未执行业务而挂起或拒批。无声视频页不要申请 AUDIO_PLAYBACK,后台展示画面走 PiP,见 video-playback.md。
58
+
59
+ ### 前提条件(三个条件缺一不可)
60
+
61
+ 1. **module.json5 声明 backgroundModes**:
62
+
63
+ ```json5
64
+ {
65
+ "module": {
66
+ "abilities": [{
67
+ "name": "EntryAbility",
68
+ "backgroundModes": ["audioPlayback"]
69
+ }]
70
+ }
71
+ }
72
+ ```
73
+
74
+ 2. **代码申请长时任务**:
75
+
76
+ ```typescript
77
+ import { backgroundTaskManager } from '@kit.BackgroundTasksKit'
78
+ import { wantAgent, WantAgent } from '@kit.AbilityKit'
79
+ import { common } from '@kit.AbilityKit'
80
+
81
+ async function startBackgroundPlay(context: common.UIAbilityContext): Promise<void> {
82
+ // 创建 WantAgent(点击通知时的跳转意图)
83
+ const wantAgentInfo: wantAgent.WantAgentInfo = {
84
+ wants: [{
85
+ bundleName: 'com.example.myapp',
86
+ abilityName: 'EntryAbility'
87
+ }],
88
+ actionType: wantAgent.OperationType.START_ABILITY,
89
+ requestCode: 0
90
+ }
91
+ const wantAgentObj = await wantAgent.getWantAgent(wantAgentInfo)
92
+
93
+ // 申请长时任务
94
+ await backgroundTaskManager.startBackgroundRunning(
95
+ context,
96
+ backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK,
97
+ wantAgentObj
98
+ )
99
+ }
100
+
101
+ // 停止长时任务
102
+ async function stopBackgroundPlay(context: common.UIAbilityContext): Promise<void> {
103
+ await backgroundTaskManager.stopBackgroundRunning(context)
104
+ }
105
+ ```
106
+
107
+ 3. **注册 AVSession**(见下方)
108
+
109
+ ---
110
+
111
+ ## AVSession(媒体会话)
112
+
113
+ ```typescript
114
+ import { avSession } from '@kit.AVSessionKit'
115
+
116
+ // 创建会话
117
+ const session = await avSession.createAVSession(context, 'musicPlayer', 'audio')
118
+
119
+ // 设置元数据
120
+ session.setAVMetadata({
121
+ assetId: 'song_001',
122
+ title: '歌曲名',
123
+ artist: '歌手',
124
+ duration: 300000 // 毫秒
125
+ })
126
+
127
+ // 设置播放状态
128
+ session.setAVPlaybackState({
129
+ state: avSession.PlaybackState.PLAYBACK_STATE_PLAY,
130
+ position: { elapsedTime: 50000, updateTime: Date.now() }
131
+ })
132
+
133
+ // 监听控制命令(来自通知栏/锁屏)
134
+ session.on('play', () => { avPlayer.play() })
135
+ session.on('pause', () => { avPlayer.pause() })
136
+ session.on('playNext', () => { playNext() })
137
+ session.on('playPrevious', () => { playPrevious() })
138
+
139
+ // 激活会话
140
+ await session.activate()
141
+
142
+ // 释放会话(退出时)
143
+ await session.destroy()
144
+ ```
145
+
146
+ ---
147
+
148
+ ## backgroundModes 类型
149
+
150
+ | 模式 | 用途 | module.json5 值 |
151
+ |------|------|----------------|
152
+ | 音频播放 | 后台播放音乐/播客 | `"audioPlayback"` |
153
+ | 定位 | 后台持续定位 | `"location"` |
154
+ | 蓝牙交互 | 后台蓝牙通信 | `"bluetoothInteraction"` |
155
+ | 数据传输 | 后台大文件下载/上传 | `"dataTransfer"` |
156
+
157
+ ---
158
+
159
+ ## 后台播放三要素实战模式
160
+
161
+ > 基于实测播客应用后台播放管理器提取的完整实战代码。
162
+
163
+ ### 三要素缺一不可
164
+
165
+ ```
166
+ 1. module.json5 声明 backgroundModes: ["audioPlayback"]
167
+ 2. backgroundTaskManager.startBackgroundRunning() 申请长时任务
168
+ 3. avSession.createAVSession() + activate() 注册媒体会话
169
+ ```
170
+
171
+ ### 完整实现 — BackgroundPlaybackManager
172
+
173
+ ```typescript
174
+ // 文件: playback/BackgroundPlaybackManager.ets
175
+ import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
176
+ import { wantAgent } from '@kit.AbilityKit';
177
+ import { avSession } from '@kit.AVSessionKit';
178
+ import { GlobalState } from '../common/GlobalState';
179
+
180
+ export interface PlaybackCallbacks {
181
+ onPlay: () => Promise<void>;
182
+ onPause: () => Promise<void>;
183
+ onSeek: (timeMs: number) => Promise<void>;
184
+ onFastForward: () => Promise<void>;
185
+ onRewind: () => Promise<void>;
186
+ onNext: () => Promise<void>;
187
+ onPrevious: () => Promise<void>;
188
+ }
189
+
190
+ export class BackgroundPlaybackManager {
191
+ private static instance: BackgroundPlaybackManager | undefined = undefined;
192
+ private session: avSession.AVSession | undefined = undefined;
193
+ private isBackgroundRunning: boolean = false;
194
+
195
+ static getInstance(): BackgroundPlaybackManager {
196
+ if (BackgroundPlaybackManager.instance === undefined) {
197
+ BackgroundPlaybackManager.instance = new BackgroundPlaybackManager();
198
+ }
199
+ return BackgroundPlaybackManager.instance;
200
+ }
201
+
202
+ async start(callbacks: PlaybackCallbacks): Promise<void> {
203
+ if (this.isBackgroundRunning) { return; }
204
+ const context = GlobalState.getContext();
205
+
206
+ // 1. 创建 WantAgent(通知栏点击跳转)
207
+ const wantAgentInfo: wantAgent.WantAgentInfo = {
208
+ wants: [{
209
+ bundleName: 'com.example.podcast',
210
+ abilityName: 'EntryAbility'
211
+ }],
212
+ actionType: wantAgent.OperationType.START_ABILITY,
213
+ requestCode: 0,
214
+ actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
215
+ };
216
+ const agent = await wantAgent.getWantAgent(wantAgentInfo);
217
+
218
+ // 2. 启动后台长时任务
219
+ await backgroundTaskManager.startBackgroundRunning(context,
220
+ backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK, agent);
221
+
222
+ // 3. 创建并激活 AVSession
223
+ this.session = await avSession.createAVSession(context, 'YourApp', 'audio');
224
+ await this.session.activate();
225
+
226
+ // 4. 注册 7 个控制命令
227
+ this.session.on('play', () => { callbacks.onPlay(); });
228
+ this.session.on('pause', () => { callbacks.onPause(); });
229
+ this.session.on('seek', (time: number) => { callbacks.onSeek(time); });
230
+ this.session.on('fastForward', () => { callbacks.onFastForward(); });
231
+ this.session.on('rewind', () => { callbacks.onRewind(); });
232
+ this.session.on('playNext', () => { callbacks.onNext(); });
233
+ this.session.on('playPrevious', () => { callbacks.onPrevious(); });
234
+
235
+ this.isBackgroundRunning = true;
236
+ }
237
+
238
+ async stop(): Promise<void> {
239
+ if (!this.isBackgroundRunning) { return; }
240
+
241
+ // 释放顺序:取消监听 → 停用会话 → 销毁会话 → 停止后台任务
242
+ if (this.session !== undefined) {
243
+ this.session.off('play');
244
+ this.session.off('pause');
245
+ this.session.off('seek');
246
+ this.session.off('fastForward');
247
+ this.session.off('rewind');
248
+ this.session.off('playNext');
249
+ this.session.off('playPrevious');
250
+ await this.session.deactivate();
251
+ await this.session.destroy();
252
+ this.session = undefined;
253
+ }
254
+
255
+ const context = GlobalState.getContext();
256
+ await backgroundTaskManager.stopBackgroundRunning(context);
257
+ this.isBackgroundRunning = false;
258
+ }
259
+
260
+ async updateMetadata(title: string, artist: string, durationMs: number): Promise<void> {
261
+ if (this.session === undefined) { return; }
262
+ const metadata: avSession.AVMetadata = {
263
+ assetId: '0',
264
+ title: title,
265
+ artist: artist,
266
+ duration: durationMs
267
+ };
268
+ await this.session.setAVMetadata(metadata);
269
+ }
270
+
271
+ async updatePlaybackState(isPlaying: boolean, positionMs: number): Promise<void> {
272
+ if (this.session === undefined) { return; }
273
+ const pos: avSession.PlaybackPosition = {
274
+ elapsedTime: positionMs,
275
+ updateTime: Date.now()
276
+ };
277
+ const playbackState: avSession.AVPlaybackState = {
278
+ state: isPlaying ? avSession.PlaybackState.PLAYBACK_STATE_PLAY :
279
+ avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
280
+ position: pos
281
+ };
282
+ await this.session.setAVPlaybackState(playbackState);
283
+ }
284
+ }
285
+ ```
286
+
287
+ ### 调用时机
288
+
289
+ ```typescript
290
+ // PlaybackController.onPrepared() 中调用 start
291
+ private onPrepared(): void {
292
+ // AVPlayer prepare 完成后,启动后台播放
293
+ const callbacks: PlaybackCallbacks = {
294
+ onPlay: async (): Promise<void> => { await this.play(); },
295
+ onPause: async (): Promise<void> => { await this.pause(); },
296
+ onSeek: async (timeMs: number): Promise<void> => { await this.seekTo(timeMs); },
297
+ onFastForward: async (): Promise<void> => { await this.fastForward(); },
298
+ onRewind: async (): Promise<void> => { await this.rewind(); },
299
+ onNext: async (): Promise<void> => { await this.skipToNext(); },
300
+ onPrevious: async (): Promise<void> => { await this.skipToPrevious(); }
301
+ };
302
+ this.backgroundManager.start(callbacks);
303
+ }
304
+
305
+ // PlaybackController.stopInternal() 中调用 stop
306
+ private async stopInternal(clearUI: boolean): Promise<void> {
307
+ await this.backgroundManager.stop();
308
+ }
309
+ ```
310
+
311
+ ---
312
+
313
+ ## workScheduler module.json5 配置模板
314
+
315
+ ### extensionAbilities 注册
316
+
317
+ ```json5
318
+ {
319
+ "module": {
320
+ "extensionAbilities": [
321
+ {
322
+ "name": "FeedUpdateWorkAbility",
323
+ "srcEntry": "./ets/workers/FeedUpdateWorkAbility.ets",
324
+ "type": "workScheduler",
325
+ "exported": false
326
+ }
327
+ ]
328
+ }
329
+ }
330
+ ```
331
+
332
+ ### WorkSchedulerExtensionAbility 实现
333
+
334
+ ```typescript
335
+ // 文件: workers/FeedUpdateWorkAbility.ets
336
+ import { WorkSchedulerExtensionAbility, workScheduler } from '@kit.BackgroundTasksKit';
337
+ import { hilog } from '@kit.PerformanceAnalysisKit';
338
+ import { FeedUpdateService } from '../network/FeedUpdateService';
339
+
340
+ const DOMAIN: number = 0x0000;
341
+ const TAG: string = 'FeedUpdateWorkAbility';
342
+
343
+ export default class FeedUpdateWorkAbility extends WorkSchedulerExtensionAbility {
344
+ onWorkStart(work: workScheduler.WorkInfo): void {
345
+ hilog.info(DOMAIN, TAG, 'Feed update work started, workId=%{public}d', work.workId);
346
+ const service = new FeedUpdateService();
347
+ service.refreshAllFeeds().then(() => {
348
+ hilog.info(DOMAIN, TAG, 'Feed update work completed');
349
+ }).catch((e: Error) => {
350
+ hilog.error(DOMAIN, TAG, 'Feed update work failed: %{public}s', e.message);
351
+ });
352
+ }
353
+
354
+ onWorkStop(work: workScheduler.WorkInfo): void {
355
+ hilog.info(DOMAIN, TAG, 'Feed update work stopped, workId=%{public}d', work.workId);
356
+ }
357
+ }
358
+ ```
359
+
360
+ ### 注册定时工作
361
+
362
+ ```typescript
363
+ import { workScheduler } from '@kit.BackgroundTasksKit';
364
+
365
+ function registerFeedUpdateWork(): void {
366
+ const workInfo: workScheduler.WorkInfo = {
367
+ workId: 1001,
368
+ bundleName: 'com.example.podcast',
369
+ abilityName: 'FeedUpdateWorkAbility',
370
+ isPersisted: true, // 重启后保留
371
+ repeatCycleTime: 7200000, // 2 小时(官方循环任务最短间隔;设了循环间隔须同时设 isRepeat 或 repeatCount 之一)
372
+ isRepeat: true, // 重复执行
373
+ networkType: workScheduler.NetworkType.NETWORK_TYPE_ANY // 需要网络
374
+ };
375
+ workScheduler.startWork(workInfo);
376
+ }
377
+
378
+ // 取消定时工作
379
+ // ⚠️ stopWork(work: WorkInfo, needCancel?: boolean): void —— 首参是【完整 WorkInfo】,
380
+ // 与 startWork 同形,须含 workId + bundleName + abilityName(缺则编译报错 10505001:
381
+ // Type '{ workId: number; }' is missing the following properties from type 'WorkInfo': bundleName, abilityName)。
382
+ // 不要传 { workId } 裸对象。复用注册时那份 workInfo(或重建含三个必填项的完整对象)。
383
+ function cancelFeedUpdateWork(): void {
384
+ const workInfo: workScheduler.WorkInfo = {
385
+ workId: 1001,
386
+ bundleName: 'com.example.podcast',
387
+ abilityName: 'FeedUpdateWorkAbility'
388
+ };
389
+ workScheduler.stopWork(workInfo);
390
+ }
391
+ ```
392
+
393
+ ### workScheduler vs Android WorkManager 对比
394
+
395
+ | 特性 | Android WorkManager | HarmonyOS workScheduler |
396
+ |------|-------------------|----------------------|
397
+ | 最短间隔 | 15 分钟 | 2 小时(循环任务,官方下限) |
398
+ | 约束条件 | 网络、充电、空闲 | 网络、充电、存储 |
399
+ | 持久化 | 自动 | `isPersisted: true` |
400
+ | 链式任务 | 支持 (WorkContinuation) | 不支持 |
401
+ | 精确执行 | 不保证 | 不保证 |
402
+ | 注册方式 | `WorkManager.enqueue()` | `workScheduler.startWork()` |
403
+ | 实现方式 | `Worker` 类 | `WorkSchedulerExtensionAbility` |
@@ -0,0 +1,121 @@
1
+ # 浏览器/URL 跳转
2
+
3
+ > 使用 `Want` + `startAbility` 拉起系统浏览器或其他应用,替代 Android 的 `Intent.ACTION_VIEW`。
4
+
5
+ ---
6
+
7
+ ## 基本导入
8
+
9
+ ```typescript
10
+ import { common, Want } from '@kit.AbilityKit'
11
+ ```
12
+
13
+ ---
14
+
15
+ ## 拉起浏览器打开 URL
16
+
17
+ > ✅ **验证状态**:已在 AboutPage.ets 中验证(网页和邮件跳转)
18
+
19
+ ```typescript
20
+ function openBrowser(context: common.UIAbilityContext, url: string): void {
21
+ const want: Want = {
22
+ action: 'ohos.want.action.viewData',
23
+ uri: url
24
+ }
25
+
26
+ context.startAbility(want)
27
+ .then(() => {
28
+ hilog.info(DOMAIN, TAG, `Opened browser to ${url}`)
29
+ })
30
+ .catch((err: Error) => {
31
+ hilog.error(DOMAIN, TAG, `Failed to open browser: ${err.message}`)
32
+ })
33
+ }
34
+ ```
35
+
36
+ ---
37
+
38
+ ## 常见 URL 协议
39
+
40
+ | 协议 | 用途 | 示例 | 验证状态 |
41
+ |------|------|------|---------|
42
+ | `https://` | 网页 | `openBrowser(ctx, 'https://example.com')` | ✅ 已验证 |
43
+ | `http://` | 网页 | `openBrowser(ctx, 'http://example.com')` | ✅ 已验证 |
44
+ | `mailto:` | 邮件 | `openBrowser(ctx, 'mailto:hello@example.com')` | ✅ 已验证 |
45
+ | `tel:` | 电话 | **电话拨号不属 URL 跳转** → 见 `telephony-dial.md`(用 `call.makeCall`);**不要**用 `openBrowser` / `viewData`+`tel:` | — |
46
+
47
+ ---
48
+
49
+ ## 发送邮件(带主题)
50
+
51
+ ```typescript
52
+ function sendEmail(context: common.UIAbilityContext, to: string, subject: string): void {
53
+ const want: Want = {
54
+ action: 'ohos.want.action.viewData',
55
+ uri: `mailto:${to}?subject=${encodeURIComponent(subject)}`
56
+ }
57
+
58
+ context.startAbility(want).catch((err: Error) => {
59
+ hilog.error(DOMAIN, TAG, `Failed to send email: ${err.message}`)
60
+ })
61
+ }
62
+ ```
63
+
64
+ ---
65
+
66
+ ## 其他常见 Want Action
67
+
68
+ | Action | 用途 |
69
+ |--------|------|
70
+ | `ohos.want.action.viewData` | 浏览器/数据查看 |
71
+ | `ohos.want.action.send` | 发送数据 |
72
+ | `ohos.want.action.edit` | 编辑数据 |
73
+ | `ohos.want.action.pick` | 选择数据 |
74
+
75
+ ---
76
+
77
+ ## module.json5 配置
78
+
79
+ ### Action 区别说明
80
+
81
+ | Action | 用途 | 是否需要配置 |
82
+ |--------|------|------------|
83
+ | `ohos.want.action.viewData` | 拉起浏览器查看数据 | 通常不需要(系统内置) |
84
+ | `ohos.want.action.view` | 响应外部应用的查看请求 | 需要配置(如相册响应外部查看图片) |
85
+
86
+ ### 配置示例
87
+
88
+ ```json5
89
+ {
90
+ "skills": [{
91
+ "entities": ["entity.system.home"],
92
+ "actions": [
93
+ "ohos.want.action.home",
94
+ "ohos.want.action.view" // 响应外部查看请求(如其他应用查看图片)
95
+ ]
96
+ }]
97
+ }
98
+ ```
99
+
100
+ > ⚠️ **重要**:
101
+ > - 使用 `ohos.want.action.viewData` **主动拉起**浏览器时,通常无需配置
102
+ > - 配置 `ohos.want.action.view` 是为了**被动响应**其他应用的查看请求
103
+ > - 相册应用需要配置 `ohos.want.action.view` 以响应外部查看图片/视频的请求
104
+
105
+ ---
106
+
107
+ ## 常见错误
108
+
109
+ ### 错误 1:URL 包含特殊字符未编码
110
+
111
+ ```typescript
112
+ // 错误 — 主题包含空格和特殊字符
113
+ uri: 'mailto:hello@example.com?subject=Hello World'
114
+
115
+ // 正确 — URL 编码
116
+ uri: 'mailto:hello@example.com?subject=' + encodeURIComponent('Hello World')
117
+ ```
118
+
119
+ ### 错误 2:startAbility 返回的 Promise 未处理
120
+
121
+ `startAbility` 可能抛出异常(如应用不存在),必须 catch 处理。
@@ -0,0 +1,118 @@
1
+ # 系统相机 CameraKit
2
+
3
+ > 使用 `@kit.CameraKit` 的 `cameraPicker` 拉起系统相机,替代 Android 的 `MediaStore.ACTION_IMAGE_CAPTURE`。
4
+
5
+ ---
6
+
7
+ ## 基本导入
8
+
9
+ ```typescript
10
+ import { cameraPicker, camera } from '@kit.CameraKit'
11
+ import { common } from '@kit.AbilityKit'
12
+ ```
13
+
14
+ ---
15
+
16
+ ## 拉起系统相机拍照
17
+
18
+ ```typescript
19
+ async function openCamera(context: common.UIAbilityContext): Promise<string | null> {
20
+ // 配置相机参数
21
+ const pickerProfile: cameraPicker.PickerProfile = {
22
+ cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK // 后置相机 ✅ 已验证
23
+ // cameraPosition: camera.CameraPosition.CAMERA_POSITION_FRONT // 前置相机(SDK 有声明)
24
+ }
25
+
26
+ try {
27
+ // 拉起系统相机,用户拍照后返回
28
+ const result: cameraPicker.PickerResult = await cameraPicker.pick(
29
+ context,
30
+ [cameraPicker.PickerMediaType.PHOTO], // 拍照
31
+ pickerProfile
32
+ )
33
+
34
+ hilog.info(DOMAIN, TAG, `Photo picked: ${result.resultUri}`)
35
+ return result.resultUri
36
+ } catch (err) {
37
+ hilog.error(DOMAIN, TAG, `Pick photo error: ${err.code}, ${err.message}`)
38
+ return null
39
+ }
40
+ }
41
+ ```
42
+
43
+ ---
44
+
45
+ ## 拉起系统相机录像
46
+
47
+ > ⚠️ **验证状态**:录像功能在当前代码仓未使用,仅供参考
48
+
49
+ ```typescript
50
+ async function openCameraVideo(context: common.UIAbilityContext): Promise<string | null> {
51
+ const pickerProfile: cameraPicker.PickerProfile = {
52
+ cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
53
+ }
54
+
55
+ try {
56
+ const result: cameraPicker.PickerResult = await cameraPicker.pick(
57
+ context,
58
+ [cameraPicker.PickerMediaType.VIDEO], // 录像
59
+ pickerProfile
60
+ )
61
+
62
+ hilog.info(DOMAIN, TAG, `Video picked: ${result.resultUri}`)
63
+ return result.resultUri
64
+ } catch (err) {
65
+ hilog.error(DOMAIN, TAG, `Pick video error: ${err.code}, ${err.message}`)
66
+ return null
67
+ }
68
+ }
69
+ ```
70
+
71
+ ---
72
+
73
+ ## PickerMediaType 类型
74
+
75
+ | 类型 | 用途 | 验证状态 |
76
+ |------|------|---------|
77
+ | `PHOTO` | 拍照 | ✅ 已验证(Index.ets) |
78
+ | `VIDEO` | 录像 | ✅ SDK 有声明(PickerMediaType.VIDEO = 'video') |
79
+
80
+ ---
81
+
82
+ ## PickerResult 返回
83
+
84
+ ```typescript
85
+ interface PickerResult {
86
+ resultUri: string // 拍摄/录制后的文件 URI
87
+ // 其他字段见官方 API 文档
88
+ }
89
+ ```
90
+
91
+ ---
92
+
93
+ ## module.json5 配置
94
+
95
+ 需要在 skills 中声明相机相关 action 和 entity:
96
+
97
+ ```json5
98
+ {
99
+ "skills": [{
100
+ "entities": [
101
+ "entity.system.home",
102
+ "entity.system.camera" // ← 关键:声明相机能力
103
+ ],
104
+ "actions": [
105
+ "ohos.want.action.home",
106
+ "ohos.want.action.camera" // ← 关键:声明相机 action
107
+ ]
108
+ }]
109
+ }
110
+ ```
111
+
112
+ ---
113
+
114
+ ## 常见错误
115
+
116
+ ### 错误:未在 module.json5 声明 camera entity
117
+
118
+ 如果未声明 `entity.system.camera`,调用 `cameraPicker.pick()` 会失败,错误码因设备而异。