Reorganize skill files into skills/ subdirectory

This commit is contained in:
cheliangzhao
2026-02-10 16:59:56 +08:00
parent 39b7772d2e
commit 2378b310a5
12 changed files with 1743 additions and 1617 deletions

View File

@@ -0,0 +1,292 @@
---
name: arkts-development
description: HarmonyOS ArkTS application development with ArkUI declarative UI framework. Use when building HarmonyOS/OpenHarmony apps, creating ArkUI components, implementing state management with decorators (@State, @Prop, @Link), migrating from TypeScript to ArkTS, or working with HarmonyOS-specific APIs (router, http, preferences). Covers component lifecycle, layout patterns, and ArkTS language constraints.
---
# ArkTS Development
Build HarmonyOS applications using ArkTS and the ArkUI declarative UI framework.
## Quick Start
Create a basic component:
```typescript
@Entry
@Component
struct HelloWorld {
@State message: string = 'Hello, ArkTS!';
build() {
Column() {
Text(this.message)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('Click Me')
.onClick(() => { this.message = 'Button Clicked!'; })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
```
## State Management Decorators
| Decorator | Usage | Description |
|-----------|-------|-------------|
| `@State` | `@State count: number = 0` | Component internal state |
| `@Prop` | `@Prop title: string` | Parent → Child (one-way) |
| `@Link` | `@Link value: number` | Parent ↔ Child (two-way, use `$varName`) |
| `@Provide/@Consume` | Cross-level | Ancestor → Descendant |
| `@Observed/@ObjectLink` | Nested objects | Deep object observation |
## Common Layouts
```typescript
// Vertical
Column({ space: 10 }) { Text('A'); Text('B'); }
.alignItems(HorizontalAlign.Center)
// Horizontal
Row({ space: 10 }) { Text('A'); Text('B'); }
.justifyContent(FlexAlign.SpaceBetween)
// Stack (overlay)
Stack({ alignContent: Alignment.Center }) {
Image($r('app.media.bg'))
Text('Overlay')
}
// List with ForEach
List({ space: 10 }) {
ForEach(this.items, (item: string) => {
ListItem() { Text(item) }
}, (item: string) => item)
}
```
## Component Lifecycle
```typescript
@Entry
@Component
struct Page {
aboutToAppear() { /* Init data */ }
onPageShow() { /* Page visible */ }
onPageHide() { /* Page hidden */ }
aboutToDisappear() { /* Cleanup */ }
build() { Column() { Text('Page') } }
}
```
## Navigation
```typescript
import { router } from '@kit.ArkUI';
// Push
router.pushUrl({ url: 'pages/Detail', params: { id: 123 } });
// Replace
router.replaceUrl({ url: 'pages/New' });
// Back
router.back();
// Get params
const params = router.getParams() as Record<string, Object>;
```
## Network Request
```typescript
import { http } from '@kit.NetworkKit';
const req = http.createHttp();
const res = await req.request('https://api.example.com/data', {
method: http.RequestMethod.GET,
header: { 'Content-Type': 'application/json' }
});
if (res.responseCode === 200) {
const data = JSON.parse(res.result as string);
}
req.destroy();
```
## Local Storage
```typescript
import { preferences } from '@kit.ArkData';
const prefs = await preferences.getPreferences(this.context, 'store');
await prefs.put('key', 'value');
await prefs.flush();
const val = await prefs.get('key', 'default');
```
## ArkTS Language Constraints
ArkTS enforces stricter rules than TypeScript for performance and safety:
| Prohibited | Use Instead |
|------------|-------------|
| `any`, `unknown` | Explicit types, interfaces |
| `var` | `let`, `const` |
| Dynamic property access `obj['key']` | Fixed object structure |
| `for...in`, `delete`, `with` | `for...of`, array methods |
| `#privateField` | `private` keyword |
| Structural typing | Explicit `implements`/`extends` |
See [references/migration-guide.md](references/migration-guide.md) for complete TypeScript → ArkTS migration details.
## Command Line Build (hvigorw)
hvigorw is the Hvigor wrapper tool for command-line builds.
```bash
# Common build tasks
hvigorw clean # Clean build directory
hvigorw assembleHap -p buildMode=debug # Build Hap (debug)
hvigorw assembleApp -p buildMode=release # Build App (release)
hvigorw assembleHar # Build Har library
hvigorw assembleHsp # Build Hsp
# Build specific module
hvigorw assembleHap -p module=entry@default --mode module
# Run tests
hvigorw onDeviceTest -p module=entry -p coverage=true
hvigorw test -p module=entry # Local test
# CI/CD recommended
hvigorw assembleApp -p buildMode=release --no-daemon
```
Common parameters:
| Parameter | Description |
|-----------|-------------|
| `-p buildMode={debug\|release}` | Build mode |
| `-p product={name}` | Target product (default: default) |
| `-p module={name}@{target}` | Target module (with `--mode module`) |
| `--no-daemon` | Disable daemon (recommended for CI) |
| `--analyze=advanced` | Enable build analysis |
| `--optimization-strategy=memory` | Memory-optimized build |
See [references/hvigor-commandline.md](references/hvigor-commandline.md) for complete command reference.
## Code Linter (codelinter)
codelinter is the code checking and fixing tool for ArkTS/TS files.
```bash
# Basic usage
codelinter # Check current project
codelinter /path/to/project # Check specified project
codelinter -c ./code-linter.json5 # Use custom rules
# Check and auto-fix
codelinter --fix
codelinter -c ./code-linter.json5 --fix
# Output formats
codelinter -f json -o ./report.json # JSON report
codelinter -f html -o ./report.html # HTML report
# Incremental check (Git changes only)
codelinter -i
# CI/CD with exit codes
codelinter --exit-on error,warn # Non-zero exit on error/warn
```
| Parameter | Description |
|-----------|-------------|
| `-c, --config <file>` | Specify rules config file |
| `--fix` | Auto-fix supported issues |
| `-f, --format` | Output format: default/json/xml/html |
| `-o, --output <file>` | Save result to file |
| `-i, --incremental` | Check only Git changed files |
| `-p, --product <name>` | Specify product |
| `-e, --exit-on <levels>` | Exit code levels: error,warn,suggestion |
See [references/codelinter.md](references/codelinter.md) for complete reference.
## Stack Trace Parser (hstack)
hstack parses obfuscated crash stacks from Release builds back to source code locations.
```bash
# Parse crash files directory
hstack -i crashDir -o outputDir -s sourcemapDir -n nameCacheDir
# Parse with C++ symbols
hstack -i crashDir -o outputDir -s sourcemapDir --so soDir -n nameCacheDir
# Parse single crash stack
hstack -c "at func (entry|entry|1.0.0|src/main/ets/pages/Index.ts:58:58)" -s sourcemapDir
```
| Parameter | Description |
|-----------|-------------|
| `-i, --input` | Crash files directory |
| `-c, --crash` | Single crash stack string |
| `-o, --output` | Output directory (or file with `-c`) |
| `-s, --sourcemapDir` | Sourcemap files directory |
| `--so, --soDir` | Shared object (.so) files directory |
| `-n, --nameObfuscation` | NameCache files directory |
Requirements:
- Must provide either `-i` or `-c` (not both)
- Must provide at least `-s` or `--so`
- For method name restoration, provide both `-s` and `-n`
See [references/hstack.md](references/hstack.md) for complete reference.
## Code Obfuscation (ArkGuard)
Enable in `build-profile.json5`:
```json
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": ["./obfuscation-rules.txt"]
}
}
}
```
Common rules in `obfuscation-rules.txt`:
```text
-enable-property-obfuscation # Property name obfuscation
-enable-toplevel-obfuscation # Top-level scope obfuscation
-enable-filename-obfuscation # Filename obfuscation
-keep-property-name apiKey # Whitelist specific names
```
See [references/arkguard-obfuscation.md](references/arkguard-obfuscation.md) for complete guide.
## Reference Files
- **Migration Guide**: [references/migration-guide.md](references/migration-guide.md) - Complete TypeScript to ArkTS migration rules and examples
- **Component Patterns**: [references/component-patterns.md](references/component-patterns.md) - Advanced component patterns and best practices
- **API Reference**: [references/api-reference.md](references/api-reference.md) - Common HarmonyOS APIs
- **ArkGuard Obfuscation**: [references/arkguard-obfuscation.md](references/arkguard-obfuscation.md) - Code obfuscation configuration and troubleshooting
- **Hvigor Command Line**: [references/hvigor-commandline.md](references/hvigor-commandline.md) - Complete hvigorw build tool reference
- **CodeLinter**: [references/codelinter.md](references/codelinter.md) - Code checking and fixing tool
- **Hstack**: [references/hstack.md](references/hstack.md) - Crash stack trace parser for Release builds
## Development Environment
- **IDE**: DevEco Studio
- **SDK**: HarmonyOS SDK
- **Simulator**: Built-in DevEco Studio emulator
## Related Skills
- **Build & Deploy**: See `harmonyos-build-deploy` skill for building, packaging, and device installation

View File

