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.
- package/LICENSE +26 -0
- package/README.md +50 -0
- package/agents/hmigbot-worker.md +61 -0
- package/agents/hmigbot.md +22 -0
- package/agents/workflow-subagent.md +55 -0
- package/commands/hmigbot.md +17 -0
- package/dist/index.js +1 -0
- package/manifest.json +19 -0
- package/package.json +29 -0
- package/skills/migrate-core/FILES.md +26 -0
- package/skills/migrate-core/SKILL.md +484 -0
- package/skills/migrate-core/references/README.md +64 -0
- package/skills/migrate-core/references/flow/arkts-vector-gate.md +55 -0
- package/skills/migrate-core/references/flow/build-error-patterns.md +52 -0
- package/skills/migrate-core/references/flow/conventions-template.md +244 -0
- package/skills/migrate-core/references/flow/navigation-migration.md +42 -0
- package/skills/migrate-core/references/flow/platform-api-guards.md +59 -0
- package/skills/migrate-core/references/flow/platform-model-gaps.md +43 -0
- package/skills/migrate-core/references/flow/resource-conversion.md +46 -0
- package/skills/migrate-core/references/flow/ui-layout-semantics.md +124 -0
- package/skills/migrate-core/references/flow/unit-breakdown.md +42 -0
- package/skills/migrate-core/references/host-capabilities.md +24 -0
- package/skills/migrate-core/references/topics/app-identity.md +214 -0
- package/skills/migrate-core/references/topics/env-doctor.md +245 -0
- package/skills/migrate-core/references/topics/i18n/README.md +458 -0
- package/skills/migrate-core/references/topics/i18n/references/code-examples.md +304 -0
- package/skills/migrate-core/references/topics/i18n/references/common-pitfalls.md +354 -0
- package/skills/migrate-core/references/topics/i18n/references/dynamic-language-switch.md +464 -0
- package/skills/migrate-core/references/topics/i18n/references/language-codes.md +104 -0
- package/skills/migrate-core/references/topics/icon-sizing.md +98 -0
- package/skills/migrate-core/references/topics/library-migration/README.md +234 -0
- package/skills/migrate-core/references/topics/library-migration/closed-source-sdk.md +128 -0
- package/skills/migrate-core/references/topics/library-migration/download-api-decision.md +84 -0
- package/skills/migrate-core/references/topics/library-migration/library-mapping-table.md +100 -0
- package/skills/migrate-core/references/topics/library-migration/napi-compile-guide.md +84 -0
- package/skills/migrate-core/references/topics/library-migration/ohpm-search-guide.md +73 -0
- package/skills/migrate-core/references/topics/library-migration/stdlib-mapping-table.md +34 -0
- package/skills/migrate-core/references/topics/resources/aar-decompile.md +25 -0
- package/skills/migrate-core/references/topics/resources/conversion-rules.md +625 -0
- package/skills/migrate-core/references/topics/resources/dependency-analysis-rules.md +328 -0
- package/skills/migrate-core/references/topics/resources/material-design-icons.md +173 -0
- package/skills/migrate-core/references/topics/resources/svg-fix-patterns.md +175 -0
- package/skills/migrate-core/references/topics/resources/xml-drawable-to-svg-rules.md +513 -0
- package/skills/migrate-core/references/topics/system-capabilities/README.md +331 -0
- package/skills/migrate-core/references/topics/system-capabilities/avplayer-guide.md +161 -0
- package/skills/migrate-core/references/topics/system-capabilities/background-tasks.md +403 -0
- package/skills/migrate-core/references/topics/system-capabilities/browser-intent.md +121 -0
- package/skills/migrate-core/references/topics/system-capabilities/camera-picker.md +118 -0
- package/skills/migrate-core/references/topics/system-capabilities/document-picker.md +246 -0
- package/skills/migrate-core/references/topics/system-capabilities/file-utils.md +131 -0
- package/skills/migrate-core/references/topics/system-capabilities/permission-helper.md +112 -0
- package/skills/migrate-core/references/topics/system-capabilities/photo-access-helper.md +208 -0
- package/skills/migrate-core/references/topics/system-capabilities/print-management.md +213 -0
- package/skills/migrate-core/references/topics/system-capabilities/share-panel.md +177 -0
- package/skills/migrate-core/references/topics/system-capabilities/system-settings.md +322 -0
- package/skills/migrate-core/references/topics/system-capabilities/telephony-dial.md +49 -0
- package/skills/migrate-core/references/topics/system-capabilities/video-playback.md +42 -0
- package/skills/migrate-core/references/topics/system-capabilities/webview-patterns.md +38 -0
- package/skills/migrate-core/references/topics/ui-alignment/README.md +344 -0
- package/skills/migrate-core/references/topics/ui-alignment/references/dark-mode.md +47 -0
- package/skills/migrate-core/references/topics/ui-alignment/references/layout-mapping.md +301 -0
- package/skills/migrate-core/references/topics/ui-alignment/references/visual-patterns.md +411 -0
- package/skills/migrate-core/scripts/closure/check-anchors.mjs +186 -0
- package/skills/migrate-core/scripts/closure/check-api-guards.mjs +175 -0
- package/skills/migrate-core/scripts/closure/check-consumers.mjs +301 -0
- package/skills/migrate-core/scripts/closure/check-permissions.mjs +165 -0
- package/skills/migrate-core/scripts/closure/check-resources.mjs +130 -0
- package/skills/migrate-core/scripts/closure/check-routes.mjs +527 -0
- package/skills/migrate-core/scripts/closure/check-safearea.mjs +122 -0
- package/skills/migrate-core/scripts/closure/check-stubs.mjs +69 -0
- package/skills/migrate-core/scripts/closure/closure-suite.mjs +256 -0
- package/skills/migrate-core/scripts/closure/idioms.json +105 -0
- package/skills/migrate-core/scripts/convert/convert-resources.mjs +437 -0
- package/skills/migrate-core/scripts/feasibility/feasibility.mjs +235 -0
- package/skills/migrate-core/scripts/feasibility/tables/cross-platform.json +11 -0
- package/skills/migrate-core/scripts/feasibility/tables/deprecated-api.json +10 -0
- package/skills/migrate-core/scripts/feasibility/tables/imported-arkts-core.json +425 -0
- package/skills/migrate-core/scripts/feasibility/tables/lib-equivalence.json +206 -0
- package/skills/migrate-core/scripts/feasibility/tables/system-capabilities.json +22 -0
- package/skills/migrate-core/scripts/front.mjs +107 -0
- package/skills/migrate-core/scripts/interface/ark-extract.mjs +172 -0
- package/skills/migrate-core/scripts/interface/interface.mjs +152 -0
- package/skills/migrate-core/scripts/ledger/ledger.mjs +383 -0
- package/skills/migrate-core/scripts/ledger/parse-cards.mjs +98 -0
- package/skills/migrate-core/scripts/lib/literals.mjs +37 -0
- package/skills/migrate-core/scripts/lib/scan.mjs +315 -0
- package/skills/migrate-core/scripts/smoke/align-sdk.mjs +118 -0
- package/skills/migrate-core/scripts/smoke/ensure-sign.mjs +53 -0
- package/skills/migrate-core/scripts/smoke/smoke.mjs +238 -0
- package/skills/migrate-core/scripts/smoke/verdict.mjs +31 -0
- package/skills/migrate-core/scripts/smoke/walk.mjs +480 -0
- package/skills/migrate-core/scripts/transpile/mapping.json +76 -0
- package/skills/migrate-core/scripts/transpile/transpile-layout.mjs +404 -0
- package/skills/migrate-core/scripts/vectors/run-arkts-vectors.mjs +107 -0
- package/skills/migrate-core/scripts/vectors/setup-arkts-test.mjs +90 -0
- package/skills/migrate-core/scripts/wire/extractors.mjs +258 -0
- package/skills/migrate-core/scripts/wire/wire-routes.mjs +507 -0
- package/skills/migrate-core/templates/acceptance.js +365 -0
- package/skills/migrate-core/templates/explore.js +86 -0
- package/skills/migrate-core/templates/implement.js +211 -0
- package/skills/migrate-core/templates/mig_slices.js +491 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# 文件管理器 DocumentViewPicker
|
|
2
|
+
|
|
3
|
+
> 使用 `@kit.CoreFileKit` 的 `picker` 模块拉起系统文件管理器,替代 Android 的 `Intent.ACTION_OPEN_DOCUMENT` 和 `Intent.ACTION_CREATE_DOCUMENT`。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 基本导入
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { picker } from '@kit.CoreFileKit';
|
|
11
|
+
import { common } from '@kit.AbilityKit';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 唤起文件管理器(选择文件)
|
|
17
|
+
|
|
18
|
+
> 对应 Android:`Intent.ACTION_OPEN_DOCUMENT`
|
|
19
|
+
|
|
20
|
+
### 基础用法
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
function openFileManager(context: common.UIAbilityContext): void {
|
|
24
|
+
const docPicker = new picker.DocumentViewPicker(context);
|
|
25
|
+
const selectOptions = new picker.DocumentSelectOptions();
|
|
26
|
+
|
|
27
|
+
docPicker.select(selectOptions)
|
|
28
|
+
.then((uris: Array<string>) => {
|
|
29
|
+
hilog.info(DOMAIN, TAG, `Selected files: ${uris.length}`);
|
|
30
|
+
// uris 是用户选择的文件 URI 数组
|
|
31
|
+
uris.forEach(uri => {
|
|
32
|
+
hilog.info(DOMAIN, TAG, `Selected URI: ${uri}`);
|
|
33
|
+
});
|
|
34
|
+
})
|
|
35
|
+
.catch((err: Error) => {
|
|
36
|
+
hilog.error(DOMAIN, TAG, `Failed to open file manager: ${err.message}`);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 限定文件后缀
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
function openFileManagerWithFilter(context: common.UIAbilityContext): void {
|
|
45
|
+
const docPicker = new picker.DocumentViewPicker(context);
|
|
46
|
+
const selectOptions = new picker.DocumentSelectOptions();
|
|
47
|
+
|
|
48
|
+
// 仅允许选择特定后缀的文件
|
|
49
|
+
selectOptions.fileSuffixFilters = ['.txt', '.pdf', '.epub', '.mht'];
|
|
50
|
+
|
|
51
|
+
docPicker.select(selectOptions)
|
|
52
|
+
.then((uris: Array<string>) => {
|
|
53
|
+
if (uris.length > 0) {
|
|
54
|
+
hilog.info(DOMAIN, TAG, `Selected: ${uris[0]}`);
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
.catch((err: Error) => {
|
|
58
|
+
hilog.error(DOMAIN, TAG, `Select error: ${err.message}`);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 唤起文件管理器(保存文件)
|
|
66
|
+
|
|
67
|
+
> 对应 Android:`Intent.ACTION_CREATE_DOCUMENT`
|
|
68
|
+
|
|
69
|
+
### 基础用法
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
function saveFileToDownloads(context: common.UIAbilityContext, fileName: string, content: string): void {
|
|
73
|
+
const docPicker = new picker.DocumentViewPicker(context);
|
|
74
|
+
const saveOptions = new picker.DocumentSaveOptions();
|
|
75
|
+
|
|
76
|
+
// 设置默认文件名
|
|
77
|
+
saveOptions.newFileNames = [fileName];
|
|
78
|
+
// 限定可保存的文件后缀
|
|
79
|
+
saveOptions.fileSuffixChoices = ['.txt', '.mht', '.html'];
|
|
80
|
+
|
|
81
|
+
docPicker.save(saveOptions)
|
|
82
|
+
.then((uris: Array<string>) => {
|
|
83
|
+
if (uris.length > 0) {
|
|
84
|
+
hilog.info(DOMAIN, TAG, `File saved to: ${uris[0]}`);
|
|
85
|
+
// 通过 URI 写入文件内容(需配合 fileIo)
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
.catch((err: Error) => {
|
|
89
|
+
hilog.error(DOMAIN, TAG, `Save cancelled or failed: ${err.message}`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 完整示例:保存网页为 MHT
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { picker } from '@kit.CoreFileKit';
|
|
98
|
+
import { fileIo } from '@kit.CoreFileKit';
|
|
99
|
+
import { common } from '@kit.AbilityKit';
|
|
100
|
+
|
|
101
|
+
function saveWebPage(context: common.UIAbilityContext, title: string, content: string): void {
|
|
102
|
+
const docPicker = new picker.DocumentViewPicker(context);
|
|
103
|
+
const saveOptions = new picker.DocumentSaveOptions();
|
|
104
|
+
|
|
105
|
+
// 清理文件名中的非法字符
|
|
106
|
+
let fileName = title || 'page';
|
|
107
|
+
if (fileName.length > 50) {
|
|
108
|
+
fileName = fileName.substring(0, 50);
|
|
109
|
+
}
|
|
110
|
+
fileName = fileName.replace(/[\\/:*?"<>|]/g, '_');
|
|
111
|
+
|
|
112
|
+
saveOptions.newFileNames = [fileName + '.mht'];
|
|
113
|
+
saveOptions.fileSuffixChoices = ['MHT|.mht'];
|
|
114
|
+
|
|
115
|
+
docPicker.save(saveOptions)
|
|
116
|
+
.then((uris: Array<string>) => {
|
|
117
|
+
if (uris.length > 0) {
|
|
118
|
+
hilog.info(DOMAIN, TAG, `Saved to: ${uris[0]}`);
|
|
119
|
+
// 写入文件内容
|
|
120
|
+
try {
|
|
121
|
+
const file = fileIo.openSync(uris[0], fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE);
|
|
122
|
+
fileIo.writeSync(file.fd, content);
|
|
123
|
+
fileIo.closeSync(file.fd);
|
|
124
|
+
hilog.info(DOMAIN, TAG, 'File content written successfully');
|
|
125
|
+
} catch (writeErr) {
|
|
126
|
+
hilog.error(DOMAIN, TAG, `Write file error: ${writeErr.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
.catch((err: Error) => {
|
|
131
|
+
hilog.info(DOMAIN, TAG, `Save cancelled: ${err.message}`);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## DocumentSelectOptions 配置项
|
|
139
|
+
|
|
140
|
+
| 属性 | 类型 | 说明 |
|
|
141
|
+
|------|------|------|
|
|
142
|
+
| `fileSuffixFilters` | `string[]` | 限定可选择的文件后缀,如 `['.txt', '.pdf']` |
|
|
143
|
+
| `subFuzzingNumber` | `number` | 子目录搜索深度,默认 0(仅当前目录) |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## DocumentSaveOptions 配置项
|
|
148
|
+
|
|
149
|
+
| 属性 | 类型 | 说明 |
|
|
150
|
+
|------|------|------|
|
|
151
|
+
| `newFileNames` | `string[]` | 默认文件名数组(第一个为默认名) |
|
|
152
|
+
| `fileSuffixChoices` | `string[]` | 允许保存的文件后缀列表,格式如 `'MHT|.mht'` 或 `'.mht'` |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## DocumentViewPicker 其他方法
|
|
157
|
+
|
|
158
|
+
| 方法 | 用途 | 对应 Android |
|
|
159
|
+
|------|------|-------------|
|
|
160
|
+
| `.select(options)` | 选择文件(可多选) | `ACTION_OPEN_DOCUMENT` |
|
|
161
|
+
| `.save(options)` | 保存文件 | `ACTION_CREATE_DOCUMENT` |
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## module.json5 配置
|
|
166
|
+
|
|
167
|
+
> ⚠️ **重要**:`DocumentViewPicker` 是系统内置 picker,通常**不需要**额外配置 module.json5。
|
|
168
|
+
|
|
169
|
+
以下情况可能需要配置:
|
|
170
|
+
- 应用需要作为文件提供者响应其他应用的文件请求
|
|
171
|
+
- 需要声明特定的 file 类型支持
|
|
172
|
+
|
|
173
|
+
```json5
|
|
174
|
+
{
|
|
175
|
+
"skills": [{
|
|
176
|
+
"entities": ["entity.system.home"],
|
|
177
|
+
"actions": [
|
|
178
|
+
"ohos.want.action.home"
|
|
179
|
+
]
|
|
180
|
+
}]
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 常见错误
|
|
187
|
+
|
|
188
|
+
### 错误 1:选择文件后未正确处理 URI
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
// 错误 — 直接使用 URI 访问文件
|
|
192
|
+
const data = fileIo.readFileSync(uri); // ❌ 不支持直接路径
|
|
193
|
+
|
|
194
|
+
// 正确 — 使用 fileIo.openSync 获取 fd
|
|
195
|
+
const file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY);
|
|
196
|
+
const data = fileIo.readSync(file.fd);
|
|
197
|
+
fileIo.closeSync(file.fd);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### 错误 2:保存文件时文件名包含非法字符
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// 错误 — Windows 文件名不允许以下字符
|
|
204
|
+
saveOptions.newFileNames = ['file:name?.mht'];
|
|
205
|
+
|
|
206
|
+
// 正确 — 替换非法字符
|
|
207
|
+
let safeName = fileName.replace(/[\\/:*?"<>|]/g, '_');
|
|
208
|
+
saveOptions.newFileNames = [safeName + '.mht'];
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### 错误 3:save() 的 Promise 被静默取消
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// 错误 — 用户取消时可能静默失败
|
|
215
|
+
docPicker.save(saveOptions)
|
|
216
|
+
.then((uris) => {
|
|
217
|
+
if (uris.length > 0) { /* 保存成功 */ }
|
|
218
|
+
// ⚠️ 用户取消时 uris 为空数组,但没有提示
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// 正确 — 用 finally 或区分处理
|
|
222
|
+
docPicker.save(saveOptions)
|
|
223
|
+
.then((uris) => {
|
|
224
|
+
if (uris.length > 0) {
|
|
225
|
+
hilog.info(DOMAIN, TAG, 'Save success');
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
.catch((err) => {
|
|
229
|
+
hilog.error(DOMAIN, TAG, 'Save failed');
|
|
230
|
+
})
|
|
231
|
+
.finally(() => {
|
|
232
|
+
// 用户取消也会走到这里
|
|
233
|
+
hilog.info(DOMAIN, TAG, 'Save dialog closed');
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 与 cameraPicker 的对比
|
|
240
|
+
|
|
241
|
+
| 维度 | DocumentViewPicker(本节) | cameraPicker |
|
|
242
|
+
|------|--------------------------|--------------|
|
|
243
|
+
| 所属 Kit | `@kit.CoreFileKit` | `@kit.CameraKit` |
|
|
244
|
+
| 功能 | 选择/保存文件 | 拍照/录像 |
|
|
245
|
+
| 返回 | `Promise<Array<string>>`(URI 数组) | `Promise<PickerResult>` |
|
|
246
|
+
| module.json5 配置 | 通常不需要 | 需要 `entity.system.camera` |
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# 文件操作指南
|
|
2
|
+
|
|
3
|
+
> @kit.CoreFileKit 文件读写的完整使用模式,替代 Android 的 java.io.File。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 基本导入
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { fileIo } from '@kit.CoreFileKit'
|
|
11
|
+
import { util } from '@kit.ArkTS'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 沙箱路径
|
|
17
|
+
|
|
18
|
+
HarmonyOS 应用只能访问自己的沙箱目录:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// 获取沙箱路径
|
|
22
|
+
const filesDir = context.filesDir // 持久化存储目录
|
|
23
|
+
const cacheDir = context.cacheDir // 缓存目录(系统可能自动清理)
|
|
24
|
+
const tempDir = context.tempDir // 临时目录
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**关键规则**:
|
|
28
|
+
- ✅ 只能使用 `context.filesDir`、`context.cacheDir`、`context.tempDir`
|
|
29
|
+
- ❌ 不能写入 `/storage/...` 等绝对路径(Permission denied)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 文件读写
|
|
34
|
+
|
|
35
|
+
### 写入文件
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
function writeFile(context: Context, fileName: string, content: string): void {
|
|
39
|
+
const filePath = `${context.filesDir}/${fileName}`
|
|
40
|
+
const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY)
|
|
41
|
+
fileIo.writeSync(file.fd, content)
|
|
42
|
+
fileIo.closeSync(file.fd)
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 读取文件
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
function readFile(context: Context, fileName: string): string {
|
|
50
|
+
const filePath = `${context.filesDir}/${fileName}`
|
|
51
|
+
const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY)
|
|
52
|
+
const stat = fileIo.statSync(filePath)
|
|
53
|
+
const buffer = new ArrayBuffer(stat.size)
|
|
54
|
+
fileIo.readSync(file.fd, buffer)
|
|
55
|
+
fileIo.closeSync(file.fd)
|
|
56
|
+
|
|
57
|
+
const decoder = new util.TextDecoder('utf-8')
|
|
58
|
+
return decoder.decodeWithStream(new Uint8Array(buffer))
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 检查文件是否存在
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
function fileExists(path: string): boolean {
|
|
66
|
+
try {
|
|
67
|
+
fileIo.accessSync(path)
|
|
68
|
+
return true
|
|
69
|
+
} catch {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 创建目录
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
function ensureDir(dirPath: string): void {
|
|
79
|
+
try {
|
|
80
|
+
fileIo.mkdirSync(dirPath, true) // true = 递归创建
|
|
81
|
+
} catch {
|
|
82
|
+
// 目录已存在
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 删除文件
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
function deleteFile(path: string): void {
|
|
91
|
+
try {
|
|
92
|
+
fileIo.unlinkSync(path)
|
|
93
|
+
} catch {
|
|
94
|
+
// 文件不存在
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 下载文件到沙箱
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { http } from '@kit.NetworkKit'
|
|
105
|
+
|
|
106
|
+
async function downloadToSandbox(
|
|
107
|
+
context: Context,
|
|
108
|
+
url: string,
|
|
109
|
+
fileName: string
|
|
110
|
+
): Promise<string> {
|
|
111
|
+
const savePath = `${context.filesDir}/downloads`
|
|
112
|
+
ensureDir(savePath)
|
|
113
|
+
|
|
114
|
+
const filePath = `${savePath}/${fileName}`
|
|
115
|
+
const httpRequest = http.createHttp()
|
|
116
|
+
try {
|
|
117
|
+
const response = await httpRequest.request(url, {
|
|
118
|
+
method: http.RequestMethod.GET,
|
|
119
|
+
expectDataType: http.HttpDataType.ARRAY_BUFFER
|
|
120
|
+
})
|
|
121
|
+
if (response.responseCode === 200) {
|
|
122
|
+
const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY)
|
|
123
|
+
fileIo.writeSync(file.fd, response.result as ArrayBuffer)
|
|
124
|
+
fileIo.closeSync(file.fd)
|
|
125
|
+
}
|
|
126
|
+
} finally {
|
|
127
|
+
httpRequest.destroy()
|
|
128
|
+
}
|
|
129
|
+
return filePath
|
|
130
|
+
}
|
|
131
|
+
```
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# 权限请求与检查
|
|
2
|
+
|
|
3
|
+
> ⚠ 本文样例中 `getContext(this)` 为已废弃写法:组件内一律改 `this.getUIContext().getHostContext()`;Ability 内用 `this.context`。
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
> abilityAccessCtrl 权限管理的完整使用模式,替代 Android 的 ActivityCompat.requestPermissions。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 基本导入
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { abilityAccessCtrl, Permissions, bundleManager, common } from '@kit.AbilityKit'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 完整权限请求封装
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// helpers/PermissionHelper.ets
|
|
22
|
+
export class PermissionHelper {
|
|
23
|
+
/**
|
|
24
|
+
* 检查并请求权限
|
|
25
|
+
* @returns true 表示已获得所有权限
|
|
26
|
+
*/
|
|
27
|
+
static async requestPermissions(
|
|
28
|
+
context: common.UIAbilityContext,
|
|
29
|
+
permissions: Permissions[]
|
|
30
|
+
): Promise<boolean> {
|
|
31
|
+
const atManager = abilityAccessCtrl.createAtManager()
|
|
32
|
+
|
|
33
|
+
// 先检查是否已有权限
|
|
34
|
+
const allGranted = await PermissionHelper.checkPermissions(context, permissions)
|
|
35
|
+
if (allGranted) return true
|
|
36
|
+
|
|
37
|
+
// 请求权限
|
|
38
|
+
const result = await atManager.requestPermissionsFromUser(context, permissions)
|
|
39
|
+
|
|
40
|
+
// 检查结果
|
|
41
|
+
for (let i = 0; i < result.authResults.length; i++) {
|
|
42
|
+
if (result.authResults[i] !== 0) {
|
|
43
|
+
return false // 有权限被拒绝
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 检查权限状态
|
|
51
|
+
*/
|
|
52
|
+
static async checkPermissions(
|
|
53
|
+
context: common.UIAbilityContext,
|
|
54
|
+
permissions: Permissions[]
|
|
55
|
+
): Promise<boolean> {
|
|
56
|
+
const atManager = abilityAccessCtrl.createAtManager()
|
|
57
|
+
const bundleInfo = await bundleManager.getBundleInfoForSelf(
|
|
58
|
+
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
|
|
59
|
+
)
|
|
60
|
+
const tokenId = bundleInfo.appInfo.accessTokenId
|
|
61
|
+
|
|
62
|
+
for (const permission of permissions) {
|
|
63
|
+
const grantStatus = await atManager.checkAccessToken(tokenId, permission)
|
|
64
|
+
if (grantStatus !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 使用示例
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// 在页面中请求权限
|
|
79
|
+
async aboutToAppear(): Promise<void> {
|
|
80
|
+
const context = getContext(this) as common.UIAbilityContext
|
|
81
|
+
const granted = await PermissionHelper.requestPermissions(context, [
|
|
82
|
+
'ohos.permission.CAMERA' as Permissions,
|
|
83
|
+
'ohos.permission.MICROPHONE' as Permissions,
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
if (granted) {
|
|
87
|
+
await this.loadData()
|
|
88
|
+
} else {
|
|
89
|
+
// 提示用户去设置页面开启权限
|
|
90
|
+
this.showPermissionDeniedTip()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 常用权限列表
|
|
98
|
+
|
|
99
|
+
| 权限 | 用途 | 类型 |
|
|
100
|
+
|------|------|------|
|
|
101
|
+
| `ohos.permission.INTERNET` | 网络访问 | system_grant(自动授权)|
|
|
102
|
+
| `ohos.permission.CAMERA` | 相机 | user_grant(需用户同意)|
|
|
103
|
+
| `ohos.permission.MICROPHONE` | 麦克风 | user_grant |
|
|
104
|
+
| `ohos.permission.APPROXIMATELY_LOCATION` | 模糊定位 | user_grant |
|
|
105
|
+
| `ohos.permission.LOCATION` | 精确定位 | user_grant,**须与 APPROXIMATELY_LOCATION 一起申请**,不能单独申请 |
|
|
106
|
+
| `ohos.permission.KEEP_BACKGROUND_RUNNING` | 后台运行 | system_grant |
|
|
107
|
+
| `ohos.permission.READ_IMAGEVIDEO` / `WRITE_IMAGEVIDEO` | 全量读/直写媒体库 | **受限开放**(system_basic,须 ACL)——三方应用改用 PhotoViewPicker / SaveButton(免权限) |
|
|
108
|
+
| `ohos.permission.READ_CONTACTS` / `WRITE_CONTACTS` | 读/写通讯录 | **受限开放**——改用 `contact.selectContacts`(系统选人,免权限) |
|
|
109
|
+
|
|
110
|
+
**system_grant**:在 module.json5 中声明即可,系统自动授权。
|
|
111
|
+
**user_grant**:需要在 module.json5 声明 + 代码运行时请求用户同意。
|
|
112
|
+
**受限开放权限**(READ/WRITE_IMAGEVIDEO、READ/WRITE_CONTACTS、READ/WRITE_AUDIO、SYSTEM_FLOAT_WINDOW、READ_PASTEBOARD 等):normal 应用须 ACL 才能申请,默认拿不到——改走系统 Picker / 安全控件。权限名以 SDK `ets/api/permissions.d.ts` 为准,是否受限以官方"受限开放权限"清单为准。
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# photoAccessHelper 媒体查询指南
|
|
2
|
+
|
|
3
|
+
> HarmonyOS 媒体库访问的完整使用模式,替代 Android 的 MediaStore + ContentProvider。
|
|
4
|
+
|
|
5
|
+
## 权限口径(先定路线再写码)
|
|
6
|
+
|
|
7
|
+
| 路线 | API | 权限 | 适用 |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| 选图 | `new photoAccessHelper.PhotoViewPicker().select(options)` | 无需权限;返回 uri 只读,不在 picker 回调里直接开文件,存下来由按钮等事件再读 | 三方应用默认 |
|
|
10
|
+
| 存图 | `SaveButton` 安全控件 `onClick` 内 `MediaAssetChangeRequest.createImageAssetRequest / createAssetRequest` + `applyChanges` | 无需权限(点击态临时授权) | 三方应用默认 |
|
|
11
|
+
| 存图 | `phAccessHelper.showAssetsCreationDialog(srcUris, configs)` 拿目标 uri 后 `fileIo` 写入 | 无需权限(弹窗授权) | 三方应用默认 |
|
|
12
|
+
| 全量读 | `getAssets` / `getAlbums` | `ohos.permission.READ_IMAGEVIDEO`——**受限开放权限**(system_basic,须 ACL) | 仅拿到 ACL 的应用 |
|
|
13
|
+
| 直写 | `createAsset`、`SaveButton` 之外调用的 `applyChanges` | `ohos.permission.WRITE_IMAGEVIDEO`——受限开放权限 | 仅拿到 ACL 的应用 |
|
|
14
|
+
|
|
15
|
+
Android `MediaStore` 查询默认映射到"选图"路线(用户从系统图库选),不是 `getAssets`;相册写入映射到 `SaveButton` 路线。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 选图(PhotoViewPicker,无需权限)
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { photoAccessHelper } from '@kit.MediaLibraryKit'
|
|
23
|
+
import { BusinessError } from '@kit.BasicServicesKit'
|
|
24
|
+
|
|
25
|
+
@State pickedUris: string[] = []
|
|
26
|
+
|
|
27
|
+
async pickImages(): Promise<void> {
|
|
28
|
+
const options = new photoAccessHelper.PhotoSelectOptions()
|
|
29
|
+
options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
|
|
30
|
+
options.maxSelectNumber = 9
|
|
31
|
+
try {
|
|
32
|
+
const result: photoAccessHelper.PhotoSelectResult = await new photoAccessHelper.PhotoViewPicker().select(options)
|
|
33
|
+
this.pickedUris = result.photoUris // 只读 uri;读文件放到按钮等事件里做,不在此回调里直接 open
|
|
34
|
+
} catch (e) {
|
|
35
|
+
const err = e as BusinessError // 用户取消也走这里:保持原状或给可见提示
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 基本导入与初始化
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { photoAccessHelper } from '@kit.MediaLibraryKit'
|
|
46
|
+
import { dataSharePredicates } from '@kit.ArkData'
|
|
47
|
+
|
|
48
|
+
const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 全量查询媒体资源(getAssets:需 `READ_IMAGEVIDEO`,受限开放——只在拿到 ACL 时用)
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
async function getMediaAssets(context: Context): Promise<photoAccessHelper.PhotoAsset[]> {
|
|
57
|
+
const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context)
|
|
58
|
+
|
|
59
|
+
const fetchOptions: photoAccessHelper.FetchOptions = {
|
|
60
|
+
fetchColumns: [
|
|
61
|
+
photoAccessHelper.PhotoKeys.URI,
|
|
62
|
+
photoAccessHelper.PhotoKeys.DISPLAY_NAME,
|
|
63
|
+
photoAccessHelper.PhotoKeys.SIZE,
|
|
64
|
+
photoAccessHelper.PhotoKeys.DATE_ADDED,
|
|
65
|
+
photoAccessHelper.PhotoKeys.DURATION,
|
|
66
|
+
photoAccessHelper.PhotoKeys.WIDTH,
|
|
67
|
+
photoAccessHelper.PhotoKeys.HEIGHT,
|
|
68
|
+
photoAccessHelper.PhotoKeys.PHOTO_TYPE,
|
|
69
|
+
],
|
|
70
|
+
predicates: new dataSharePredicates.DataSharePredicates()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const fetchResult = await phAccessHelper.getAssets(fetchOptions)
|
|
74
|
+
const count = fetchResult.getCount()
|
|
75
|
+
const assets: photoAccessHelper.PhotoAsset[] = []
|
|
76
|
+
|
|
77
|
+
if (count > 0) {
|
|
78
|
+
let asset = await fetchResult.getFirstObject()
|
|
79
|
+
let index = 0
|
|
80
|
+
while (index < count) {
|
|
81
|
+
assets.push(asset)
|
|
82
|
+
index++
|
|
83
|
+
if (index < count) {
|
|
84
|
+
asset = await fetchResult.getNextObject()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fetchResult.close() // 必须关闭!
|
|
90
|
+
return assets
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## FetchResult 遍历模式(关键)
|
|
97
|
+
|
|
98
|
+
**FetchResult 不是数组**,是游标式迭代器。必须按以下模式遍历:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// ✅ 正确模式
|
|
102
|
+
let asset = await fetchResult.getFirstObject()
|
|
103
|
+
let index = 0
|
|
104
|
+
while (index < count) {
|
|
105
|
+
// 处理 asset
|
|
106
|
+
processAsset(asset)
|
|
107
|
+
index++
|
|
108
|
+
if (index < count) {
|
|
109
|
+
asset = await fetchResult.getNextObject()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
fetchResult.close() // 必须关闭
|
|
113
|
+
|
|
114
|
+
// ❌ 错误 — 不能用 for...of
|
|
115
|
+
for (const asset of fetchResult) { ... } // 不支持
|
|
116
|
+
|
|
117
|
+
// ❌ 错误 — 不能一次性获取全部
|
|
118
|
+
const allAssets = await fetchResult.getAllObjects() // 大量数据会内存溢出
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 获取文件 URI
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// 获取资源 URI(可用于 Image 组件显示)
|
|
127
|
+
const uri = asset.uri // 格式: file://media/Photo/xxx
|
|
128
|
+
|
|
129
|
+
// ⚠️ 视频 URI 不能传给 Image 组件(会显示灰色空白)
|
|
130
|
+
// 图片 URI 可以直接传给 Image 组件
|
|
131
|
+
Image(asset.uri)
|
|
132
|
+
.sourceSize({ width: 256, height: 256 }) // 缩略图优化
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 按类型筛选
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// 只查图片
|
|
141
|
+
const predicates = new dataSharePredicates.DataSharePredicates()
|
|
142
|
+
predicates.equalTo(photoAccessHelper.PhotoKeys.PHOTO_TYPE, photoAccessHelper.PhotoType.IMAGE)
|
|
143
|
+
|
|
144
|
+
// 只查视频
|
|
145
|
+
predicates.equalTo(photoAccessHelper.PhotoKeys.PHOTO_TYPE, photoAccessHelper.PhotoType.VIDEO)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 保存图片到系统相册(MediaAssetChangeRequest 三步)
|
|
151
|
+
|
|
152
|
+
把 app 生成/下载的图片写入系统相册,**走资产变更请求范式**,不要臆造一步式 `saveImage`,也**不要用 `phAccessHelper.createAsset(...)` + `fileIo.open(uri)` + `copyFile` 直写文件**(`createAsset` 需受限权限 `WRITE_IMAGEVIDEO`)。
|
|
153
|
+
`applyChanges` 免权限的前提是**在 `SaveButton` 安全控件的 `onClick` 里调用**(点击态临时授权);在别处调用同样需要 `WRITE_IMAGEVIDEO`。
|
|
154
|
+
|
|
155
|
+
三步(编译校验过):
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { photoAccessHelper } from '@kit.MediaLibraryKit'
|
|
159
|
+
import { common } from '@kit.AbilityKit'
|
|
160
|
+
|
|
161
|
+
// srcFileUri:沙箱内源图片的 file:// URI(沙箱路径可用 fileUri.getUriFromPath 转换)
|
|
162
|
+
async function saveImageToAlbum(context: common.UIAbilityContext, srcFileUri: string): Promise<void> {
|
|
163
|
+
const phAccessHelper: photoAccessHelper.PhotoAccessHelper =
|
|
164
|
+
photoAccessHelper.getPhotoAccessHelper(context)
|
|
165
|
+
|
|
166
|
+
// 1) 创建图片资产变更请求(photoType + 扩展名;options 可选)
|
|
167
|
+
const req: photoAccessHelper.MediaAssetChangeRequest =
|
|
168
|
+
photoAccessHelper.MediaAssetChangeRequest.createAssetRequest(
|
|
169
|
+
context, photoAccessHelper.PhotoType.IMAGE, 'jpg'
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
// 2) 注入图片数据:源文件 URI(string)或 ArrayBuffer 二选一
|
|
173
|
+
req.addResource(photoAccessHelper.ResourceType.IMAGE_RESOURCE, srcFileUri)
|
|
174
|
+
|
|
175
|
+
// 3) 提交生效(异步,必须 await / then)
|
|
176
|
+
await phAccessHelper.applyChanges(req)
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
触发点必须是 `SaveButton`(安全控件)的 `onClick`——点击态临时授权,免 `WRITE_IMAGEVIDEO`:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
SaveButton({ icon: SaveIconStyle.FULL_FILLED, text: SaveDescription.SAVE_IMAGE, buttonType: ButtonType.Capsule })
|
|
184
|
+
.onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => {
|
|
185
|
+
if (result === SaveButtonOnClickResult.SUCCESS) {
|
|
186
|
+
await saveImageToAlbum(this.getUIContext().getHostContext() as common.UIAbilityContext, srcFileUri)
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
关键签名与要点:
|
|
192
|
+
|
|
193
|
+
| 项 | 内容 |
|
|
194
|
+
|----|------|
|
|
195
|
+
| 创建请求 | `MediaAssetChangeRequest.createAssetRequest(context: Context, photoType: PhotoType, extension: string, options?: CreateOptions): MediaAssetChangeRequest` |
|
|
196
|
+
| 注入数据 | `req.addResource(type: ResourceType, fileUri: string \| ArrayBuffer): void`,类型用 `photoAccessHelper.ResourceType.IMAGE_RESOURCE` |
|
|
197
|
+
| 提交 | `await phAccessHelper.applyChanges(req)`(返回 Promise) |
|
|
198
|
+
| 权限 | `SaveButton.onClick` 内调用免权限;其它位置需 `ohos.permission.WRITE_IMAGEVIDEO`(受限开放,须 ACL) |
|
|
199
|
+
|
|
200
|
+
> `addResource` 传源文件 URI 时,须确保该 URI 对应的资源实际存在。已有现成图片文件时也可用 `MediaAssetChangeRequest.createImageAssetRequest(context, fileUri)` 一步建请求再 `applyChanges`。
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 权限要求
|
|
205
|
+
|
|
206
|
+
三方应用默认路线(PhotoViewPicker / SaveButton / showAssetsCreationDialog)**不声明任何媒体权限**。
|
|
207
|
+
只有拿到 ACL 的应用才声明 `ohos.permission.READ_IMAGEVIDEO` / `WRITE_IMAGEVIDEO`(受限开放权限,system_basic;普通应用声明后签名即失败)。
|
|
208
|
+
写相册不能靠只读权限:`READ_IMAGEVIDEO` 不覆盖写入。
|