@@ -0,0 +1,57 @@
// ArkTS Basic Component Template
// Replace {{ComponentName}} with your component name
@Entry
@Component
struct {{ComponentName}} {
// State management
@State isLoading: boolean = false;
@State errorMessage: string = '';
// Lifecycle
aboutToAppear(): void {
this.loadData();
}
aboutToDisappear(): void {
// Cleanup resources
}
// Methods
async loadData(): Promise<void> {
this.isLoading = true;
try {
// Load data here
} catch (error) {
this.errorMessage = 'Failed to load data';
} finally {
this.isLoading = false;
}
}
// Build
build() {
Column() {
if (this.isLoading) {
LoadingProgress()
.width(50)
.height(50)
} else if (this.errorMessage) {
Text(this.errorMessage)
.fontSize(16)
.fontColor(Color.Red)
Button('Retry')
.onClick(() => { this.loadData(); })
} else {
// Main content here
Text('{{ComponentName}}')
.fontSize(24)
.fontWeight(FontWeight.Bold)
}
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}

View File

@@ -0,0 +1,129 @@
// ArkTS List Page Template
// Replace {{PageName}}, {{ItemType}}, {{itemName}} with your values
import { router } from '@kit.ArkUI';
interface {{ItemType}} {
id: string;
title: string;
description: string;
}
@Entry
@Component
struct {{PageName}} {
@State items: {{ItemType}}[] = [];
@State isLoading: boolean = false;
@State isRefreshing: boolean = false;
aboutToAppear(): void {
this.loadItems();
}
async loadItems(): Promise<void> {
this.isLoading = true;
try {
// TODO: Fetch items from API
this.items = [
{ id: '1', title: 'Item 1', description: 'Description 1' },
{ id: '2', title: 'Item 2', description: 'Description 2' },
];
} finally {
this.isLoading = false;
}
}
async refreshItems(): Promise<void> {
this.isRefreshing = true;
await this.loadItems();
this.isRefreshing = false;
}
navigateToDetail(item: {{ItemType}}): void {
router.pushUrl({
url: 'pages/{{PageName}}Detail',
params: { id: item.id }
});
}
@Builder
ItemCard(item: {{ItemType}}) {
Column() {
Text(item.title)
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
Text(item.description)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.padding(16)
.width('100%')
.backgroundColor(Color.White)
.borderRadius(8)
.onClick(() => { this.navigateToDetail(item); })
}
build() {
Column() {
// Header
Row() {
Text('{{PageName}}')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Blank()
Button('Add')
.onClick(() => {
router.pushUrl({ url: 'pages/{{PageName}}Create' });
})
}
.width('100%')
.padding(16)
// Content
if (this.isLoading && this.items.length === 0) {
Column() {
LoadingProgress().width(50).height(50)
Text('Loading...').margin({ top: 16 })
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
} else if (this.items.length === 0) {
Column() {
Text('No items found')
.fontSize(16)
.fontColor('#999999')
Button('Refresh')
.margin({ top: 16 })
.onClick(() => { this.loadItems(); })
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
} else {
Refresh({ refreshing: $$this.isRefreshing }) {
List({ space: 12 }) {
ForEach(this.items, (item: {{ItemType}}) => {
ListItem() {
this.ItemCard(item)
}
}, (item: {{ItemType}}) => item.id)
}
.width('100%')
.padding({ left: 16, right: 16 })
}
.onRefreshing(() => { this.refreshItems(); })
.layoutWeight(1)
}
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
}
}

View File

@@ -0,0 +1,503 @@
# HarmonyOS API Reference
Common HarmonyOS APIs for ArkTS development.
## Table of Contents
1. [Router Navigation](#router-navigation)
2. [HTTP Networking](#http-networking)
3. [Preferences Storage](#preferences-storage)
4. [File Operations](#file-operations)
5. [Device Info](#device-info)
6. [Prompt & Dialog](#prompt--dialog)
7. [Media](#media)
---
## Router Navigation
```typescript
import { router } from '@kit.ArkUI';
```
### Push Page
```typescript
router.pushUrl({
url: 'pages/DetailPage',
params: {
id: 123,
title: 'Detail'
}
});
```
### Push with Mode
```typescript
// Standard mode (default) - adds to stack
router.pushUrl({
url: 'pages/Page',
}, router.RouterMode.Standard);
// Single mode - reuses if exists
router.pushUrl({
url: 'pages/Page',
}, router.RouterMode.Single);
```
### Replace Page
```typescript
router.replaceUrl({
url: 'pages/NewPage'
});
```
### Back Navigation
```typescript
// Back to previous
router.back();
// Back to specific page
router.back({
url: 'pages/HomePage'
});
// Back with result
router.back({
url: 'pages/HomePage',
params: { result: 'success' }
});
```
### Get Parameters
```typescript
// In target page
aboutToAppear(): void {
const params = router.getParams() as Record<string, Object>;
if (params) {
const id = params['id'] as number;
const title = params['title'] as string;
}
}
```
### Get Router State
```typescript
const state = router.getState();
console.log('Current page:', state.name);
console.log('Page path:', state.path);
console.log('Stack index:', state.index);
```
### Clear Router Stack
```typescript
router.clear();
```
---
## HTTP Networking
```typescript
import { http } from '@kit.NetworkKit';
```
### GET Request
```typescript
async function getData(): Promise<void> {
const httpRequest = http.createHttp();
try {
const response = await httpRequest.request(
'https://api.example.com/data',
{
method: http.RequestMethod.GET,
header: {
'Content-Type': 'application/json'
},
connectTimeout: 60000,
readTimeout: 60000
}
);
if (response.responseCode === 200) {
const data = JSON.parse(response.result as string);
console.log('Data:', JSON.stringify(data));
}
} catch (error) {
console.error('Request failed:', error);
} finally {
httpRequest.destroy();
}
}
```
### POST Request
```typescript
async function postData(body: object): Promise<void> {
const httpRequest = http.createHttp();
try {
const response = await httpRequest.request(
'https://api.example.com/submit',
{
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
extraData: JSON.stringify(body)
}
);
console.log('Response code:', response.responseCode);
} finally {
httpRequest.destroy();
}
}
```
### Request Options
```typescript
interface HttpRequestOptions {
method: http.RequestMethod; // GET, POST, PUT, DELETE, etc.
header?: Object; // Request headers
extraData?: string | Object; // Request body
connectTimeout?: number; // Connection timeout (ms)
readTimeout?: number; // Read timeout (ms)
expectDataType?: http.HttpDataType;
}
```
---
## Preferences Storage
```typescript
import { preferences } from '@kit.ArkData';
```
### Get Preferences Instance
```typescript
// In component with context
const dataPreferences = await preferences.getPreferences(
this.context,
'myPreferencesStore'
);
```
### Write Data
```typescript
await dataPreferences.put('username', 'John');
await dataPreferences.put('age', 25);
await dataPreferences.put('isVip', true);
await dataPreferences.put('scores', [90, 85, 92]);
await dataPreferences.flush(); // Persist to disk
```
### Read Data
```typescript
// With default values
const username = await dataPreferences.get('username', '') as string;
const age = await dataPreferences.get('age', 0) as number;
const isVip = await dataPreferences.get('isVip', false) as boolean;
```
### Check Key Exists
```typescript
const hasKey = await dataPreferences.has('username');
```
### Delete Data
```typescript
await dataPreferences.delete('username');
await dataPreferences.flush();
```
### Clear All
```typescript
await dataPreferences.clear();
await dataPreferences.flush();
```
### Delete Preferences File
```typescript
await preferences.deletePreferences(this.context, 'myPreferencesStore');
```
---
## File Operations
```typescript
import { fileIo as fs } from '@kit.CoreFileKit';
```
### Get Application Paths
```typescript
// In AbilityStage or UIAbility
const filesDir = this.context.filesDir; // /data/app/.../files
const cacheDir = this.context.cacheDir; // /data/app/.../cache
const tempDir = this.context.tempDir; // /data/app/.../temp
```
### Write File
```typescript
const filePath = `${this.context.filesDir}/data.txt`;
const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);
fs.writeSync(file.fd, 'Hello, HarmonyOS!');
fs.closeSync(file);
```
### Read File
```typescript
const filePath = `${this.context.filesDir}/data.txt`;
const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
const buffer = new ArrayBuffer(4096);
const readLen = fs.readSync(file.fd, buffer);
const content = String.fromCharCode(...new Uint8Array(buffer.slice(0, readLen)));
fs.closeSync(file);
```
### Check File Exists
```typescript
const exists = fs.accessSync(filePath);
```
### Delete File
```typescript
fs.unlinkSync(filePath);
```
### List Directory
```typescript
const files = fs.listFileSync(this.context.filesDir);
files.forEach((file: string) => {
console.log('File:', file);
});
```
---
## Device Info
```typescript
import { deviceInfo } from '@kit.BasicServicesKit';
```
### Get Device Information
```typescript
const brand = deviceInfo.brand; // e.g., "HUAWEI"
const model = deviceInfo.productModel; // e.g., "Mate 60"
const osVersion = deviceInfo.osFullName; // e.g., "HarmonyOS 5.0"
const sdkVersion = deviceInfo.sdkApiVersion; // e.g., 12
const deviceType = deviceInfo.deviceType; // e.g., "phone", "tablet"
```
---
## Prompt & Dialog
```typescript
import { promptAction } from '@kit.ArkUI';
```
### Toast
```typescript
promptAction.showToast({
message: 'Operation successful',
duration: 2000,
bottom: 80
});
```
### Alert Dialog
```typescript
promptAction.showDialog({
title: 'Confirm',
message: 'Are you sure you want to delete?',
buttons: [
{ text: 'Cancel', color: '#999999' },
{ text: 'Delete', color: '#FF0000' }
]
}).then((result) => {
if (result.index === 1) {
// Delete confirmed
}
});
```
### Action Sheet
```typescript
promptAction.showActionMenu({
title: 'Select Option',
buttons: [
{ text: 'Camera', color: '#000000' },
{ text: 'Gallery', color: '#000000' },
{ text: 'Cancel', color: '#999999' }
]
}).then((result) => {
switch (result.index) {
case 0: // Camera
break;
case 1: // Gallery
break;
}
});
```
### Custom Dialog
```typescript
@CustomDialog
struct ConfirmDialog {
controller: CustomDialogController;
title: string = '';
onConfirm: () => void = () => {};
build() {
Column() {
Text(this.title).fontSize(20).margin({ bottom: 20 })
Row({ space: 20 }) {
Button('Cancel')
.onClick(() => { this.controller.close(); })
Button('Confirm')
.onClick(() => {
this.onConfirm();
this.controller.close();
})
}
}
.padding(20)
}
}
// Usage in component
@Entry
@Component
struct DialogExample {
dialogController: CustomDialogController = new CustomDialogController({
builder: ConfirmDialog({
title: 'Delete Item?',
onConfirm: () => { this.handleDelete(); }
}),
autoCancel: true
});
handleDelete(): void {
// Delete logic
}
build() {
Button('Show Dialog')
.onClick(() => { this.dialogController.open(); })
}
}
```
---
## Media
### Image Picker
```typescript
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { picker } from '@kit.CoreFileKit';
async function pickImage(): Promise<string | null> {
const photoPicker = new picker.PhotoViewPicker();
try {
const result = await photoPicker.select({
MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 1
});
if (result.photoUris.length > 0) {
return result.photoUris[0];
}
} catch (error) {
console.error('Pick image failed:', error);
}
return null;
}
```
### Camera Capture
```typescript
import { camera } from '@kit.CameraKit';
// Request camera permission first
// Then use camera APIs for capture
```
### Audio Playback
```typescript
import { media } from '@kit.MediaKit';
async function playAudio(uri: string): Promise<void> {
const player = await media.createAVPlayer();
player.on('stateChange', (state: string) => {
if (state === 'prepared') {
player.play();
}
});
player.url = uri;
}
```
---
## Common Import Patterns
```typescript
// UI Kit
import { router, promptAction } from '@kit.ArkUI';
// Network Kit
import { http } from '@kit.NetworkKit';
// Data Kit
import { preferences } from '@kit.ArkData';
// File Kit
import { fileIo as fs, picker } from '@kit.CoreFileKit';
// Basic Services
import { deviceInfo } from '@kit.BasicServicesKit';
// Media Kit
import { media } from '@kit.MediaKit';
```

View File

@@ -0,0 +1,126 @@
# ArkGuard 代码混淆指南
ArkGuard 是 HarmonyOS 官方推荐的代码混淆工具,用于提升应用安全性,防止逆向分析。
## 环境要求
- **DevEco Studio**: 5.0.3.600 及以上版本
- **项目模型**: 仅支持 Stage 模型
- **生效模式**: 仅在 Release 模式下生效
## 开启混淆
在模块的 `build-profile.json5` 中配置:
```json
{
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": ["./obfuscation-rules.txt"]
},
"consumerFiles": ["./consumer-rules.txt"]
}
}
}
```
## 混淆规则配置
在项目根目录创建 `obfuscation-rules.txt`
```text
# 开启属性混淆
-enable-property-obfuscation
# 开启顶层作用域名称混淆
-enable-toplevel-obfuscation
# 开启文件名混淆
-enable-filename-obfuscation
# 开启导入导出名称混淆
-enable-export-obfuscation
```
## 白名单配置
某些名称不能混淆如动态属性名、API 字段、数据库字段等):
```text
# 保留属性名
-keep-property-name apiKey
-keep-property-name userId
-keep-property-name responseData
# 保留全局名称
-keep-global-name AppConfig
# 保留文件名
-keep-file-name MainPage
-keep-file-name LoginPage
```
## 配置文件说明
| 配置文件 | 作用 | 可修改 | 影响范围 |
|---------|------|:------:|---------|
| `obfuscation-rules.txt` | 本模块编译时的混淆规则 | ✓ | 本模块 |
| `consumer-rules.txt` | 本模块被依赖时的混淆规则(建议仅配置保留项) | ✓ | 依赖此模块的模块 |
| `obfuscation.txt` | HAR/HSP 构建产物,自动生成 | ✗ | 依赖模块 |
## 常用混淆选项
| 选项 | 说明 |
|------|------|
| `-enable-property-obfuscation` | 混淆对象属性名 |
| `-enable-toplevel-obfuscation` | 混淆顶层作用域的变量和函数名 |
| `-enable-filename-obfuscation` | 混淆文件名 |
| `-enable-export-obfuscation` | 混淆导入导出的名称 |
| `-disable-obfuscation` | 临时禁用混淆(用于调试) |
## 白名单选项
| 选项 | 说明 |
|------|------|
| `-keep-property-name <name>` | 保留指定属性名不被混淆 |
| `-keep-global-name <name>` | 保留指定全局名称不被混淆 |
| `-keep-file-name <name>` | 保留指定文件名不被混淆 |
## 问题排查
### 排查步骤
1. **确认是否与混淆相关**: 临时添加 `-disable-obfuscation` 禁用混淆,验证问题是否消失
2. **定位问题字段**: 根据崩溃日志定位被混淆的关键字段
3. **添加白名单**: 将问题字段加入 `-keep-property-name` 白名单
### 常见需要保留的场景
- **网络请求**: 接口传参字段名、响应数据字段名
- **数据库操作**: 表字段名
- **系统 API**: 系统回调参数
- **三方库接口**: 三方库要求的字段名
### 示例:网络请求字段保留
```text
# API 请求/响应字段
-keep-property-name code
-keep-property-name message
-keep-property-name data
-keep-property-name token
-keep-property-name userId
```
## 验证混淆效果
1. 切换到 **Release** 模式编译
2. 检查构建产物
3. 使用反编译工具验证类名/方法名/属性名是否已混淆
4. 测试应用功能是否正常
## 参考
- [华为官方文档 - ArkGuard](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-arkguard)

View File

@@ -0,0 +1,160 @@
# CodeLinter 代码检查工具
codelinter 是 HarmonyOS 的代码检查与修复工具,可集成到门禁或 CI/CD 环境中。
## 命令格式
```bash
codelinter [options] [dir]
```
- `options`: 可选配置参数
- `dir`: 待检查的工程根目录(可选,默认为当前目录)
## 命令参数
| 参数 | 说明 |
|------|------|
| `--config, -c <filepath>` | 指定规则配置文件 (code-linter.json5) |
| `--fix` | 检查同时执行自动修复 |
| `--format, -f <format>` | 输出格式: `default`/`json`/`xml`/`html` |
| `--output, -o <filepath>` | 指定结果保存位置(不在命令行显示) |
| `--version, -v` | 查看版本 |
| `--product, -p <productName>` | 指定生效的 product |
| `--incremental, -i` | 仅检查 Git 增量文件(新增/修改/重命名) |
| `--help, -h` | 查询帮助 |
| `--exit-on, -e <levels>` | 指定返回非零退出码的告警级别 |
## 基本用法
### 在工程根目录下执行
```bash
# 使用默认规则检查当前工程
codelinter
# 指定规则配置文件
codelinter -c ./code-linter.json5
# 检查并自动修复
codelinter -c ./code-linter.json5 --fix
```
### 在非工程目录下执行
```bash
# 检查指定工程目录
codelinter /path/to/project
# 检查多个目录或文件
codelinter dir1 dir2 file1.ets
# 指定规则文件和工程目录
codelinter -c /path/to/code-linter.json5 /path/to/project
# 检查并修复指定工程
codelinter -c ./code-linter.json5 /path/to/project --fix
```
## 输出格式
```bash
# 默认文本格式输出到命令行
codelinter /path/to/project
# JSON 格式输出
codelinter /path/to/project -f json
# HTML 格式保存到文件
codelinter /path/to/project -f html -o ./report.html
# XML 格式保存到文件
codelinter /path/to/project -f xml -o ./report.xml
```
## 增量检查
对 Git 工程中的增量文件执行检查(仅检查新增、修改、重命名的文件):
```bash
codelinter -i
codelinter --incremental
```
## 指定 Product
当工程存在多个 product 时,指定生效的 product
```bash
codelinter -p free /path/to/project
codelinter --product default
```
## 退出码 (--exit-on)
用于 CI/CD 中根据告警级别控制流程。告警级别:`error``warn``suggestion`
退出码计算方式3位二进制数从高到低表示 error, warn, suggestion
| 配置 | 检查结果包含 | 二进制 | 退出码 |
|------|-------------|--------|--------|
| `--exit-on error` | error, warn, suggestion | 100 | 4 |
| `--exit-on error` | warn, suggestion | 000 | 0 |
| `--exit-on error,warn` | error, warn | 110 | 6 |
| `--exit-on error,warn,suggestion` | error | 100 | 4 |
| `--exit-on error,warn,suggestion` | error, warn, suggestion | 111 | 7 |
```bash
# 仅 error 级别返回非零退出码
codelinter --exit-on error
# error 和 warn 级别返回非零退出码
codelinter --exit-on error,warn
# 所有级别都返回非零退出码
codelinter --exit-on error,warn,suggestion
```
## CI/CD 集成示例
```bash
# 完整的 CI 检查流程
codelinter -c ./code-linter.json5 \
-f json \
-o ./codelinter-report.json \
--exit-on error,warn
# 增量检查(仅检查变更文件)
codelinter -i -c ./code-linter.json5 --exit-on error
# 检查并自动修复,生成 HTML 报告
codelinter -c ./code-linter.json5 \
--fix \
-f html \
-o ./codelinter-report.html
```
## 规则配置文件 (code-linter.json5)
默认规则清单可在检查完成后,根据命令行提示查看生成的 `code-linter.json5` 文件。
示例配置:
```json5
{
"files": [
"**/*.ets",
"**/*.ts"
],
"ignore": [
"**/node_modules/**",
"**/oh_modules/**",
"**/build/**"
],
"ruleSet": ["plugin:@ohos/recommended"],
"rules": {
"@ohos/no-any": "error",
"@ohos/no-console": "warn"
}
}
```

View File

@@ -0,0 +1,521 @@
# ArkUI Component Patterns
Advanced component patterns and best practices for ArkTS development.
## Table of Contents
1. [Component Structure](#component-structure)
2. [State Management Patterns](#state-management-patterns)
3. [Parent-Child Communication](#parent-child-communication)
4. [List Optimization](#list-optimization)
5. [Custom Components](#custom-components)
6. [Conditional Rendering](#conditional-rendering)
---
## Component Structure
### Basic Component
```typescript
@Component
struct MyComponent {
// Private properties
private readonly TAG: string = 'MyComponent';
// State properties
@State isLoading: boolean = false;
// Props from parent
@Prop title: string = '';
// Lifecycle
aboutToAppear(): void {
console.log(this.TAG, 'aboutToAppear');
}
// Build method (required)
build() {
Column() {
Text(this.title)
}
}
}
```
### Entry Component (Page)
```typescript
@Entry
@Component
struct HomePage {
@State currentTab: number = 0;
onPageShow(): void {
// Called when page becomes visible
}
onPageHide(): void {
// Called when page becomes hidden
}
onBackPress(): boolean {
// Return true to prevent default back behavior
return false;
}
build() {
Navigation() {
// Page content
}
.title('Home')
}
}
```
---
## State Management Patterns
### @State - Component Internal State
```typescript
@Component
struct Counter {
@State count: number = 0;
build() {
Column() {
Text(`Count: ${this.count}`)
Button('Increment')
.onClick(() => { this.count++; })
}
}
}
```
### @Prop - One-Way Binding (Parent → Child)
```typescript
// Child component
@Component
struct DisplayCard {
@Prop title: string = '';
@Prop value: number = 0;
build() {
Column() {
Text(this.title).fontSize(16)
Text(`${this.value}`).fontSize(24)
}
}
}
// Parent component
@Entry
@Component
struct Dashboard {
@State temperature: number = 25;
build() {
Column() {
DisplayCard({ title: 'Temperature', value: this.temperature })
Button('Update')
.onClick(() => { this.temperature++; })
}
}
}
```
### @Link - Two-Way Binding
```typescript
// Child component
@Component
struct EditableInput {
@Link inputValue: string;
build() {
TextInput({ text: this.inputValue })
.onChange((value: string) => {
this.inputValue = value;
})
}
}
// Parent component
@Entry
@Component
struct FormPage {
@State username: string = '';
build() {
Column() {
Text(`Username: ${this.username}`)
EditableInput({ inputValue: $username }) // Note: $ prefix
}
}
}
```
### @Provide/@Consume - Cross-Level State
```typescript
// Ancestor component
@Entry
@Component
struct App {
@Provide('theme') theme: string = 'light';
build() {
Column() {
SettingsPage()
Button('Toggle Theme')
.onClick(() => {
this.theme = this.theme === 'light' ? 'dark' : 'light';
})
}
}
}
// Descendant component (any depth)
@Component
struct ThemedCard {
@Consume('theme') theme: string;
build() {
Column() {
Text('Card Content')
.fontColor(this.theme === 'light' ? Color.Black : Color.White)
}
.backgroundColor(this.theme === 'light' ? Color.White : Color.Black)
}
}
```
### @Observed/@ObjectLink - Nested Object Observation
```typescript
// Observable class
@Observed
class Task {
id: number;
title: string;
completed: boolean;
constructor(id: number, title: string) {
this.id = id;
this.title = title;
this.completed = false;
}
}
// Child component with object link
@Component
struct TaskItem {
@ObjectLink task: Task;
build() {
Row() {
Checkbox()
.select(this.task.completed)
.onChange((value: boolean) => {
this.task.completed = value;
})
Text(this.task.title)
.decoration({
type: this.task.completed ? TextDecorationType.LineThrough : TextDecorationType.None
})
}
}
}
// Parent component
@Entry
@Component
struct TaskList {
@State tasks: Task[] = [
new Task(1, 'Buy groceries'),
new Task(2, 'Read book')
];
build() {
List() {
ForEach(this.tasks, (task: Task) => {
ListItem() {
TaskItem({ task: task })
}
}, (task: Task) => task.id.toString())
}
}
}
```
### @StorageLink/@StorageProp - AppStorage Binding
```typescript
// Initialize in EntryAbility
AppStorage.setOrCreate('userToken', '');
AppStorage.setOrCreate('isLoggedIn', false);
// Component with storage binding
@Entry
@Component
struct ProfilePage {
@StorageLink('userToken') token: string = ''; // Two-way
@StorageProp('isLoggedIn') isLoggedIn: boolean = false; // One-way
build() {
Column() {
if (this.isLoggedIn) {
Text('Welcome!')
Button('Logout')
.onClick(() => {
this.token = '';
AppStorage.set('isLoggedIn', false);
})
} else {
Text('Please login')
}
}
}
}
```
---
## Parent-Child Communication
### Events via Callback
```typescript
// Child component
@Component
struct SearchBar {
private onSearch: (query: string) => void = () => {};
@State query: string = '';
build() {
Row() {
TextInput({ placeholder: 'Search...' })
.onChange((value: string) => { this.query = value; })
Button('Search')
.onClick(() => { this.onSearch(this.query); })
}
}
}
// Parent component
@Entry
@Component
struct SearchPage {
@State results: string[] = [];
handleSearch(query: string): void {
// Perform search
this.results = [`Result for: ${query}`];
}
build() {
Column() {
SearchBar({ onSearch: (q: string) => this.handleSearch(q) })
ForEach(this.results, (item: string) => {
Text(item)
})
}
}
}
```
---
## List Optimization
### LazyForEach for Large Lists
```typescript
// Data source implementing IDataSource
class MyDataSource implements IDataSource {
private data: string[] = [];
private listeners: DataChangeListener[] = [];
constructor(data: string[]) {
this.data = data;
}
totalCount(): number {
return this.data.length;
}
getData(index: number): string {
return this.data[index];
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listeners.push(listener);
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const idx = this.listeners.indexOf(listener);
if (idx >= 0) {
this.listeners.splice(idx, 1);
}
}
}
// Component with LazyForEach
@Entry
@Component
struct LargeList {
private dataSource: MyDataSource = new MyDataSource(
Array.from({ length: 10000 }, (_, i) => `Item ${i}`)
);
build() {
List() {
LazyForEach(this.dataSource, (item: string, index: number) => {
ListItem() {
Text(item).fontSize(16).padding(10)
}
}, (item: string) => item)
}
.cachedCount(5) // Number of items to cache
}
}
```
### ForEach Key Function
```typescript
// Always provide a unique key function
ForEach(this.items, (item: Item) => {
ListItem() { ItemCard({ item: item }) }
}, (item: Item) => item.id.toString()) // Unique key
```
---
## Custom Components
### Builder Pattern
```typescript
@Entry
@Component
struct BuilderExample {
@Builder
CardBuilder(title: string, content: string) {
Column() {
Text(title).fontSize(20).fontWeight(FontWeight.Bold)
Text(content).fontSize(14)
}
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
}
build() {
Column({ space: 16 }) {
this.CardBuilder('Card 1', 'Content 1')
this.CardBuilder('Card 2', 'Content 2')
}
.padding(16)
}
}
```
### BuilderParam for Slot Pattern
```typescript
@Component
struct Card {
@BuilderParam content: () => void = this.defaultContent;
@Builder
defaultContent() {
Text('Default Content')
}
build() {
Column() {
this.content()
}
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
}
}
// Usage
@Entry
@Component
struct SlotExample {
build() {
Column() {
Card() {
Column() {
Text('Custom Title')
Image($r('app.media.icon'))
}
}
}
}
}
```
---
## Conditional Rendering
### if/else
```typescript
@Component
struct ConditionalExample {
@State isLoggedIn: boolean = false;
build() {
Column() {
if (this.isLoggedIn) {
Text('Welcome back!')
Button('Logout')
} else {
Text('Please login')
Button('Login')
.onClick(() => { this.isLoggedIn = true; })
}
}
}
}
```
### Visibility Control
```typescript
@Component
struct VisibilityExample {
@State showDetails: boolean = false;
build() {
Column() {
Text('Summary')
Text('Detailed information...')
.visibility(this.showDetails ? Visibility.Visible : Visibility.None)
Button(this.showDetails ? 'Hide' : 'Show')
.onClick(() => { this.showDetails = !this.showDetails; })
}
}
}
```
---
## Best Practices
1. **Minimize @State scope** - Keep state as close to where it's used as possible
2. **Use @Prop for read-only data** - Prevents accidental modifications
3. **Prefer @Link for form inputs** - Enables two-way binding
4. **Use LazyForEach for lists > 100 items** - Improves performance
5. **Always provide key functions** - Enables efficient list updates
6. **Use @Builder for reusable UI blocks** - Reduces duplication
7. **Clean up in aboutToDisappear** - Cancel timers, unsubscribe events

View File

@@ -0,0 +1,153 @@
# 堆栈解析工具 (hstack)
hstack 是用于将 Release 应用混淆后的 crash 堆栈解析为源码对应堆栈的工具,支持 Windows、Mac、Linux 三个平台。
## 命令格式
```bash
hstack [options]
```
## 命令参数
| 参数 | 说明 |
|------|------|
| `-i, --input` | 指定 crash 文件归档目录 |
| `-c, --crash` | 指定一条 crash 堆栈 |
| `-o, --output` | 指定解析结果输出目录(使用 `-c` 时指定输出文件) |
| `-s, --sourcemapDir` | 指定 sourcemap 文件归档目录 |
| `--so, --soDir` | 指定 shared object (.so) 文件归档目录 |
| `-n, --nameObfuscation` | 指定 nameCache 文件归档目录 |
| `-v, --version` | 查看版本 |
| `-h, --help` | 查询帮助 |
## 参数约束
- crash 文件目录 (`-i`) 与 crash 堆栈 (`-c`) **必须且只能提供一项**
- sourcemap (`-s`) 与 shared object (`--so`) 目录**至少提供一项**
- 如需还原混淆的方法名,需**同时提供** sourcemap 和 nameCache 文件
- 路径参数不支持特殊字符:`` `~!@#$^&*=|{};,\s\[\]<>? ``
## 环境配置
1. 将 Command Line Tools 的 `bin` 目录配置到 PATH 环境变量
2. 配置 Node.js 到环境变量
3. 解析 C++ 异常需配置 SDK 的 `native\llvm\bin` 目录到环境变量 `ADDR2LINE_PATH`
## 使用示例
### 解析 crash 文件目录
```bash
# 完整解析命令
hstack -i crashDir -o outputDir -s sourcemapDir --so soDir -n nameCacheDir
# 仅使用 sourcemap 解析 (ArkTS)
hstack -i crashDir -o outputDir -s sourcemapDir
# 仅使用 so 文件解析 (C++)
hstack -i crashDir -o outputDir --so soDir
# 包含方法名还原
hstack -i crashDir -o outputDir -s sourcemapDir -n nameCacheDir
```
### 解析单条堆栈
```bash
# 输出到控制台
hstack -c "at har (entry|har|1.0.0|src/main/ets/pages/Index.ts:58:58)" -s sourcemapDir
# 输出到文件
hstack -c "at har (entry|har|1.0.0|src/main/ets/pages/Index.ts:58:58)" -s sourcemapDir -o result.txt
```
## 输出说明
- 解析结果输出到 `-o` 指定目录,文件以原始 crash 文件名加 `_` 前缀命名
- 不指定 `-o` 时:
- 使用 `-i` 输入:输出到 crashDir 目录
- 使用 `-c` 输入:直接输出到控制台
## 文件获取
### Sourcemap 文件
构建产物中的 sourcemap 文件,包含:
- 路径信息映射
- 行列号映射 (mappings 字段)
- package-info 信息
### NameCache 文件
构建产物中的 nameCache 文件,包含:
- `IdentifierCache`: 标识符混淆映射
- `MemberMethodCache`: 成员方法混淆映射,格式为 `"源码方法名:起始行:结束行": "混淆后方法名"`
### Shared Object (.so) 文件
构建 Release 应用时,默认 so 文件不包含符号表。如需生成包含符号表的 so 文件,在模块 `build-profile.json5` 中配置:
```json5
{
"buildOption": {
"externalNativeOptions": {
"arguments": "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
}
}
}
```
## 堆栈解析原理
### Crash 堆栈格式
```
at har (entry|har|1.0.0|src/main/ets/components/mainpage/MainPage.js:58:58)
at i (entry|entry|1.0.0|src/main/ets/pages/Index.ts:71:71)
```
路径格式:`引用方packageName|被引用方packageName|version|源码相对路径`
### 解析步骤
1. **根据路径信息找到 sourcemap**
- 从路径 `entry|har|1.0.0|src/main/ets/...` 在 entry 模块 sourcemap 中查找对应字段
2. **利用 sourcemap 还原路径和行列号**
- 根据 `sources``mappings` 字段解析
- 如包含 `package-info`,可进行二次解析获取更准确的源码位置
3. **利用 nameCache 还原方法名**
- 查找混淆后方法名对应的所有条目
- 根据还原后的行号范围匹配正确的源码方法名
### 解析示例
原始堆栈:
```
at i (entry|entry|1.0.0|src/main/ets/pages/Index.ts:71:71)
```
还原后:
```
at callHarFunction (entry/src/main/ets/pages/Index.ets:25:3)
```
## CI/CD 集成
```bash
# 自动化解析脚本示例
hstack \
-i ./crash-logs \
-o ./parsed-logs \
-s ./build/sourcemap \
--so ./build/libs \
-n ./build/nameCache
```
## 常见问题
1. **方法名未还原**: 确保同时提供 `-s``-n` 参数
2. **C++ 堆栈未解析**: 检查 `ADDR2LINE_PATH` 环境变量配置
3. **so 文件无符号表**: 配置 `RelWithDebInfo` 构建选项

View File

@@ -0,0 +1,179 @@
# Hvigor 命令行构建工具 (hvigorw)
hvigorw 是 Hvigor 的 wrapper 包装工具,支持自动安装 Hvigor 构建工具和相关插件依赖,以及执行 Hvigor 构建命令。
## 命令格式
```bash
hvigorw [taskNames...] <options>
```
## 编译构建任务
| 任务 | 说明 |
|------|------|
| `clean` | 清理构建产物 build 目录 |
| `assembleHap` | 构建 Hap 应用 |
| `assembleApp` | 构建 App 应用 |
| `assembleHsp` | 构建 Hsp 包 |
| `assembleHar` | 构建 Har 包 |
| `collectCoverage` | 基于打点数据生成覆盖率统计报表 |
## 常用构建参数
| 参数 | 说明 |
|------|------|
| `-p buildMode={debug\|release}` | 指定构建模式。默认Hap/Hsp/Har 为 debugApp 为 release |
| `-p debuggable=true/false` | 覆盖 buildOption 中的 debuggable 配置 |
| `-p product={ProductName}` | 指定 product 进行编译,默认为 default |
| `-p module={ModuleName}@{TargetName}` | 指定模块及 target 编译(需配合 `--mode module` |
| `-p ohos-test-coverage={true\|false}` | 执行测试框架代码覆盖率插桩编译 |
| `-p parameterFile=param.json` | 设置 oh-package.json5 的参数配置文件 |
## 构建示例
```bash
# 清理构建产物
hvigorw clean
# Debug 模式构建 Hap
hvigorw assembleHap -p buildMode=debug
# Release 模式构建 App
hvigorw assembleApp -p buildMode=release
# 构建指定 product
hvigorw assembleHap -p product=free
# 构建指定模块
hvigorw assembleHap -p module=entry@default --mode module
# 构建多个模块
hvigorw assembleHar -p module=library1@default,library2@default --mode module
```
## 测试命令
### Instrument Test (设备测试)
```bash
hvigorw onDeviceTest -p module={moduleName} -p coverage={true|false} -p scope={suiteName}#{methodName}
```
- `module`: 执行测试的模块,缺省执行所有模块
- `coverage`: 是否生成覆盖率报告,默认 true
- `scope`: 测试范围,格式 `{suiteName}#{methodName}``{suiteName}`
- `ohos-debug-asan`: 是否启用 ASan 检测,默认 false (5.19.0+)
**输出路径:**
- 覆盖率报告: `<module-path>/.test/default/outputs/ohosTest/reports`
- 测试结果: `<project>/<module>/.test/default/intermediates/ohosTest/coverage_data/test_result.txt`
### Local Test (本地测试)
```bash
hvigorw test -p module={moduleName} -p coverage={true|false} -p scope={suiteName}#{methodName}
```
**输出路径:**
- 覆盖率报告: `<module-path>/.test/default/outputs/test/reports`
- 测试结果: `<project>/<module>/.test/default/intermediates/test/coverage_data/test_result.txt`
## 日志级别
| 参数 | 说明 |
|------|------|
| `-e, --error` | 设置日志级别为 error |
| `-w, --warn` | 设置日志级别为 warn |
| `-i, --info` | 设置日志级别为 info |
| `-d, --debug` | 设置日志级别为 debug |
| `--stacktrace` | 开启打印异常堆栈信息 |
## 构建分析 (Build Analyzer)
| 参数 | 说明 |
|------|------|
| `--analyze=normal` | 普通模式分析 |
| `--analyze=advanced` | 进阶模式,更详细的任务耗时数据 |
| `--analyze=ultrafine` | 超精细化模式ArkTS 编译详细打点 (6.0.0+) |
| `--analyze=false` | 不启用构建分析 |
| `--config properties.hvigor.analyzeHtml=true` | 生成 HTML 可视化报告到 `.hvigor/report` |
## 守护进程 (Daemon)
| 参数 | 说明 |
|------|------|
| `--daemon` | 启用守护进程 |
| `--no-daemon` | 关闭守护进程(命令行模式推荐) |
| `--stop-daemon` | 关闭当前工程的守护进程 |
| `--stop-daemon-all` | 关闭所有工程的守护进程 |
| `--status-daemon` | 查询所有 Hvigor 守护进程信息 |
| `--max-old-space-size=12345` | 设置老生代内存大小 (MB) |
| `--max-semi-space-size=32` | 设置新生代半空间大小 (MB, 5.18.4+) |
## 性能与内存优化
| 参数 | 说明 |
|------|------|
| `--parallel` / `--no-parallel` | 开启/关闭并行构建(默认开启) |
| `--incremental` / `--no-incremental` | 开启/关闭增量构建(默认开启) |
| `--optimization-strategy=performance` | 性能优先模式,加快构建但占用更多内存 (5.19.2+) |
| `--optimization-strategy=memory` | 内存优先模式(默认)(5.19.2+) |
## 公共命令
| 任务 | 说明 |
|------|------|
| `tasks` | 打印工程各模块包含的任务信息 |
| `taskTree` | 打印工程各模块的任务依赖关系 |
| `prune` | 清除 30 天未使用的缓存并删除 pnpm 未引用包 |
| `buildInfo` | 打印 build-profile.json5 配置信息 (5.18.4+) |
### buildInfo 扩展参数
```bash
# 打印工程级配置
hvigorw buildInfo
# 打印指定模块配置
hvigorw buildInfo -p module=entry
# 包含 buildOption 配置
hvigorw buildInfo -p buildOption
# JSON 格式输出
hvigorw buildInfo -p json
```
## 其他参数
| 参数 | 说明 |
|------|------|
| `-h, --help` | 打印帮助信息 |
| `-v, --version` | 打印版本信息 |
| `-s, --sync` | 同步工程信息到 `./hvigor/outputs/sync/output.json` |
| `-m, --mode` | 指定执行目录级别 (如 `-m project`) |
| `--type-check` | 开启 hvigorfile.ts 类型检查 |
| `--watch` | 观察模式,用于预览和热加载 |
| `--node-home <string>` | 指定 Node.js 路径 |
| `--config, -c` | 指定 hvigor-config.json5 参数 |
## CI/CD 常用命令组合
```bash
# 完整的 Release 构建流程
hvigorw clean && hvigorw assembleApp -p buildMode=release --no-daemon
# 带构建分析的 Debug 构建
hvigorw assembleHap -p buildMode=debug --analyze=advanced --no-daemon
# 运行测试并生成覆盖率报告
hvigorw onDeviceTest -p coverage=true --no-daemon
# 内存受限环境构建
hvigorw assembleHap --optimization-strategy=memory --no-daemon
# 清理缓存
hvigorw prune
hvigorw --stop-daemon-all
```

View File

@@ -0,0 +1,407 @@
# TypeScript to ArkTS Migration Guide
Complete guide for migrating TypeScript code to ArkTS, covering all language constraints and adaptation rules.
## Table of Contents
1. [Overview](#overview)
2. [Constraint Categories](#constraint-categories)
3. [Prohibited Features](#prohibited-features)
4. [Migration Examples](#migration-examples)
5. [Migration Checklist](#migration-checklist)
---
## Overview
ArkTS is based on TypeScript but enforces stricter rules for:
- **Performance**: Static analysis enables AOT compilation
- **Type Safety**: Eliminates runtime type errors
- **Predictability**: Fixed object structures at compile time
Constraints are categorized as:
- **Error**: Must fix, blocks compilation
- **Warning**: Should fix, may become errors in future
---
## Constraint Categories
### 1. Type System Constraints
#### Prohibited: `any` and `unknown`
```typescript
// ❌ TypeScript
let value: any = getData();
let result: unknown = parse(input);
// ✅ ArkTS
interface Data { id: number; name: string; }
let value: Data = getData();
let result: Data | null = parse(input);
```
#### Prohibited: Type assertions to `any`
```typescript
// ❌ TypeScript
(obj as any).dynamicProp = value;
// ✅ ArkTS - Define complete interface
interface MyObject {
existingProp: string;
dynamicProp?: number;
}
let obj: MyObject = { existingProp: 'test' };
obj.dynamicProp = value;
```
### 2. Variable Declaration
#### Prohibited: `var`
```typescript
// ❌ TypeScript
var count = 0;
var name = "hello";
// ✅ ArkTS
let count = 0;
const name = "hello";
```
### 3. Object Structure Constraints
#### Prohibited: Runtime property modification
```typescript
class Point {
x: number = 0;
y: number = 0;
}
let p = new Point();
// ❌ All prohibited
p['z'] = 99; // Dynamic property
delete p.x; // Property deletion
Object.assign(p, {z: 1}); // Runtime extension
// ✅ Define all properties upfront
class Point3D {
x: number = 0;
y: number = 0;
z: number = 0;
}
```
#### Prohibited: Structural typing (duck typing)
```typescript
interface Named { name: string; }
// ❌ TypeScript allows structural matching
let obj = { name: "Alice", age: 25 };
let named: Named = obj; // Works in TS, fails in ArkTS
// ✅ ArkTS requires explicit implementation
class Person implements Named {
name: string = "";
age: number = 0;
}
let named: Named = new Person();
```
### 4. Private Fields
#### Prohibited: `#` private fields
```typescript
// ❌ TypeScript
class MyClass {
#secret: string = "";
#getValue(): string { return this.#secret; }
}
// ✅ ArkTS
class MyClass {
private secret: string = "";
private getValue(): string { return this.secret; }
}
```
### 5. Symbol Properties
#### Prohibited: Symbol as property key
```typescript
// ❌ TypeScript
const sym = Symbol('key');
let obj = { [sym]: 'value' };
// ✅ ArkTS
let obj = { key: 'value' };
```
### 6. Prohibited Statements
#### `for...in`
```typescript
// ❌ TypeScript
for (let key in obj) {
console.log(obj[key]);
}
// ✅ ArkTS - Use Object.keys with forEach
Object.keys(obj).forEach((key: string) => {
// Access via typed interface
});
// ✅ ArkTS - Use for...of for arrays
let arr: string[] = ['a', 'b', 'c'];
for (let item of arr) {
console.log(item);
}
```
#### `delete`
```typescript
// ❌ TypeScript
delete obj.property;
// ✅ ArkTS - Use optional properties
interface Config {
name: string;
value?: number; // Optional, can be undefined
}
let config: Config = { name: 'test', value: undefined };
```
#### `with`
```typescript
// ❌ TypeScript
with (obj) {
console.log(property);
}
// ✅ ArkTS - Use explicit references
console.log(obj.property);
```
#### `in` operator for type checking
```typescript
// ❌ TypeScript
if ('name' in person) {
console.log(person.name);
}
// ✅ ArkTS - Use instanceof
if (person instanceof Person) {
console.log(person.name);
}
// ✅ ArkTS - Use discriminated unions
interface Person { type: 'person'; name: string; }
interface Animal { type: 'animal'; species: string; }
type Entity = Person | Animal;
function getName(e: Entity): string {
if (e.type === 'person') {
return e.name;
}
return e.species;
}
```
### 7. Interface Constraints
#### Prohibited: Call signatures and construct signatures
```typescript
// ❌ TypeScript
interface Callable {
(x: number): number;
new (s: string): Object;
}
// ✅ ArkTS - Use classes
class Calculator {
calculate(x: number): number {
return x * 2;
}
}
class Factory {
create(s: string): Object {
return { value: s };
}
}
```
### 8. Other Restrictions
| Feature | Status | Alternative |
|---------|--------|-------------|
| Comma expressions | Prohibited (except in `for`) | Separate statements |
| Computed property names | Limited | String literal keys |
| Spread on non-arrays | Limited | Explicit copying |
| `eval()` | Prohibited | Avoid |
| `Function()` constructor | Prohibited | Arrow functions |
| Prototype modification | Prohibited | Class inheritance |
---
## Migration Examples
### Example 1: Dynamic Configuration Object
```typescript
// ❌ TypeScript
let config: any = {};
config.apiUrl = 'https://api.example.com';
config.timeout = 5000;
config.retry = true;
// ✅ ArkTS
interface AppConfig {
apiUrl: string;
timeout: number;
retry: boolean;
}
let config: AppConfig = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retry: true
};
```
### Example 2: Object Iteration
```typescript
// ❌ TypeScript
interface User { name: string; age: number; }
let user: User = { name: 'John', age: 30 };
for (let key in user) {
console.log(`${key}: ${user[key]}`);
}
// ✅ ArkTS
interface User {
name: string;
age: number;
}
let user: User = { name: 'John', age: 30 };
console.log(`name: ${user.name}`);
console.log(`age: ${user.age}`);
// Or use explicit property list
const props: (keyof User)[] = ['name', 'age'];
for (let prop of props) {
// Handle each known property
}
```
### Example 3: Optional Property Handling
```typescript
// ❌ TypeScript
let obj: any = { a: 1 };
if (obj.b) {
delete obj.b;
}
obj.c = 3;
// ✅ ArkTS
interface MyObj {
a: number;
b?: number;
c?: number;
}
let obj: MyObj = { a: 1 };
if (obj.b !== undefined) {
obj.b = undefined; // Set to undefined instead of delete
}
obj.c = 3;
```
### Example 4: Type Guards
```typescript
// ❌ TypeScript
function process(input: unknown) {
if (typeof input === 'string') {
return input.toUpperCase();
}
if ('length' in input) {
return (input as any[]).length;
}
}
// ✅ ArkTS
function processString(input: string): string {
return input.toUpperCase();
}
function processArray(input: string[]): number {
return input.length;
}
// Use union types with type narrowing
type Input = string | string[];
function process(input: Input): string | number {
if (typeof input === 'string') {
return input.toUpperCase();
}
return input.length;
}
```
---
## Migration Checklist
### Phase 1: Enable Strict Mode
- [ ] Enable `strict: true` in tsconfig.json
- [ ] Enable `noImplicitAny: true`
- [ ] Enable `strictNullChecks: true`
- [ ] Fix all resulting errors
### Phase 2: Remove Prohibited Keywords
- [ ] Replace all `var` with `let`/`const`
- [ ] Remove all `any` type annotations
- [ ] Remove all `unknown` type annotations
- [ ] Replace `#` private fields with `private`
### Phase 3: Fix Object Patterns
- [ ] Replace dynamic property access with typed interfaces
- [ ] Remove `delete` statements
- [ ] Remove `for...in` loops
- [ ] Remove `with` statements
- [ ] Replace `in` operator type checks
### Phase 4: Update Interfaces
- [ ] Remove call signatures from interfaces
- [ ] Remove construct signatures from interfaces
- [ ] Replace structural typing with explicit implements
### Phase 5: Validate
- [ ] Build with ArkTS compiler
- [ ] Fix remaining errors
- [ ] Test all functionality
---
## Resources
- [Official Migration Guide](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/typescript-to-arkts-migration-guide)
- [ArkTS Language Reference](https://developer.huawei.com/consumer/cn/arkts/)

View File

@@ -0,0 +1,366 @@
---
name: harmonyos-build-deploy
description: HarmonyOS application build, clean, package and device installation. Use when building HarmonyOS projects with hvigorw, cleaning build artifacts, managing ohpm dependencies, packaging HAP/HSP/APP bundles, installing to devices via hdc, or troubleshooting installation errors like "version code not same".
---
# HarmonyOS Build & Deploy
Complete workflow for building, cleaning, packaging, and installing HarmonyOS applications.
## First Step: Ask User for Operation
**IMPORTANT:** Before executing any build or deploy operation, you MUST first ask the user which specific operation(s) they want to perform using the `question` tool.
Use the following question configuration:
```javascript
question({
questions: [{
header: "选择操作",
multiple: true,
question: "您想要执行哪些构建部署操作?",
options: [
{ label: "清理构建产物", description: "清理之前的构建缓存和产物" },
{ label: "安装依赖", description: "使用 ohpm 安装项目依赖" },
{ label: "构建项目", description: "使用 hvigorw 构建 HAP/APP 包" },
{ label: "安装到设备", description: "使用 hdc 将应用安装到设备" },
{ label: "完整流程", description: "依次执行清理、安装依赖、构建、部署到设备" }
]
}]
})
```
**Why ask first:**
- Different scenarios require different operations (e.g., incremental build vs clean build)
- Avoid unnecessary time-consuming operations
- Give user control over the workflow
- Prevent accidental device installation
**After user responds:**
- Execute only the selected operations
- Use the subagent Task tool for time-consuming operations (build, deploy)
- Report progress and results clearly
## Quick Reference
```bash
# For time-consuming operations (build, deploy), use subagent Task tool
# See "Workflow with Subagent" section below for details
# Build complete app (incremental)
hvigorw assembleApp --mode project -p product=default -p buildMode=release --no-daemon
# Install to device (check actual output path in your project)
hdc -t <UDID> shell "rm -rf /data/local/tmp/install && mkdir -p /data/local/tmp/install"
hdc -t <UDID> file send <output_path>/signed /data/local/tmp/install
hdc -t <UDID> shell "bm install -p /data/local/tmp/install/signed"
```
**Note:** Build output path varies by project. Common paths:
- `outputs/default/signed/`
- `outputs/project/bundles/signed/`
Check your project's actual output after build.
## Workflow with Subagent
For time-consuming operations (build, clean, deploy), use the **general** subagent to handle the entire workflow:
### Clean Build & Deploy
```bash
# Launch subagent to clean, build, and deploy to device
Task(description="Clean build and deploy", prompt="Clean build and deploy the HarmonyOS app to device.
1. Run: hvigorw clean --no-daemon
2. Run: ohpm install --all
3. Run: hvigorw assembleApp --mode project -p product=default -p buildMode=release --no-daemon
4. Find the build output directory (outputs/project/ or outputs/default/)
5. Deploy to device using hdc commands:
- Clean device temp: hdc -t <UDID> shell \"rm -rf /data/local/tmp/install && mkdir -p /data/local/tmp/install\"
- Send files: hdc -t <UDID> file send <output_path> /data/local/tmp/install/
- Install: hdc -t <UDID> shell \"bm install -p /data/local/tmp/install/project/bundles/signed\"
- Launch: hdc -t <UDID> shell \"aa start -a EntryAbility -b <bundleName>\"
6. Report success/failure with details.", subagent_type="general")
```
### Deploy Only (No Rebuild)
```bash
# Launch subagent to deploy existing build to device
Task(description="Deploy app to device", prompt="Deploy the HarmonyOS application to device <UDID>.
1. Read AppScope/app.json5 to get bundleName
2. Clean device temp: hdc -t <UDID> shell \"rm -rf /data/local/tmp/install && mkdir -p /data/local/tmp/install\"
3. Send build output to device: hdc -t <UDID> file send \"<output_path>/project/\" \"/data/local/tmp/install/\"
4. Install: hdc -t <UDID> shell \"bm install -p /data/local/tmp/install/project/bundles/signed\"
5. Launch: hdc -t <UDID> shell \"aa start -a EntryAbility -b <bundleName>\"
6. Report success/failure with details.", subagent_type="general")
```
### Restart App
```bash
# Launch subagent to restart the app
Task(description="Restart app", prompt="Restart the HarmonyOS app on device <UDID>.
1. Force stop: hdc -t <UDID> shell \"aa force-stop <bundleName>\"
2. Launch: hdc -t <UDID> shell \"aa start -a EntryAbility -b <bundleName>\"
3. Report success/failure.", subagent_type="general")
```
### Clean App Cache/Data
```bash
# Launch subagent to clean app data
Task(description="Clean app data", prompt="Clean app data on device <UDID>.
1. Clean cache: hdc -t <UDID> shell \"bm clean -n <bundleName> -c\"
2. Clean data: hdc -t <UDID> shell \"bm clean -n <bundleName> -d\"
3. Report success/failure.", subagent_type="general")
```
**Why use subagent?**
- Long-running operations (build ~30s, file transfer ~20s) don't timeout
- Better error handling and reporting
- User gets clear progress updates
## Build Commands (hvigorw)
### Incremental Build (Default)
Use incremental build for normal development - only changed modules are rebuilt:
```bash
# Build complete app (incremental)
hvigorw assembleApp --mode project -p product=default -p buildMode=release --no-daemon
```
### Single Module Build
Build only a specific module for faster iteration:
```bash
# Build single HAP module
hvigorw assembleHap -p module=entry@default --mode module -p buildMode=release --no-daemon
# Build single HSP module
hvigorw assembleHsp -p module=feature_module@default --mode module -p buildMode=release --no-daemon
# Build single HAR module
hvigorw assembleHar -p module=library@default --mode module -p buildMode=release --no-daemon
# Build multiple modules at once
hvigorw assembleHsp -p module=module1@default,module2@default --mode module -p buildMode=release --no-daemon
```
**Module name format:** `{moduleName}@{targetName}`
- `moduleName`: Directory name of the module (e.g., `entry`, `feature_home`)
- `targetName`: Target defined in module's `build-profile.json5` (usually `default`)
**When to use single module build:**
- Developing/debugging a specific module
- Faster build times during iteration
- Testing changes in isolated module
**Note:** After single module build, you still need to run `assembleApp` to package the complete application for installation.
### Clean Build (When Needed)
Only perform clean build when:
- First time building the project
- Encountering unexplained build errors
- After modifying `build-profile.json5` or SDK version
- Dependency resolution issues
```bash
# Clean build artifacts
hvigorw clean --no-daemon
# Deep clean (for dependency issues)
ohpm clean && ohpm cache clean
ohpm install --all
hvigorw --sync -p product=default -p buildMode=release --no-daemon
hvigorw assembleApp --mode project -p product=default -p buildMode=release --no-daemon
```
### Install Dependencies (ohpm)
```bash
# Install all dependencies (after clean or first clone)
ohpm install --all
# With custom registry
ohpm install --all --registry "https://repo.harmonyos.com/ohpm/"
```
### Sync Project
Only needed after modifying `build-profile.json5` or `oh-package.json5`:
```bash
hvigorw --sync -p product=default -p buildMode=release --no-daemon
```
### Build Types
```bash
hvigorw assembleHap # Build HAP (Harmony Ability Package)
hvigorw assembleHsp # Build HSP (Harmony Shared Package)
hvigorw assembleHar # Build HAR (Harmony Archive)
hvigorw assembleApp # Build complete APP bundle
```
### Build Parameters
| Parameter | Description |
|-----------|-------------|
| `-p product={name}` | Target product defined in build-profile.json5 |
| `-p buildMode={debug\|release}` | Build mode |
| `-p module={name}@{target}` | Target module with `--mode module` |
| `--mode project` | Build all modules in project |
| `--mode module` | Build specific module only |
| `--no-daemon` | Disable daemon (recommended for CI) |
| `--analyze=advanced` | Enable build analysis |
## Build Outputs
Build output path varies by project configuration. Common patterns:
```
outputs/
├── default/signed/ # Pattern 1
│ ├── entry-default-signed.hap
│ └── *.hsp
└── project/bundles/signed/ # Pattern 2
├── entry-default-signed.hap
└── *.hsp
```
**Tip:** After build, check the actual output directory in your project.
### Module Types
| Type | Extension | Description |
|------|-----------|-------------|
| HAP | `.hap` | Harmony Ability Package - Application entry module |
| HSP | `.hsp` | Harmony Shared Package - Dynamic shared library |
| HAR | `.har` | Harmony Archive - Static library (compiled into HAP) |
| APP | `.app` | Complete application bundle (all HAP + HSP) |
## Unwanted Modules in Output Directory
Sometimes HSP files appear in the output directory that are **not listed in `build-profile.json5`**. These modules should not be deployed.
**Cause:** Build scripts or dependency configurations may copy precompiled HSP files to the output directory, even though they are not part of the current build.
**How to identify:**
1. Check `build-profile.json5``modules` array
2. If an HSP file in output is not in the modules list, it should be removed before installation
**Solution:** Remove these HSP files before installation:
```bash
# Example: Remove modules not in build-profile.json5
rm <output_path>/signed/unwanted-module-default-signed.hsp
```
**Note:** Installation will fail with "version code not same" error if these unwanted modules have a different versionCode than the main app. The root cause is that these modules shouldn't be deployed at all.
## Device Installation (hdc)
hdc (HarmonyOS Device Connector) is the CLI tool for device communication, similar to Android's adb.
### Check Device
```bash
hdc list targets # List connected devices (returns UDID)
hdc -t <UDID> shell "whoami" # Test connection
```
### Push and Install
```bash
# Clear device directory
hdc -t <UDID> shell "rm -rf /data/local/tmp/install && mkdir -p /data/local/tmp/install"
# Push signed bundles
hdc -t <UDID> file send path/to/signed /data/local/tmp/install
# Install all HAP/HSP in directory
hdc -t <UDID> shell "bm install -p /data/local/tmp/install/signed"
```
### Verify and Launch
```bash
# Check installation
hdc -t <UDID> shell "bm dump -n <bundleName>"
# Launch app (default ability)
hdc -t <UDID> shell "aa start -a EntryAbility -b <bundleName>"
```
### Uninstall
```bash
hdc -t <UDID> shell "bm uninstall -n <bundleName>"
```
## hdc Command Reference
| Command | Description |
|---------|-------------|
| `hdc list targets` | List connected devices |
| `hdc -t <UDID> shell "<cmd>"` | Execute shell command on device |
| `hdc -t <UDID> file send <local> <remote>` | Push file/directory to device |
| `hdc -t <UDID> file recv <remote> <local>` | Pull file/directory from device |
| `hdc kill` | Kill hdc server |
| `hdc start` | Start hdc server |
| `hdc version` | Show hdc version |
## Bundle Manager (bm)
Run via `hdc -t <UDID> shell "bm ..."`:
| Command | Description |
|---------|-------------|
| `bm install -p <path>` | Install from directory (all HAP/HSP) |
| `bm install -p <file.hap>` | Install single HAP file |
| `bm uninstall -n <bundleName>` | Uninstall application |
| `bm dump -n <bundleName>` | Show package info |
| `bm dump -a` | List all installed packages |
## Ability Assistant (aa)
Run via `hdc -t <UDID> shell "aa ..."`:
| Command | Description |
|---------|-------------|
| `aa start -a <ability> -b <bundle>` | Start specific ability |
| `aa force-stop <bundleName>` | Force stop application |
| `aa dump -a` | Dump all running abilities |
## Troubleshooting
| Error | Cause | Solution |
|-------|-------|----------|
| `version code not same` | HSP in output not in build-profile.json5 | Remove unwanted HSP files before install |
| `install parse profile prop check error` | Signature/profile mismatch | Check signing config in build-profile.json5 |
| `install failed due to older sdk version` | Device SDK < app's compatibleSdkVersion | Update device or lower compatibleSdkVersion |
| Device not found | Connection issue | Check USB, enable debugging, `hdc kill && hdc start` |
| `signature verification failed` | Invalid or expired certificate | Regenerate signing certificate |
## Key Configuration Files
| File | Description |
|------|-------------|
| `AppScope/app.json5` | App metadata (bundleName, versionCode, versionName) |
| `build-profile.json5` | Modules list, products, signing configs |
| `{module}/src/main/module.json5` | Module config (abilities, permissions) |
| `{module}/oh-package.json5` | Module dependencies |
## Reference Files
- **Complete Installation Guide**: [references/device-installation.md](references/device-installation.md)

View File

@@ -0,0 +1,292 @@
# Device Installation Guide
Complete guide for building, packaging, and installing HarmonyOS applications to physical devices.
## Prerequisites
- **hdc**: HarmonyOS Device Connector (included in HarmonyOS SDK)
- **Device**: HarmonyOS device with USB debugging enabled
- **Build Output**: Signed HAP/HSP files
## Complete Workflow
### Step 1: Check Device Connection
```bash
hdc list targets
# Output: device UDID (e.g., 1234567890ABCDEF)
```
If no device found:
1. Check USB connection
2. Enable Developer Options on device
3. Enable USB debugging
4. Run `hdc kill && hdc start` to restart hdc server
### Step 2: Build Project
```bash
# Incremental build (default, use for normal development)
hvigorw assembleApp --mode project -p product=default -p buildMode=release --no-daemon
```
Only perform clean build when encountering issues or first time setup:
```bash
# Clean build (only when needed)
hvigorw clean --no-daemon
ohpm clean && ohpm cache clean
ohpm install --all
hvigorw --sync -p product=default -p buildMode=release --no-daemon
hvigorw assembleApp --mode project -p product=default -p buildMode=release --no-daemon
```
Build outputs location:
- APP bundle: `outputs/{product}/{project}-{product}-signed.app`
- Signed modules: `outputs/{product}/signed/`
### Step 3: Verify Build Outputs
All HAP/HSP modules must have the **same versionCode**. Check for mismatches:
```bash
# Using Python (cross-platform)
python3 -c "
import zipfile, json, glob
for f in glob.glob('outputs/default/signed/*.hsp'):
z = zipfile.ZipFile(f)
data = json.loads(z.read('module.json'))
print(f\"{f.split('/')[-1]}: versionCode = {data['app']['versionCode']}\")
"
# Using unzip + grep (Linux/macOS)
for f in outputs/default/signed/*.hsp; do
echo -n "$(basename $f): "
unzip -p "$f" module.json | grep -o '"versionCode":[0-9]*'
done
```
**How to identify problematic modules:**
1. Module directory has no `src/` folder (precompiled binary only)
2. Module not listed in `build-profile.json5` modules array
3. Module versionCode differs from `AppScope/app.json5`
If any module has a different versionCode, remove it before installation:
```bash
rm outputs/default/signed/problematic-module-default-signed.hsp
```
### Step 4: Push Files to Device
```bash
# Clear and create installation directory on device
hdc -t <UDID> shell "rm -rf /data/local/tmp/app_install && mkdir -p /data/local/tmp/app_install"
# Push signed HAP/HSP files
hdc -t <UDID> file send outputs/default/signed /data/local/tmp/app_install
```
### Step 5: Install Application
```bash
# Install all HAP/HSP from directory
hdc -t <UDID> shell "bm install -p /data/local/tmp/app_install/signed"
# Expected output: "install bundle successfully."
```
### Step 6: Verify and Launch
```bash
# Check package info
hdc -t <UDID> shell "bm dump -n <bundleName>"
# Launch application
hdc -t <UDID> shell "aa start -a EntryAbility -b <bundleName>"
```
### Uninstall Application
```bash
hdc -t <UDID> shell "bm uninstall -n <bundleName>"
```
## Quick Installation Script
Save as `install.sh` (Linux/macOS) or run with Git Bash on Windows:
```bash
#!/bin/bash
# === Configuration ===
DEVICE_ID="${1:-$(hdc list targets | head -1)}"
SIGNED_PATH="${2:-outputs/default/signed}"
BUNDLE_NAME="${3:-}"
REMOTE_PATH="/data/local/tmp/app_install"
if [ -z "$DEVICE_ID" ]; then
echo "Error: No device found. Connect a device or specify UDID as first argument."
exit 1
fi
echo "Device: $DEVICE_ID"
echo "Source: $SIGNED_PATH"
# === Clear remote directory ===
hdc -t "$DEVICE_ID" shell "rm -rf $REMOTE_PATH && mkdir -p $REMOTE_PATH"
# === Push signed files ===
hdc -t "$DEVICE_ID" file send "$SIGNED_PATH" "$REMOTE_PATH"
# === Install ===
hdc -t "$DEVICE_ID" shell "bm install -p $REMOTE_PATH/$(basename $SIGNED_PATH)"
echo ""
echo "Installation complete!"
# === Optional: Launch app ===
if [ -n "$BUNDLE_NAME" ]; then
echo "Launching $BUNDLE_NAME..."
hdc -t "$DEVICE_ID" shell "aa start -a EntryAbility -b $BUNDLE_NAME"
fi
```
Usage:
```bash
# Auto-detect device, use default path
./install.sh
# Specify device UDID
./install.sh 1234567890ABCDEF
# Specify device and path
./install.sh 1234567890ABCDEF outputs/default/signed
# Specify device, path, and bundle name (auto-launch)
./install.sh 1234567890ABCDEF outputs/default/signed com.example.app
```
## hdc Command Reference
### Device Management
| Command | Description |
|---------|-------------|
| `hdc list targets` | List connected devices (UDID) |
| `hdc -t <UDID> shell "<cmd>"` | Execute shell command on device |
| `hdc kill` | Kill hdc server |
| `hdc start` | Start hdc server |
| `hdc version` | Show hdc version |
### File Transfer
| Command | Description |
|---------|-------------|
| `hdc -t <UDID> file send <local> <remote>` | Push file/directory to device |
| `hdc -t <UDID> file recv <remote> <local>` | Pull file/directory from device |
### Bundle Manager (bm)
Execute via `hdc -t <UDID> shell "bm ..."`:
| Command | Description |
|---------|-------------|
| `bm install -p <path>` | Install from directory (all HAP/HSP) |
| `bm install -p <file.hap>` | Install single HAP file |
| `bm uninstall -n <bundleName>` | Uninstall application |
| `bm dump -n <bundleName>` | Dump package info |
| `bm dump -a` | Dump all installed packages |
### Ability Assistant (aa)
Execute via `hdc -t <UDID> shell "aa ..."`:
| Command | Description |
|---------|-------------|
| `aa start -a <ability> -b <bundle>` | Start specific ability |
| `aa force-stop <bundleName>` | Force stop application |
| `aa dump -a` | Dump all running abilities |
## Troubleshooting
### Error: "version code not same"
**Cause:** Some HAP/HSP modules have different versionCode than others.
**Solution:**
1. Use the version check commands to find modules with different versionCode
2. Remove those modules from signed directory before installation
3. Usually caused by precompiled modules not in build-profile.json5
### Error: "install parse profile prop check error"
**Cause:** Signature or profile configuration mismatch.
**Solution:**
1. Check signing config in `build-profile.json5`
2. Ensure certificate and profile match
3. Verify profile bundleName matches app.json5 bundleName
4. Check certificate is not expired
### Error: Device not found
**Cause:** Connection or hdc service issue.
**Solution:**
1. Check USB cable connection
2. Enable Developer Options: Settings → About → Tap build number 7 times
3. Enable USB debugging: Settings → Developer options → USB debugging
4. Restart hdc server: `hdc kill && hdc start`
5. Try different USB port or cable
### Error: "install failed due to older sdk version in the device"
**Cause:** Device system version is lower than app's minimum requirement.
**Solution:**
1. Update device to latest system version
2. Or lower `compatibleSdkVersion` in `build-profile.json5`
### Error: "signature verification failed"
**Cause:** Certificate issues.
**Solution:**
1. Regenerate debug/release certificate in DevEco Studio
2. Check certificate validity period
3. Ensure using correct signing config for build type
## Build Output Structure
```
outputs/
└── {product}/ # e.g., default/
├── {project}-{product}-signed.app # Complete APP bundle
├── signed/
│ ├── entry-{product}-signed.hap # Main entry HAP
│ ├── feature-{product}-signed.hap # Feature HAP (if any)
│ └── *.hsp # Shared library modules
└── unsigned/
└── ... # Unsigned versions
```
## Key Configuration Files
| File | Description |
|------|-------------|
| `AppScope/app.json5` | App metadata: bundleName, versionCode, versionName, icon, label |
| `build-profile.json5` | Build config: modules list, products, signing configs |
| `{module}/src/main/module.json5` | Module config: abilities, permissions, pages |
| `{module}/oh-package.json5` | Module dependencies |
## Module Types
| Type | Extension | Description |
|------|-----------|-------------|
| HAP | `.hap` | Harmony Ability Package - Application entry point |
| HSP | `.hsp` | Harmony Shared Package - Dynamic shared library |
| HAR | `.har` | Harmony Archive - Static library (compiled into HAP) |
| APP | `.app` | Complete bundle containing all HAP + HSP |