harmonyos-development について
harmonyos-developmentは、HarmonyOSアプリ開発者向けの開発スキルです。ArkTS、ArkUI、開発ツールチェーンと複数のAPIバージョンを扱い、API 24の本番基準とAPI 26のプレビュー情報を区別して説明します。
主な機能
- 01ArkTSとArkUIの解説
- 02HarmonyOS APIバージョンの整理
- 03ツールチェーンと基準構成の提示
- 04KitとAPIの変更点の要約
活用シーン
- 01HarmonyOSネイティブアプリの開発
- 02APIバージョンに応じたアプリ適応
- 03プラットフォームとツールチェーンの変更確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dengshiyinga/ harmonyos-ai-skill --skill harmonyos-developmentインストール後、Claude Code で/harmonyos-developmentを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/cases.yamlexamples/lazyforeach-list.etspermission-request.etsrecipes/debug-build-error.mdreview-arkts-code.mdreferences/ai-development-tools.mdapi26-preview.mdarkts-rules.mdarkui-components.mdbuild-sign-release.mdnative-api-compatibility.mdnavigation.mdperformance.mdpermissions.mdplatform-baseline.mdREADME.mdstage-model.mdstate-management.md詳細
HarmonyOS(鸿蒙)開発
HarmonyOS 6.1(API 23、安定版)/6.1.1(API 24、リリース版)/HarmonyOS 7 デベロッパープレビュー(API 26 Beta1)/NEXT ネイティブアプリ開発を対象とします。HarmonyOS は Android から独立して動作する Huawei のモバイル OS ファミリーです(2024 年リリースの HarmonyOS NEXT 以降、AOSP を使用していません)。主な言語は ArkTS(厳密な型チェックを行う、TypeScript のスーパーセット)で、主な UI フレームワークは ArkUI(宣言型、状態駆動型)です。本番環境のデフォルトのベースラインには API 24 Release を使用してください。API 26 Beta1 はプレビュー、移行対応、初期の互換性対応にのみ使用してください。
プラットフォーム概要
| 項目 | 値 |
|---|---|
| OS | HarmonyOS 6.1(安定版、2026/04/20 リリース、API 23)。HarmonyOS 6.1.1(Release、2026/05/26 リリース、API 24)。HarmonyOS 7 / 26.0.0 Beta1(デベロッパープレビュー、2026/06/12 リリース、API 26)。純粋な HarmonyOS、AOSP 非使用 |
| 言語 | ArkTS(主言語)、Cangjie(ベータ版)、NAPI 経由の C/C++ |
| UI フレームワーク | 宣言型の ArkUI(クロスプラットフォーム向けの ArkUI-X) |
| コンパイラー | ArkCompiler — ネイティブマシンコードへの AOT コンパイル、LiteActor による並行処理 |
| パッケージマネージャー | ohpm — oh-package.json5。レジストリは DevEco Service(OHPM Central) |
| IDE | DevEco Studio 6.1.1 Release(6.1.1.280、本番環境向け API 24)。DevEco Studio 26.0.0 Beta1(26.0.0.461、プレビュー向け API 26) |
| アプリモデル | Stage モデル(FA モデルはレガシーです。新しいアプリでは使用しないでください) |
| パッケージ形式 | HAP(entry/feature)、HSP(共有パッケージ)、HAR(静的アーカイブ)、アトミック .app |
| 推奨 API | 本番環境では API 24 Release を使用してください。API 26 Beta1 は HarmonyOS 7 のプレビュー/移行対応にのみ使用してください。 |
| サンプルカタログ | https://developer.huawei.com/consumer/cn/samples/ |
最近のリリース履歴:
- HarmonyOS 6.0.1(21) — 2025/11/25(Mate 80 シリーズとともに初の安定版をリリース)
- HarmonyOS 6.0.2(22) — 2026/01/23(増分アップデート)
- HarmonyOS 6.0.0.328 Pollen Beta(23) — 2026/02/28(クローズドベータ、25 モデル)
- HarmonyOS 6.1(23) — 2026/04/20(安定版の一般リリース)
- HarmonyOS 6.1.1(24) Beta 1 — 2026/04/30(デベロッパーベータ)
- HarmonyOS 6.1.1(24) Release — 2026/05/26(API 24 Release、DevEco Studio 6.1.1.280)
- HarmonyOS 7 / 26.0.0 Beta1 — 2026/06/12(API 26 デベロッパープレビュー、DevEco Studio 26.0.0.461)
API 23(HarmonyOS 6.1)の新機能
ArkUI の機能強化:
Navigationでルーティングスタックをコンポーネント自体にバインドし、NavDestinationをナビゲーションバー(ホームページ)として指定できるようになりました。別個のルートコンテナーは不要ですMenuにanchorPositionプロパティが追加されました。水平方向/垂直方向のオフセットを使って、アンカーの左上を基準にポップアップの位置を制御できますImageコンポーネントの SVG 解析機能が強化されました- 属性スタイル向けの新しい C API 群(ネイティブ側)
C 側の新機能(Native/NAPI):
- UDMF(Unified Data Management Framework)C API
- コンポーネントのドラッグ&ドロップ用 C API
- 暗号アルゴリズム用 C API
データ:
relationalStoreのsendable関数が強化され、スレッド間でのデータ受け渡しが改善されました
グラフィックスと AI:
- Graphics Accelerate Kit の AI 超解像フレーム機能(フレーム補間によってアニメーションをより滑らかにします)
ArkWeb: 機能がさらに強化されました(インターセプト/Cookie)
API 24 の新機能(HarmonyOS 6.1.1 リリース)
バージョンの基準: API 24 はベータ版だけでなく、正式リリース版の SDK になりました。API 24 のプロジェクトでは、DevEco Studio 6.1.1 Release(6.1.1.280)、HarmonyOS SDK 6.1.1 Release、Hvigor 6.24.2、ohpm 6.1.2.268、Node.js 18.20.1 を使用してください。
Beta1 からのリリース版での追加点:
- Ability Kit —
AbilityStageに、最初の Ability が作成される前、およびアプリケーションのスナップショットからプロセスが開始されたときのコールバックが追加されました。 - ArkTS —
enableLocalHandleDetectionが追加され、EventHandler/libuv タスクを意図したスコープ内に保ち、リークを防げるようになりました。XML パーサーには、SAX 方式の解析用にXmlSAXHandlerコールバックが追加されました。 - ArkWeb — ダウンロード完了コールバックから元の URL とリファラー URL を取得できるようになりました。
- Call Service Kit — 着信/発信電話番号からエンタープライズ通話サービスを検索できます。
- Camera Kit のプロ向けコントロール — フラッシュ状態のイベント登録/解除、OIS の照会/設定、レンズの焦点距離/35mm 換算焦点距離/最短撮影距離/歪み/内部キャリブレーション/センサーサイズ/ピクセルアレイ/カラーフィルターデータ、論理カメラ構成、自動/手動露出、手動フォーカス、ISO、物理絞り。
- CANN Kit — PC デバイスでの大規模言語モデル推論を高速化する API。
- MDM Kit — 現在のユーザーに対して、非表示の設定項目を管理できます。
API 24 Release でも引き続き利用できる Beta1 の追加点:
- Camera Kit の「Follow the Person」 — リアルタイムのクロップ/ズームにより人物をフレーム中央に保つ、自動カメラ追跡 API です。ビデオ通話、ワークアウトの録画、動画ブログの撮影に役立ちます。
// Conceptual API — full signature in official docs cameraSession.enableSubjectTracking(camera.TrackingMode.PERSON); - 遅延プレビュー出力 — 通常のプレビュー出力の代わりに遅延プレビューをストリームパイプラインへ直接追加し、その Surface を個別に設定できます。
- ArkTS VM 診断 — VM スレッドごとのヒープ情報、GC 後のヒープ警告コールバック、
taskpool.execute()のタイムアウト設定。 - ArkUI — 並列ウィンドウ状態の照会、Ability インスタンス間でのカスタムコンポーネントの移行、動的レイアウトコンテナー、
UIContextのルートノード検索、非同期ドラッグ&ドロップ判定、onNeedSoftkeyboard、Canvas/OffscreenCanvas のantialias、Tabs の入れ子スクロール、複数行の省略表示モード(MULTILINE_START、MULTILINE_CENTER)。 - ArkWeb — User-Agent Client Hints、コンテキストメニューのデフォルト設定、URL ホワイトリスト、読み込み/ジャンプのセキュリティ制御。
- Audio Kit — キャプチャ/レンダリング向けの個別のオーディオセッション戦略/動作、および USB/BLE MIDI デバイス用の OH_MIDI C API。
- 新規/拡張された Kits — Content Embed Kit、Enterprise Threat Protection Kit、FAST Kit、NearLink Kit、Network Boost Kit、Screen Time Guard Kit、Device Security Kit、Desktop Extension Kit。
- DevEco Studio — API 24 プロジェクト、C++ およびリソース編集のホットリロード、AppFreeze 解析の拡張、ComMemory の UI メモリ解析、高速な厳密構文チェック用の
strictCheckerOnly。
HarmonyOS 7/API 26 Beta1 のプレビュー(2026/06/12)
ステータス(2026/07/25 時点): 開発者向けベータ版であり、デフォルトの本番環境向け基準ではありません。Huawei は API 26 Beta2、RC、Release を公開していません。HarmonyOS 7、API 26、HDC 2026、プレビュー対応、または Beta1 の機能についてユーザーから尋ねられた場合に限り、API 26 の機能に言及してください。本番コードでは、プロジェクトが明示的に API 26 プレビューを対象としている場合を除き、API 24 Release を優先してください。
開発キットの基準: HarmonyOS SDK 26.0.0 Beta1(OpenHarmony SDK Ohos_sdk_public 26.0.0.23、API Version 26.0.0 Beta1)および DevEco Studio 26.0.0 Beta1(26.0.0.461)。ツールチェーン:HarmonyOS Emulator 26.0.0.200、Hvigor/hvigorw 6.26.1、ohpm 26.0.0.410、Node.js 24.14.1、hstack 6.0.0、compileSdkVersion: "26.0.0"、targetSdkVersion: "4.0.0(10)~26.0.0"。
バージョン番号のルール: API 26.0.0 以降、HarmonyOS 開発キットの API バージョンには、従来の X.Y.Z(N) 形式ではなく、SemVer(X.Y.Z)が使用されます。X は大幅な機能追加や適応に影響する変更を伴うメジャーバージョン、Y は新機能を含むマイナーバージョン、Z は互換性を保つ修正/小規模な改善を表します。
API 26 Beta1 の主な変更点:
- Ability Kit — AgentCard をサポートしました。ArkTS スクリプトベースのアプリ Skill 開発、パッケージ名とクローンインデックスによるアプリ名の検索、スクリプト管理用 ArkTS API、
ModularObjectExtensionAbility用 C API に対応しました。 - Accessibility Kit — 高齢者に配慮したアプリ体験を実現するため、システムのケアモードと連携します。
- Accessory Kit — アクセサリーの起動、システムサービスとの連携、オンデマンドスケジューリング、安全な信頼管理を実現する新しい Kit です。
- Account Kit —
LoginWithHuaweiIDButtonが、カスタムの多言語テキストと読み込みアニメーションに対応しました。 - AR Engine — カメラのフラッシュ制御用 C API、プレビュー映像からの画像取得用 ArkTS API、3D Gaussian モデルの読み込み、外部カメラおよびセンサーデータ用 ArkTS/C API に対応しました。
- ArkUI / UI Design — Toggle、Tips、Toast、ダイアログ、メニュー、ポップアップ、カスタムダイアログ、ハーフモーダル、Popup のシステムマテリアル設定、コンポーネント単位の没入感ある光の表現、
@Reusable/@ReusableV2のグローバル再利用プール、標準フローティングウィンドウ、タイトル領域のカスタマイズの更新に対応しました。 - ArkWeb — Chromium カーネルを 132 から 144 にアップグレードし、新しいセキュリティ設定オプションを追加しました。
- AVCodec Kit — H.265 ハードウェアエンコーダーの CBRHQ モード、Audio Vivid エンコードおよび C API に対応しました。
- AVSession Kit — シナリオ固有の AVSession メタデータ用に、新しい追加キーの列挙値を追加しました。
- Background Tasks Kit — リマインダーのカウントダウンインスタンスに
repeatIntervalとrepeatCountを追加しました。 - Core File Kit —
UNCACHEオープンオプション、再帰的なlistFileExt、mmap ベースのファイルマッピング、アプリのサンドボックスディレクトリをシステムから参照可能にする機能に対応しました。 - Core Vision Kit — 画像の超解像と、画像を対象とした意味ベースのテキスト検索に対応しました。
- Data Augmentation Kit — メールの分類、要約、ToDo 抽出を行うインテリジェンスハンドラーを追加しました。
- Device Security Kit — Star Shield の機密リスク制御エンジンの強化、リスク制御認証情報の統合、カメラ/マイク/位置情報のプライバシーポリシー制御、ファイルイベントのサブスクリプションとフィルタリングに対応しました。
- Driver Development Kit — 外部 USB ハブの照会とユーザーモードドライバーの開発に対応しました。
- Enterprise Data Guard Kit — ファイル分類ポリシー API
getPolicyとisKiaを追加しました。 - Enterprise Space Kit — デュアルスペースの状態を照会し、ワークスペースがエンタープライズスペースかどうかを判定できるようになりました。
- FAST Kit — 実数 FFT と逆 FFT、インテリジェントな系列予測に対応しました。
- Graphics Accelerate Kit — 起動時の体験を向上させる、ゲームの事前起動機能を追加しました。
- Image Kit — GIF/JFIF/TIFF/PNG/AVIS のメタデータクラスと XMP メタデータに対応しました。
- Input Kit — キーボードおよびマウスの入力イベント注入に対応しました。
- Live View Kit — 割合を示す進捗リング付きの補助領域テンプレートを追加しました。
- NDK / JSVM — 外部メモリから
ArrayBufferを作成できるようになりました。 - NearLink Kit —
startScanが、検出可能な近くの NearLink デバイスをすべてスキャンするようになりました。 - Network Boost Kit —
netBoost.setDataFlowDescが、5 タプルデータからフローの説明を設定できるようになりました。 - Notification Kit — 通知の管理と表示を強化し、ハーフモーダルの通知設定画面への導線を追加しました。
- Online Authentication Kit — DID(分散型識別子)の鍵生成、認証情報のインポート/照会/削除、データ署名に対応しました。
- PDF Kit — 複数ページにまたがる指定領域を 1 枚の画像に変換できるようになりました。
- Performance Analysis Kit — グレーリリース時の障害ログ収集と、HiAppEvent のアプリフリーズ警告サブスクリプションに対応しました。
- Preview Kit — ファイルの高速スキャン、プリロード戦略のカスタマイズ、利用可能性の照会、ファイル操作イベントの報告に対応しました。
- Push Kit — Live View のプッシュメッセージが Wearable デバイスに対応しました。
- Remote Communication Kit — HTTP バージョン選択用の
HttpVersionSelectCallback、ストリーミングアップロード用のHMS_Rcp_SetRequestGetDataCallback()、フォームの順序指定用のHMS_Rcp_SetFormOrder()、QUIC C API に対応しました。 - Scenario Fusion Kit — シナリオ共有の Button が、画像、動画、テキストに対応しました。
- Share Kit — スマートフォンから PC/2in1/タブレットへのタップ共有で、受信側にタップ位置を表示できるようになりました。
- Spatial Recon Kit — 3DGS ガウシアン編集と、1 枚の写真からの空間写真生成に対応しました。
- Scan Kit — 現在のデバイスで、デフォルトまたはカスタムのスキャン UI がサポートされているかを照会できるようになりました。 適応時に確認すべき API 26 の動作および UX の変更:
- Ability Kit — 公開パッケージ変更共通イベント(
COMMON_EVENT_PACKAGE_ADDED、REMOVED、CHANGED、CACHE_CLEARED)により、targetSdkVersion >= 26.0.0の場合、In-House アプリ向けの制御が追加されます。In-House アプリは、サードパーティーのリスナー向けにapp.json5でallowListenBundleChangedEventを設定する必要があります。 - ArkTS / JSVM — Chromium/V8 コアが 132 から 144 にアップグレードされます。非同期関数の型検出が修正され、Wasm jitless のデフォルトの動作が変更されます。また、XML の解析時に
fastConvertToJSObjectが兄弟のテキストノードを保持するようになります。 - ArkUI —
NodeAdapter.onAttachToNode、マウスのrawDeltaX/rawDeltaY、LayoutPolicy.matchParent、EmbeddedComponentのフォーカス、WithTheme、ホームNavDestinationのqueryNavDestinationInfo/onResult、NODE_SWIPER_EVENT_ON_CONTENT_DID_SCROLL、シャドウのぼかし半径の動作には、適応に影響する変更があります。 - 権限 —
ohos.permission.READ_IMAGEVIDEO、getUidRxBytes、getUidTxBytes、および一般的な権限ポリシーの動作が、API 26 のルールに基づいて変更されます。 - UX — フォームコントロール(Button/Button スタイルの Toggle/Select/Chip/ChipGroup)の最小タッチターゲットが 28vp から 32vp に変更されます。組み込みのテキストの改行処理と、小さな文字体系の行の高さが最適化されます。Dialog、Toast、AlphabetIndexer、テキスト選択メニューでは、システムのイマーシブマテリアルがデフォルトで有効になります。全体で無効にするには、
metadataの名前をohos.arkui.UIMaterial.state、値をdisableに設定します。コンポーネントごとに無効にするには、uiMaterial.Material.emptyを使用します。
API 26 V2 の動作適用範囲に関する注意事項(公式ドキュメント更新日:2026/06/17):
- 常に有効: JSVM/Chromium の 132 から 144 へのアップグレード、非同期関数の型検出、
fastConvertToJSObjectによる兄弟テキストの保持、ArkUI のrawDeltaX/rawDeltaY、Stage 専用の ArkUI API 制約、ホームNavDestinationのqueryNavDestinationInfo/onResult、@ReusableV2の動的再利用識別子、ArkWeb Chromium の 132 から 144 へのアップグレード、READ_IMAGEVIDEO権限の動作、notofonts の小さな文字体系向けフォント更新。 targetSdkVersion >= 26.0.0の場合にのみ有効: In-House のパッケージ変更イベント制御、JSVM Wasm jitless のデフォルトの動作、NodeAdapter.onAttachToNode、先頭にCustomSpan/ImageAttachmentがある属性付き文字列の段落スタイル、LayoutPolicy.matchParent、EmbeddedComponentのフォーカス、WithTheme、NODE_SWIPER_EVENT_ON_CONTENT_DID_SCROLL、コンポーネントのシャドウのぼかし半径、getUidRxBytes/getUidTxBytes、権限ポリシーの変更、フォームコントロールの 32vp タッチターゲット、組み込みテキストスタイルの最適化、イマーシブマテリアルのデフォルト設定、ハーフモーダルの中央配置ダイアログの最大高さ。 DevEco Studio 26.0.0 Beta1 の追加機能:- AI コーディング:カスタムエージェントのトークン使用量表示、会話のロールバック、MCP Market ツール/LSP ツール/ArkTS および C++ のセマンティックコード検索を使ったプロジェクト Q&A、「ファイルコメント」や「パラメーター検証」などの組み込み Inline Chat コマンド、
UI Verificationツール、カスタムコマンド。 - 編集/デバッグ:API 26.0.0 プロジェクト、モジュールの読み込み/アンロード、ArkUI の状態変数関連ビューアー、Code Scanner のリソースリークチェック、カスタム Clang-Tidy、ACL 権限リクエスト、8 ブレークポイントプレビュー、Car マルチスクリーンエミュレーター、シナリオシミュレーション、リモートエミュレーター制御、ネイティブデバッグの起動高速化、デバイス投影、SQL ハイライト付きデータベースデバッグ、ダンプファイルのスタック解析、HiLog タグフィルタリング、AppAnalyzer レポート診断、OOM/アプリフリーズ/リソースリークの診断。
- パフォーマンス分析:Memory レーンに、Malloc 割り当てと ArkWeb レンダープロセスの JS ヒープ使用量を示す ArkWeb PA および JS Heap サブレーンが追加され、VMA 数と PSS メモリの最小値/最大値/平均値を示す Statistics タブも追加されます。
- ビルド/リリース:
apiCompatibilityCheck、tsImportSoCheck、モジュールのnativeLib.enableSoDirCollection、syncNative、Hvigor のgetAllDependencyInfo、AppGallery パッケージの再署名、Linux エミュレーターのサポート、ohpmrc のauto_skip_install、metadata_cache_effective、metadata_cache、およびバージョンを厳密に指定したメタデータクエリ。 - 互換性の変更:DevEco Studio と Command Line Tools では、Node.js が 18 から 24 にアップグレードされます。カスタム Hvigor/ohpm/ohpm-repo プラグインは Node.js 24 への対応が必要です。
ohpm-repo 5.5.1はnode-fetchへの依存を解消しています。このバンドル済み依存関係に依存していたプラグインは、代替のものに置き換えるか、[email protected]を独自にインストールする必要があります。
DevEco Testing 26.0.0 Beta1:
- 安定性テストでは、指定したエントリーポイントを対象に安定性の問題を発生させることができ、メモリリーク検出の対象範囲も拡大されます。
- UX テストでは、ストレートスクリーン端末と折りたたみ式端末を対象に、複数デバイスのレイアウトを比較できます。
- テストサービスのマトリックスには、ローカルアプリ一覧の事前チェック、パフォーマンスベースライン/モニタリング、安定性ベースライン、メモリリークテスト、UX ベースラインと複数デバイスのレイアウト比較、セキュリティベースライン、電力ベースライン、機能体験ベースライン、探索的テスト、回帰テスト、デバイス投影、UIViewer、アプリグラフ管理、パフォーマンスレポートの自動分析、レポート比較が含まれます。
HarmonyOS AI 開発ツールと主な機能(2026/06 に公式発表):
- DevEco Code — HarmonyOS に特化した AI コーディングエージェントで、計画立案、コード生成、ビルド/実行、デバイスログ、UI 検証、ArkTS チェック、ナレッジ検索、デバッグ、反復的な修正に対応します。DevEco Studio を置き換えるのではなく、補完するものです。
- DevEco CLI — プロジェクト作成、構文チェック、ビルド、デバイスでの実行/デバッグ、その他の HarmonyOS エンジニアリング操作を、エージェントから利用しやすい形でコマンドライン経由で実行できます。サードパーティ製コーディングエージェント、自動化、CI/CD との連携に使用します。
- CodeGenie — DevEco Studio の AI アシスタント/プラグインは、引き続き独立した製品として提供されます。Huawei は、本番ツールチェーン向けに CodeGenie 6.1.1 Release、Command Line Tools 6.1.1 Release、DevEco Studio 6.1.1 Release を掲載しています。API 26 ツールチェーンは引き続き Beta1 です。
- Agent Framework Kit — アプリからシステムエージェントの組み合わせを起動するための UI コントロールを提供します。アプリのインテントを宣言する Intents Kit、アプリの機能を公開する ArkTS スクリプトベースのアプリ Skills、デバイス上の A2A(エージェント間通信)とは区別してください。
- HarmonyOS 7 の体験領域 — 空間オーディオ処理ノード、アプリ/ゲームのクイックスタート、コールドスタート時のネットワーク事前接続、QUIC と弱いネットワーク環境でのライブストリーミング最適化、LTPO 可変フレームレートが、プラットフォームの主な機能として紹介されています。一般提供 API として案内する前に、インストール済みの API 26 SDK とデバイスの対応状況を確認してください。
- API 26 クラウドデバッグ — AGC リモートデバイスのクラウドデバッグでは、API 26 またはシステムバージョン
7.0.0.23でデバイスを絞り込み、早期の互換性検証を行えます。 ツールの選択、機能の境界、および回答のルールについては、references/ai-development-tools.mdをご覧ください。
公式ドキュメントの更新(2026/06/12)
Huawei のリリースドキュメントでは 26.0.0 Beta1 以降の新しい SDK は追加されていませんが、ドキュメントの変更履歴には、回答で取り上げる価値のある開発者向けガイドがいくつか追加されています。
- Ability Kit のアプリライフサイクル — アプリのライフサイクル状態の変化、ウィンドウモードや開始ページのパスなどの起動設定、クイックスタート起動、ユーザー/アプリ/システムによる終了、アクティブ/パッシブな再起動フローを網羅する新しいガイドが追加されました。
- Ark Intelligence Development Framework — フレームワークの概要、Intent Framework の開発、ArkTS スクリプトベースのアプリ Skill 開発、Xiaoyi などのシステムエージェントにアプリの機能を公開するためのデバイス側 A2A フレームワーク開発を扱う新しいガイドが追加されました。
- ウィンドウ管理 — ウィンドウの種類、モード、レイアウト、フォーカスを含む 20 件以上の開発シナリオが再編成・拡充され、
hidumperを使って一般的なウィンドウのログや問題を特定する新しいガイドも追加されました。 - ArkTS Sendable の実践 — ArkTS で Sendable オブジェクトを操作するために TurboTransJSON を使用する、新しい Sendable 移行/実践ガイドが追加されました。
- FA モデルのドキュメントの移動 — FA モデルは、主に軽量スマートウェアラブル向けの場合を除き、推奨されるアプリモデルではなくなりました。一般的な HarmonyOS アプリのドキュメントには Stage モデルの内容が残り、FA モデルのガイダンスは軽量ウェアラブルアプリ開発の基本の下に移動しました。
公式ドキュメントの更新(2026/07)
HarmonyOS 7 の新しい SDK は公開されていませんが、いくつかの公式ガイドが実装やレビュー時の回答に影響するようになりました。
- ネイティブ C API の互換性 — SDK API 22 以降では、
compatibleSdkVersionより新しい API にAPIAVAILABLEを使った弱参照を利用できます。正しいリンク依存関係、古いデバイスに存在しないライブラリの弱参照ライブラリ設定、実行時フォールバック、最も古い互換デバイスと新しい API 対応デバイスの両方でのテストが必須です。コンパイルできるだけでは不十分です。依存関係の不足は実行時にのみ失敗する場合があるためです。references/native-api-compatibility.mdをご覧ください。 - Linux CI パイプライン — JDK 17 と対応する Command Line Tools を使用し、同梱されている Node.js を優先してください。プロジェクト/モジュールの依存関係をインストールし、Hvigor を
--no-daemon付きで実行し、署名シークレットを保護し、署名済み HAP を HDC でインストールしてください。また、Windows や macOS では見過ごされるファイル名/インポートの不一致を検出するため、caseSensitiveCheckを有効にしてください。references/build-sign-release.mdをご覧ください。 - ArkTS のリーク検出 —
@ohos.hiviewdfx.jsLeakWatcherは主に開発時に使用してください。本番環境での使用は、小規模な段階的リリースの対象に限定してください。疑われるレイヤーに応じて、JS Heap スナップショット、HWASan/AddrSanitizer、AppFreeze、HiAppEvent と組み合わせてください。references/performance.mdをご覧ください。 - コンテナーに応じた ArkUI のレスポンシブ対応 — レイアウトをウィンドウではなく、包含するコンポーネントに応じて変化させる必要がある場合は、
ContainerReaderを使用してください。API 26 では、@Reusable/@ReusableV2向けの集中管理型再利用プールについても説明されています。再利用には、明示的なライフサイクルのクリーンアップと状態のリセットが必要です。references/arkui-components.mdをご覧ください。
プロジェクト構成(Stage モデル)
MyApp/
├─ AppScope/
│ ├─ app.json5 # global app config (bundleName, icon, label)
│ └─ resources/ # shared resources
├─ entry/ # main HAP (entry module)
│ ├─ src/main/
│ │ ├─ ets/
│ │ │ ├─ entryability/EntryAbility.ets # UIAbility subclass
│ │ │ ├─ entrybackupability/
│ │ │ └─ pages/Index.ets # ArkUI pages
│ │ ├─ resources/ # strings, colors, media, profiles
│ │ └─ module.json5 # module config, abilities, permissions
│ └─ build-profile.json5
├─ oh-package.json5 # ohpm dependencies
└─ build-profile.json5 # project-level
Stage モデル — 中核となる概念
コンポーネントの種類
- UIAbility — UI を持ち、ユーザー操作を処理します。エントリーポイントです。タスクごとに 1 つのインスタンスがあります。
- ExtensionAbility — バックグラウンド/拡張のシナリオに使用します。
ServiceExtensionAbility— バックグラウンドサービス(システムアプリ)FormExtensionAbility— ホーム画面ウィジェット(服务卡片)WorkSchedulerExtensionAbility— 遅延タスクInputMethodExtensionAbility、WallpaperExtensionAbility、BackupExtensionAbilityなど
- AbilityStage — モジュール単位のライフサイクルコンテナー(HAP ごとに 1 つ)
- WindowStage — UIAbility のスコープ内にあるウィンドウコンテナー
UIAbility のライフサイクル
import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { }
onDestroy(): void { }
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => { /* ... */ });
}
onWindowStageDestroy(): void { }
onForeground(): void { }
onBackground(): void { }
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { }
}
別の ability の起動
import { common, Want } from '@kit.AbilityKit';
const ctx = getContext(this) as common.UIAbilityContext;
const want: Want = {
bundleName: 'com.example.app',
abilityName: 'DetailAbility',
parameters: { id: 42 }
};
ctx.startAbility(want);
// or startAbilityForResult for a return value
module.json5 の要点
{
"module": {
"name": "entry",
"type": "entry", // entry | feature | shared
"srcEntry": "./ets/entryability/EntryAbility.ets",
"deviceTypes": ["phone", "tablet", "2in1"],
"abilities": [{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"skills": [{
"actions": ["action.system.home"],
"entities": ["entity.system.home"]
}]
}],
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
]
}
}
ArkTS 言語 — 厳格化ルール
ArkTS = TypeScript から動的なパターンを除き、AOT のパフォーマンス向上のために静的型付けを強化したものです。
禁止事項/TS からの変更点:
- 日常的な回避策としての
any/unknownは使用できません。実際の型を宣言してください - オブジェクトへの動的なプロパティの追加/削除はできません(型指定されていないオブジェクトに対する
obj.foo = barはコンパイルエラーになります) thisを再構成するFunction.prototype.bind、call、applyは使用できません- 構造的型付けによる簡略化はできません。公称クラス/インターフェースを使用してください
- 任意の形状変更に
Object.keys/Object.assignは使用できません - 実際の辞書型マップには
Record<K,V>を使用します - 明示的なフィールドを持つ
class/interfaceを使用します
// Prefer:
class User { constructor(public id: number, public name: string) {} }
// Over:
// const user: any = { id: 1, name: 'A' }
命名規則(公式コーディングスタイルガイド)
| 要素 | 規則 | 例 |
|---|---|---|
| クラス、構造体、列挙型 | UpperCamelCase | PersonInfo、ColorType |
| 変数、パラメーター、メソッド | lowerCamelCase | userName、getUserInfo() |
| 定数 | UPPER_SNAKE_CASE | MAX_VALUE、DEFAULT_TIMEOUT |
| ブール変数 | is、has、can を接頭辞として付ける | isVisible、hasPermission |
フォーマット:インデントは2スペース、1行は最大120文字、K&R形式の波括弧を使用し、if/for/while には必ず {} を使用します。
高パフォーマンス ArkTS ルール(公式ベストプラクティスより)
- 変更されない値には
constを使用します。エンジンの最適化が可能になります - 同じ変数で int と float を混在させないでください。
let n = 1; n = 1.1;はボックス化のオーバーヘッドを引き起こします - 数値計算には TypedArrays(
Int8Array、Float32Array)を使用します - 疎配列は避けてください。
arr[9999] = 0はハッシュテーブルによるストレージを強制し、処理が大幅に遅くなります - 配列内で型を混在させないでください。
[1, "a", 2]は最適化を無効にします - 高頻度ループの外でプロパティの検索結果をキャッシュします
- パフォーマンスが重要なループ内での例外のスローは避け、代わりにセンチネル値を使用します
- ホットパスでのクロージャーを最小限にし、代わりに関数パラメーターで変数を渡します
- Array メソッド(
forEach、map、filter、reduce)を使用します。内部で最適化されています build()は純粋かつ宣言的に保ち、副作用を発生させず、データはaboutToAppear()で読み込みます- キーと値の操作には
Recordの代わりにHashMapを使用します。検索/挿入がより高速です - 多段階の間接エクスポートを減らし、
export { foo } from './module'のような直接エクスポートを優先します - 起動時に不要なモジュールには遅延インポート(
import lazy { Foo } from './heavy')を使用します
ArkUI — 宣言型 UI
カスタムコンポーネントの基本
@Entry // marks route/page entry
@Component
struct Index {
@State count: number = 0;
build() {
Column({ space: 12 }) {
Text(`Count: ${this.count}`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
Button('Increment')
.onClick(() => { this.count++; })
}
.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
コンポーネントのライフサイクルコールバック
すべての @Component:
| コールバック | タイミング | 備考 |
|---|---|---|
aboutToAppear() | 作成後、build() の前 | ここで状態を変更できます。変更は最初の build() に適用されます |
onDidBuild() | build() 完了後 | 状態を変更したり、animateTo() を呼び出したりしないでください。API 12 以降 |
aboutToDisappear() | 破棄前 | 状態(特に @Link)を変更しないでください。async/await は使用できません |
aboutToReuse(params) | 再利用可能なコンポーネントがキャッシュから再追加されたとき | 新しいパラメーターで状態を更新します。API 10 以降 |
aboutToRecycle() | コンポーネントが再利用キャッシュに移動するとき | 大きなリソースを解放します。API 10 以降 |
@Entry のみ:
| コールバック | タイミング |
|---|---|
onPageShow() | ページが表示されるたび |
onPageHide() | ページが非表示になるたび |
onBackPress(): boolean | ユーザーが「戻る」をタップしたとき。既定の動作を上書きするには true を返します |
実行順序(コールドスタート時):
Parent aboutToAppear → Parent build → Parent onDidBuild → Child aboutToAppear → Child build → Child onDidBuild → onPageShow
レイアウトコンテナー
| コンテナー | 使用する場面 | パフォーマンス |
|---|---|---|
Column / Row | 直線的な配置 | 最高 — 1回のパスでレイアウト |
Stack | 重ね合わせ/積み重ね | 良好 |
Flex | アイテムを伸縮させる必要がある場合 | 遅め — flexGrow/flexShrink のために追加のパスが必要 |
RelativeContainer | 複雑なレイアウト、深いネストの回避 | 良好 — フラットな構造 |
GridRow / GridCol | 複数デバイスに対応するレスポンシブグリッド | 良好 |
List | リサイクル機能を備えたスクロール可能なリスト | 長いリストに最適(LazyForEach を使用) |
Column/Row の配置:
- 主軸:
justifyContent(FlexAlign.Start | .Center | .End | .SpaceBetween | .SpaceAround | .SpaceEvenly) - 交差軸: Column →
alignItems(HorizontalAlign.Start | .Center | .End)、Row →alignItems(VerticalAlign.Top | .Center | .Bottom)
Stack: alignContent は9つの位置(TopStart、Top、TopEnd、Start、Center、End、BottomStart、Bottom、BottomEnd)を指定できます。zIndex はレイヤーの順序を制御します。
Flex と Column/Row の比較: Flex では flexShrink/flexGrow のためにレイアウトを再実行する必要があります。Flex の動作が必要でない場合は、常に Column/Row を優先してください。
RelativeContainer: コンテナー自体のアンカー ID には __container__ を使用します。各子要素には .id() が必要です。.alignRules({ top: { anchor: 'id', align: VerticalAlign.Bottom } }) を設定します。
Blank(): Row/Column の残りのスペースを埋めます。「ラベル ... 値」のレイアウトに使用します: Row() { Text('Name'); Blank(); Text('Value') }。
displayPriority: コンテナーが縮小すると、優先度の低い子要素は自動的に非表示になります。Row() { A().displayPriority(1); B().displayPriority(3); C().displayPriority(1) } — A と C が最初に非表示になります。
layoutWeight: 比例サイズを指定します — Row() { Column().layoutWeight(1); Column().layoutWeight(2) } は 1:2 の比率になります。
AttributeModifier — 再利用可能なスタイルオブジェクト:
class PrimaryButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance.width('100%').height(48).fontSize(16).fontColor(Color.White).backgroundColor('#007DFF');
}
applyPressedAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#0056B3');
}
}
// Usage:
Button('Submit').attributeModifier(new PrimaryButtonModifier())
パフォーマンスが重要なパターン
大きなリストには LazyForEach(表示中のアイテムのみをレンダリング):
class MyDataSource implements IDataSource {
private data: string[] = []
totalCount(): number { return this.data.length }
getData(index: number): string { return this.data[index] }
registerDataChangeListener(listener: DataChangeListener): void { /* ... */ }
unregisterDataChangeListener(listener: DataChangeListener): void { /* ... */ }
}
List() {
LazyForEach(this.dataSource, (item: string, index: number) => {
ListItem() { Text(item) }
}, (item: string) => item) // key generator — MUST produce unique keys
}
.cachedCount(5) // preload 5 items off-screen
@Reusable コンポーネント(コンポーネント作成が69%高速化):
@Reusable
@Component
struct MyListItem {
@State message: Message = new Message('default')
aboutToReuse(params: Record<string, ESObject>) {
this.message = params.message as Message
}
aboutToRecycle() { /* release heavy resources */ }
build() { Text(this.message.value) }
}
ルール: 同じ親の中でのみ動作します。@Reusable の中に @Reusable をネストしないでください。LazyForEach と組み合わせてください。
レイアウトのパフォーマンスルール:
- ネストは最大3階層にします — 階層ごとにレイアウトコストが増加します
.visibility()よりif/elseを使用します — 非表示のコンポーネントもレイアウトに参加しますRelativeContainerを使用して、深くネストされた Row/Column/Flex の階層をフラット化します(ドキュメントで26%の改善が報告されています)Scroll内のListには明示的なサイズを設定します — 設定しないと、すべての子要素が一度に読み込まれます- 頻繁に変化するデータには
@StorageLinkを使用しないでください — すべてのサブスクライバーに変更が伝播します
onVisibleAreaChange — コンポーネントがビューポートに入る/出るときにロジックを実行します:
Image(item.url)
.onVisibleAreaChange([0.0, 1.0], (isVisible: boolean, currentRatio: number) => {
if (isVisible && currentRatio >= 1.0) { this.loadHighRes(); } // fully visible
if (!isVisible) { this.releaseImage(); } // off screen
})
使用例: 画像の遅延読み込み、スクロール時の動画の自動再生/一時停止、表示回数の分析。
アニメーション
明示的なアニメーション(animateTo) — クロージャー内の状態変更をアニメーション化します:
this.getUIContext()?.animateTo({
duration: 300,
curve: Curve.EaseOut,
onFinish: () => { console.info('done') }
}, () => {
this.width = 200 // this change animates
})
プロパティアニメーション(.animation()) — 暗黙的に適用され、直前の属性に作用します:
Text('Hello')
.width(this.myWidth)
.animation({ duration: 500, curve: Curve.EaseIn })
カーブの値: Curve.Linear | .Ease | .EaseIn | .EaseOut | .EaseInOut | .FastOutSlowIn | .Friction | .Sharp | .Smooth
スプリングカーブ(文字列): 'spring(velocity,mass,stiffness,damping)'、'springMotion(response,dampingFraction)'、'responsiveSpringMotion(response,dampingFraction)'
共有要素のトランジション:
// Bind same geometryTransition ID on source and target
Image($r('app.media.photo')).geometryTransition('picture')
// Wrap state change in animateTo
this.getUIContext()?.animateTo({ duration: 300 }, () => { this.isExpanded = !this.isExpanded })
キーフレームアニメーション(keyframeAnimateTo) — 複数ステップのシーケンス:
this.getUIContext()?.keyframeAnimateTo({ iterations: 2 }, [
{ duration: 100, event: () => { this.translateX = 10; } },
{ duration: 100, event: () => { this.translateX = -10; } },
{ duration: 100, event: () => { this.translateX = 0; } },
]);
アニメーションのパフォーマンスに関するヒント: レイアウトプロパティ(width/height/margin)よりも変換プロパティ(scale/translate/rotate/opacity)を優先してください — 変換ではレイアウトが再実行されません。
Tabs — 下部/上部ナビゲーション
@Entry @Component
struct MainPage {
@State currentIndex: number = 0;
@Builder tabBuilder(index: number, title: string, icon: Resource) {
Column() {
SymbolGlyph(icon).fontSize(24)
.fontColor([this.currentIndex === index ? '#007DFF' : '#99000000'])
Text(title).fontSize(10).margin({ top: 4 })
.fontColor(this.currentIndex === index ? '#007DFF' : '#99000000')
}.justifyContent(FlexAlign.Center).height('100%').width('100%')
}
build() {
Tabs({ barPosition: BarPosition.End }) { // BarPosition.Start for top
TabContent() { HomePage() }
.tabBar(this.tabBuilder(0, 'Home', $r('sys.symbol.house')))
TabContent() { MinePage() }
.tabBar(this.tabBuilder(1, 'Me', $r('sys.symbol.person')))
}
.barHeight(56)
.onChange((index) => { this.currentIndex = index; })
.scrollable(false) // disable swipe between tabs
}
}
ガラス風のぼかしを使ったタブバー: .barOverlap(true).barBackgroundBlurStyle(BlurStyle.Thin)。
Swiper — カルーセル/バナー
Swiper() {
ForEach(this.banners, (item: BannerItem) => {
Image(item.url).width('100%').height(180).borderRadius(12)
})
}
.loop(true)
.autoPlay(true)
.interval(3000)
.indicator(new DotIndicator()
.color('#33000000').selectedColor('#007DFF')
.itemWidth(8).selectedItemWidth(16))
WaterFlow — Pinterest 風レイアウト
WaterFlow({ scroller: this.scroller }) {
LazyForEach(this.dataSource, (item: CardItem) => {
FlowItem() {
Column() {
Image(item.image).width('100%').borderRadius(8)
Text(item.title).fontSize(14).padding(8)
}
}
}, (item: CardItem) => item.id)
}
.columnsTemplate('1fr 1fr') // 2 columns
.columnsGap(8)
.rowsGap(8)
.cachedCount(10)
Grid — 固定グリッドレイアウト
Grid() {
ForEach(this.items, (item: GridItemData) => {
GridItem() {
Column() {
Image(item.icon).width(40).height(40)
Text(item.name).fontSize(12).margin({ top: 4 })
}
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr') // 4 columns
.rowsGap(12)
.columnsGap(12)
.height(200)
TextInput / TextArea
TextInput({ placeholder: 'Enter username' })
.type(InputType.Normal) // .Email, .Number, .Password, .PhoneNumber
.maxLength(20)
.onChange((value: string) => { this.username = value; })
.onSubmit((enterKey: EnterKeyType) => { /* handle submit */ })
TextArea({ placeholder: 'Enter description', text: $$this.desc })
.maxLength(200)
.showCounter(true) // character count indicator
$$ による双方向バインディング: TextInput({ text: $$this.value }) — onChange は不要です。
Router — 基本的なページナビゲーション
import { router } from '@kit.ArkUI';
// Push to new page (with params)
router.pushUrl({
url: 'pages/Detail',
params: { id: '123', title: 'Hello' }
});
// Get params on target page
const params = router.getParams() as Record<string, string>;
// Go back
router.back();
// Replace current page (no back stack)
router.replaceUrl({ url: 'pages/Login' });
Note: Navigation ベースのアプリでは、Router よりも
NavPathStack.pushPath()を優先してください。
AlertDialog / Toast
// Alert dialog
AlertDialog.show({
title: 'Confirm',
message: 'Delete this item?',
primaryButton: { value: 'Cancel', action: () => {} },
secondaryButton: { value: 'Delete', fontColor: Color.Red,
action: () => { this.deleteItem(); }
},
});
// Toast
this.getUIContext().getPromptAction().showToast({
message: 'Operation successful',
duration: 2000,
});
共通フォームコンポーネント — クイックリファレンス
| コンポーネント | 主なプロパティ | 例 |
|---|---|---|
Checkbox | select、onChange((val: boolean) => {}) | Checkbox({ name: 'agree' }).select(this.agreed) |
Toggle | type(ToggleType.Switch)、isOn、onChange | Toggle({ type: ToggleType.Switch, isOn: $$this.on }) |
Radio | value、group、checked、onChange | Radio({ value: 'male', group: 'gender' }).checked(true) |
Select | options: SelectOption[]、selected、value、onSelect | Select([{value:'A'},{value:'B'}]).selected(0) |
Slider | value、min、max、step、onChange | Slider({ value: $$this.val, min: 0, max: 100 }) |
DatePicker | start、end、selected、onChange | DatePicker({ selected: this.date }).onChange((v) => {}) |
TimePicker | selected、useMilitaryTime、onChange | TimePicker({ selected: this.time }) |
Search | value、placeholder、onSubmit、onChange | Search({ value: $$this.keyword, placeholder: 'Search' }) |
Progress | value、total、type(ProgressType.Linear) | Progress({ value: 60, total: 100 }) |
LoadingProgress | — | LoadingProgress().width(48).color('#007DFF') |
EventHub — UIAbility とページ間の通信
// In UIAbility — emit event
this.context.eventHub.emit('dataReady', { items: [...] });
// In page — subscribe
const context = getContext(this) as common.UIAbilityContext;
context.eventHub.on('dataReady', (data: Record<string, ESObject>) => {
this.items = data.items;
});
// Unsubscribe
context.eventHub.off('dataReady');
HarmonyOS 6.0 の視覚効果(没入型の光感視覚効果/リキッドガラス)
HarmonyOS 6.0(API 23)では、システムレベルの「没入型光感視覚効果」が導入されています。ユーザーは「設定」→「デスクトップとパーソナライズ」→「没入型光効果」(強/均衡/弱)から有効にできます。開発者は、以下の ArkUI 属性を使用して同様の効果を実現できます。
BlurStyle 列挙型(API 9~11):
| 名前 | 対応開始 | レベル |
|---|---|---|
Thin / Regular / Thick | API 9 | マテリアルのぼかし |
BACKGROUND_THIN / BACKGROUND_REGULAR / BACKGROUND_THICK / BACKGROUND_ULTRA_THICK | API 10 | 被写界深度(最小→最大) |
COMPONENT_ULTRA_THIN / COMPONENT_THIN / COMPONENT_REGULAR / COMPONENT_THICK / COMPONENT_ULTRA_THICK | API 11 | コンポーネントレベルのマテリアル |
NONE | API 10 | ぼかしなし |
backgroundBlurStyle(API 9 以降):
Column() { /* content */ }
.backgroundBlurStyle(BlurStyle.Thin, {
colorMode: ThemeColorMode.LIGHT, // SYSTEM | LIGHT | DARK
adaptiveColor: AdaptiveColor.DEFAULT, // DEFAULT | AVERAGE
scale: 1.0 // 0.0–1.0 (blur intensity)
})
foregroundBlurStyle(API 10 以降):
Image($r('app.media.photo'))
.foregroundBlurStyle(BlurStyle.Regular)
backgroundEffect(API 11 以降)— 詳細な制御:
Column() { /* content */ }
.backgroundEffect({
radius: 20, // blur radius
saturation: 15, // [0, 50] recommended
brightness: 0.6, // [0, 2] recommended
color: '#80FFFFFF' // mask color
})
blur / backdropBlur(API 7 以降)— 数値による半径指定:
Column() { /* content */ }
.backdropBlur(20, { grayscale: [30, 50] }) // background blur
.blur(10) // foreground blur
backgroundBrightness(API 12 以降):
Column() { /* content */ }
.backgroundBrightness({ rate: 0.5, lightUpDegree: 0.2 })
視覚効果フィルター(API 12 以降):
import { uiEffect } from '@kit.ArkGraphics2D';
const blurFilter = uiEffect.createFilter().blur(10);
Column() { /* content */ }
.backgroundFilter(blurFilter) // background filter
.foregroundFilter(blurFilter) // content filter
pointLight(API 11 以降、System API のみ — サードパーティアプリでは利用できません):
// System apps only! Supports: Image, Column, Flex, Row, Stack, Button, Toggle
Flex()
.pointLight({
lightSource: { positionX: '50%', positionY: '50%', positionZ: 80, intensity: 2, color: Color.White },
illuminated: IlluminatedType.BORDER, // NONE | BORDER | CONTENT | BORDER_CONTENT
bloom: 0.5 // luminous intensity 0–1
})
1 つのコンポーネントを最大 12 個の光源で照らすことができます。HarmonyOS 6.0 では、デュアルエッジのフローライト効果と UV 背景フローライト効果が追加されています。
systemMaterialEffect(HDS レイヤー、API 23 以降、HarmonyOS 専用 SDK):
import { hdsMaterial } from '@kit.ArkUI';
Column() { /* content */ }
.systemMaterialEffect({
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
})
注:hdsMaterial はクローズドソースの HarmonyOS Design System(HDS)の一部であり、OpenHarmony には含まれていません。HarmonyOS 6.0 SDK(API 23 以降)が必要です。
状態管理デコレーター
| デコレーター | スコープ | 目的 |
|---|---|---|
@State | コンポーネント内 | 所有する変更可能な状態。再レンダリングをトリガーします |
@Prop | 親 → 子 | 一方向のコピー(子がローカルコピーを持ちます) |
@Link | 親 ↔ 子 | 双方向バインディング(渡すときは $var を使用します) |
@Provide / @Consume | 祖先 → 子孫 | キーによる階層をまたいだ暗黙的なバインディング |
@Observed(クラス)+ @ObjectLink(プロパティ) | クラスインスタンス | クラスのプロパティの変更を監視します |
@Watch('handler') | 上記のいずれか | 値の変更時にコールバックを実行します |
@StorageLink / @StorageProp | アプリ全体の AppStorage | グローバルなリアクティブ状態 |
@LocalStorageLink / @LocalStorageProp | ページ単位 | スコープ付きのリアクティブ状態 |
@Link の受け渡し:
@Entry @Component struct Parent {
@State val: number = 0;
build() { Child({ val: $val }) } // $ prefix for @Link
}
@Component struct Child {
@Link val: number;
build() { Button(`${this.val}`).onClick(() => this.val++) }
}
クラスオブジェクトの監視:
@Observed class Task { constructor(public title: string, public done: boolean) {} }
@Component struct TaskRow {
@ObjectLink task: Task; // re-renders when task.title/done changes
build() { Text(this.task.title) }
}
@Observed インスタンスの配列では、行コンポーネントで @ObjectLink が必要です。親の @State tasks: Task[] が反応するのは配列の変更(push/splice/再代入)のみで、各アイテムの変更には反応しません。
状態管理のパフォーマンスに関するルール(公式ドキュメントより):
- 状態のスコープを最小限にします。UI に直接影響する変数のみを
@Stateにします @Propは更新のたびにディープコピーを作成します。大きなオブジェクトには、@Link(参照渡し)または@ObjectLinkを優先します- コンポーネント間の通信には
@Linkを推奨します。不要な再レンダリングを防げます - ネストしたオブジェクトには
@Observed+@ObjectLinkを使用し、プロパティをきめ細かく監視します @ObjectLinkは読み取り専用です。オブジェクト全体を再代入できません(this.task = new Task()とするとバインディングが壊れます)- 頻繁に変化するデータには
@StorageLinkを使用しないでください。グローバル状態の変更はすべてのサブスクライバーに伝播します - 監視の深さ(V1):
@State/@Prop/@Linkが監視するのは第 1 階層のプロパティのみです。ネストされた変更は検出されません。配列では、push/splice/再代入/length のみが対象となり、アイテムの変更は対象外です。
V2 状態デコレーター(API 12 以降、API 23 から安定版 — 新しいコードで推奨)
HarmonyOS 6.1(API 23)で、V2 デコレーターは実験段階から安定版になりました。公式推奨:新規プロジェクトでは V2 へ移行してください。
| V1 | V2 の置き換え | 変更点 |
|---|---|---|
@Component | @ComponentV2 | 意味がより明確 |
@State | @Local | 外部から初期化できません。内部状態専用です |
@Prop | @Param + @Once | 読み取り専用の入力。初期化時に一度だけ使用する場合は @Once |
@Link | @Param + @Event | 双方向:入力には @Param、出力にはコールバックの @Event を使用します |
@Observed + @ObjectLink | @ObservedV2 + @Trace | 複数のネスト階層にわたる深い監視 |
@Watch | @Monitor('prop') | より精密な深いリスナー |
AppStorage | AppStorageV2 | @ObservedV2 + @Trace と統一 |
| (なし) | PersistenceV2 | V2 の監視機能を備えた永続ストレージ。ディスクに自動保存されます |
@Provide / @Consume | @Provider() / @Consumer() | 名前が変更されました。セマンティクスは同じです |
@ObservedV2
class UserInfo {
@Trace name: string = ''; // changes to this trigger UI refresh
@Trace age: number = 0; // changes to this trigger UI refresh
address: string = ''; // NO @Trace → changes do NOT trigger refresh
}
ルール:@ObservedV2 と @Trace は必ず併用してください(単独では効果がありません)。UI のレンダリングに関与するのは @Trace が付いたプロパティのみです。
AppStorageV2 — グローバルなリアクティブ状態:
import { AppStorageV2 } from '@kit.ArkUI';
@ObservedV2
class UserState { @Trace name: string = 'Guest'; }
// Connect (creates if not exists)
const user = AppStorageV2.connect(UserState, 'user', () => new UserState())!;
// In component
@ComponentV2
struct Header {
user: UserState = AppStorageV2.connect(UserState, 'user')!;
build() { Text(this.user.name) }
}
PersistenceV2 — 自動永続化される状態(アプリの再起動後も保持):
import { PersistenceV2, Type } from '@kit.ArkUI';
@ObservedV2
class Settings {
@Trace @Type(String) theme: string = 'light';
@Trace @Type(Number) fontSize: number = 14;
}
// Connect — auto-loads from disk if exists, writes on change
const settings = PersistenceV2.connect(Settings, 'app_settings', () => new Settings())!;
// Optional: error/success callback
PersistenceV2.notifyOnError((key, reason, msg) => { console.error(reason, msg); });
PersistenceV2 が正しくシリアライズするには、
@Typeデコレーターが必要です。
StateStore — グローバル状態管理(2026 年、中規模から大規模のアプリで公式に推奨)
状態ロジックを UI コンポーネントから完全に分離します。@ObservedV2 + @Trace と連携します。
import { StateStore } from '@kit.ArkUI';
@ObservedV2
class CounterStore {
@Trace count: number = 0;
increment(): void {
this.count++;
}
}
// Create global store (do this once, e.g. in EntryAbility or top-level)
const counterStore = StateStore.createStore(new CounterStore());
// In any component — read state
@Entry
@Component
struct CounterPage {
build() {
Column() {
Text(`Count: ${counterStore.getState().count}`)
Button('Add').onClick(() => {
counterStore.getState().increment();
})
}
}
}
使用する場面: 複数のページ/コンポーネントで同じ状態を共有する場合、状態ロジックが複雑な場合、スレッドセーフな更新が必要な場合(TaskPool のワーカーから StateStore を安全に更新できます)。
ドキュメント:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-global-state-management-state-store
ビルダー、スタイル、extends
@Builder function Label(text: string) { Text(text).fontSize(16) }
@Styles function Card() { .padding(12).borderRadius(12).backgroundColor('#FFF') }
@Extend(Text) function title() { .fontSize(22).fontWeight(FontWeight.Bold) }
// Usage:
Text('Hello').title()
Column() { Label('x') } .Card()
ナビゲーション(推奨:Router ではなく Navigation コンポーネント)
@Entry @Component struct App {
@Provide('pathStack') pathStack: NavPathStack = new NavPathStack();
build() {
Navigation(this.pathStack) {
Button('Go').onClick(() => this.pathStack.pushPath({ name: 'Detail', param: 42 }))
}
.navDestination((name: string, param: Object) => {
if (name === 'Detail') Detail({ id: param as number })
})
}
}
旧来の router モジュール(@ohos.router)も引き続き動作しますが、段階的に廃止される予定です。API 12 以降では、Navigation + NavPathStack が正式な代替機能です。Huawei は、router から Navigation への移行に関する移行ガイドを公開しています。新規プロジェクトでは必ず Navigation を使用してください。既存のコードは、都合のよいタイミングで移行してください。
NavPathStack の全 API:
// Push
pathStack.pushPath({ name: 'Page', param: data });
pathStack.pushPathByName('Page', data);
pathStack.pushPathByName('Page', data, (popInfo) => {
console.info('Pop result: ' + JSON.stringify(popInfo.result));
});
// Pop, Replace, Remove
pathStack.pop();
pathStack.replacePath({ name: 'Page', param: data });
pathStack.removeIndex(0);
pathStack.movePageToTop('Page');
// Query
pathStack.getParamByIndex(index);
pathStack.getParamByName('Page');
pathStack.getAllPathName();
pathStack.size();
ルートのインターセプト:
pathStack.setInterception({
willShow: (from, to, operation) => { /* validate/redirect */ },
didShow: (from, to, operation) => { /* analytics */ }
});
表示モード:Stack(1 列)、Split(2 列)、Auto(適応型、しきい値は 600vp)。
HarmonyOS キット(一般的なインポート)
import { UIAbility, Want, common, abilityAccessCtrl } from '@kit.AbilityKit';
import { window, display, promptAction } from '@kit.ArkUI';
import { http, webSocket, connection } from '@kit.NetworkKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit'; // photo/video picker
import { fileIo as fs } from '@kit.CoreFileKit';
import { geoLocationManager } from '@kit.LocationKit';
import { relationalStore, preferences, distributedKVStore } from '@kit.ArkData';
import { notificationManager } from '@kit.NotificationKit';
import { speechRecognizer } from '@kit.CoreSpeechKit'; // ASR / TTS
import { hilog } from '@kit.PerformanceAnalysisKit';
キットの全カタログ — 公式 SDK カテゴリ(developer.huawei.com/consumer/cn/sdk/)
アプリケーションフレームワーク Application Framework
| キット | インポートキー | 用途 |
|---|---|---|
| Ability Kit | AbilityKit | UIAbility、ExtensionAbility、Want、コンテキスト、ルーティング |
| Accessibility Kit | AccessibilityKit | スクリーンリーダー、ユニバーサルデザイン、アクセシビリティサービス |
| ArkData | ArkData | relationalStore、preferences、distributedKVStore |
| ArkTS | — | 言語仕様と Ark コンパイラツール |
| ArkUI | ArkUI | window、display、promptAction、Navigation、コンポーネント |
| ArkWeb | ArkWeb | WebView / Web コンポーネントの埋め込み |
| Background Tasks Kit | BackgroundTasksKit | 遅延・スケジュール実行のバックグラウンド処理、一時タスク |
| Core File Kit | CoreFileKit | fileIo、ピッカー、サンドボックスパス |
| Form Kit | FormKit | FormExtensionAbility、ホーム画面のサービスカード/ウィジェット |
| IME Kit | IMEKit | 入力メソッドエンジンの開発 |
| IPC Kit | IPCKit | プロセス間通信(Parcel、RemoteObject) |
| Localization Kit | LocalizationKit | i18n、l10n、RTL、疑似ローカライゼーション |
アプリケーションサービス Application Services
| キット | インポートキー | 用途 |
|---|---|---|
| Account Kit | AccountKit | ワンタップでの Huawei ID ログイン、OAuth 2.0 |
| Ads Kit | AdsKit | 広告 SDK(バナー、ネイティブ、インタースティシャル) |
| App Linking Kit | AppLinkingKit | ディープリンク、遅延ディープリンク |
| Calendar Kit | CalendarKit | カレンダーの予定、リマインダー |
| Contacts Kit | ContactsKit | 連絡先の読み取り/書き込み/検索 |
| Content Embed Kit | ContentEmbedKit | アプリ間でのドキュメント埋め込みと共同編集 |
| IAP Kit | IAPKit | アプリ内購入(消費型、非消費型、サブスクリプション) |
| Live View Kit | LiveViewKit | ロック画面/通知センターでのライブアクティビティ |
| Location Kit | LocationKit | geoLocationManager、ジオフェンシング、ジオコーディング |
| Map Kit | MapKit | Huawei Maps SDK、ルート検索、場所検索 |
| Notification Kit | NotificationKit | ローカル通知とプッシュ通知、バッジ |
| Payment Kit | PaymentKit | Huawei Pay の取引処理 |
| Push Kit | PushKit | プッシュ通知配信サービス |
| Scan Kit | ScanKit | QR コード/バーコードのスキャン |
| Share Kit | ShareKit | アプリ間でのコンテンツ共有 |
| Call Service Kit | CallServiceKit | 法人向けの着信識別とサービス検索 |
| Weather Service Kit | WeatherServiceKit | 気象データ(現在、日別、時間別、警報、指数、潮汐) |
| Pen Kit | Penkit | スタイラス/手書きコンポーネント(M-Pencil デバイス) |
| Wear Engine | WearEngine | スマートフォンと時計の通信、デバイス検出 |
| Health Service Kit | HealthServiceKit | ヘルスデータサービス |
システム System
| Kit | Import key | Purpose |
|---|---|---|
| Asset Store Kit | AssetStoreKit | 認証情報/シークレットの安全な保存 |
| Basic Services Kit | BasicServicesKit | バッテリー、振動、温度、明るさ、クリップボード |
| Connectivity Kit | ConnectivityKit | Bluetooth、Wi-Fi、NFC、USB、ホットスポット |
| Crypto Architecture Kit | CryptoArchitectureKit | 暗号化、鍵管理、証明書 |
| Device Security Kit | DeviceSecurityKit | コード署名の照会、デジタルシールド、セキュリティイベント |
| Distributed Service Kit | DistributedServiceKit | deviceManager、デバイス間の検出 |
| Enterprise Threat Protection Kit | EnterpriseThreatProtectionKit | 企業向けファイル脅威のスキャン、隔離、復元、削除 |
| MDM Kit | MDMKit | 企業向けデバイス、アプリ、設定の管理 |
| Network Boost Kit | NetworkBoostKit | ネットワーク転送の最適化と低電力転送モード |
| NearLink Kit | NearLinkKit | NearLink デバイス機能とパートナーデバイスの管理 |
| Screen Time Guard Kit | ScreenTimeGuardKit | スクリーンタイムの認証とアプリ制御ポリシー |
メディア Media
| Kit | Import key | Purpose |
|---|---|---|
| Audio Kit | AudioKit | オーディオの再生、録音、ルーティング、フォーカス |
| AVCodec Kit | AVCodecKit | ハードウェアアクセラレーションによるエンコード/デコード(H.264、H.265、AAC…) |
| Camera Kit | CameraKit | カメラのプレビュー、写真撮影、動画録画 |
| Image Kit | ImageKit | 画像のデコード、変換、EXIF |
| Media Kit | MediaKit | AVPlayer、AVRecorder(再生/録音の統合) |
| Media Library Kit | MediaLibraryKit | photoAccessHelper、メディアライブラリの CRUD |
| Scan Kit | ScanKit | QR/バーコードのスキャン |
グラフィックス Graphics
| Kit | Import key | Purpose |
|---|---|---|
| ArkGraphics 2D | ArkGraphics2D | 2D Canvas の描画、エフェクト、ぼかし、影 |
| ArkGraphics 3D | ArkGraphics3D | 3D シーングラフ、glTF レンダリング |
| UI Design Kit | UIDesignKit | hdsDrawable アイコン処理、HdsNavigation コンポーネント |
| XComponent | (ArkUI built-in) | NAPI 経由のネイティブ OpenGL ES/Vulkan サーフェス |
AI
| Kit | Import key | Purpose |
|---|---|---|
| Core Speech Kit | CoreSpeechKit | オンデバイス ASR/TTS エンジン |
| Core Vision Kit | CoreVisionKit | OCR、顔検出、画像セグメンテーション、超解像 |
| CANN Kit | CANNKit | AI 計算の高速化。API 24 では PC での LLM 推論に対応 |
| FAST Kit | FASTKit | 並行ハッシュテーブル、ベクトル演算、フィルター |
| Intents Kit | IntentsKit | インテント認識(30 以上のドメイン、60 以上の組み込みインテント) |
| MindSpore Lite | MindSporeLiteKit | エッジモデル推論(Caffe/TF/ONNX/MindIR) |
| Natural Language Kit | NaturalLanguageKit | 単語分割、NER、キーワード抽出 |
パフォーマンスと開発ツール
| Kit | Import key | Purpose |
|---|---|---|
| Performance Analysis Kit | PerformanceAnalysisKit | hilog、HiAppEvent、クラッシュ分析 |
写真/動画ピッカー(正しい API — picker.PhotoViewPicker は非推奨です)
import { photoAccessHelper } from '@kit.MediaLibraryKit';
const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
const picker = new photoAccessHelper.PhotoViewPicker();
const result = await picker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 9
});
const uris: string[] = result.photoUris;
ファイル/ドキュメントピッカー
import { picker } from '@kit.CoreFileKit';
// Pick document files (PDF, DOCX, etc.)
const documentPicker = new picker.DocumentViewPicker(context);
const result = await documentPicker.select({
maxSelectNumber: 5,
fileSuffixFilters: ['.pdf', '.docx', '.xlsx'], // optional filter
});
const uris: string[] = result; // file URIs with temporary read permission
// Save file to user-chosen location
const savePicker = new picker.DocumentViewPicker(context);
const saveResult = await savePicker.save({
newFileNames: ['export.pdf'],
});
const destUri: string = saveResult[0];
CoreSpeechKit — ASR の音声形式要件
import { speechRecognizer } from '@kit.CoreSpeechKit';
const engine = await speechRecognizer.createEngine({
language: 'zh-CN',
online: 0 // 0=on-device, 1=cloud
});
// Audio MUST be: PCM, 16kHz, mono, 16-bit
// Chunk size: 640 or 1280 bytes, write every 20ms or 40ms
engine.startListening({
sessionId: `asr_${Date.now()}`,
audioInfo: {
audioType: 'pcm',
sampleRate: 16000,
soundChannel: 1,
sampleBit: 16
}
});
HTTP リクエストの例
import { http } from '@kit.NetworkKit';
async function fetchJson(url: string): Promise<string> {
const req = http.createHttp();
try {
const res = await req.request(url, {
method: http.RequestMethod.GET,
header: { 'Content-Type': 'application/json' },
connectTimeout: 5000, readTimeout: 10000
});
return res.result as string;
} finally {
req.destroy();
}
}
ネットワーク接続の監視
import { connection } from '@kit.NetworkKit';
// Check current network state
const hasNet = connection.hasDefaultNetSync();
// Monitor network changes
const netCon = connection.createNetConnection();
netCon.on('netAvailable', () => { /* network restored */ });
netCon.on('netLost', () => { /* network lost */ });
netCon.on('netCapabilitiesChange', (info: connection.NetCapabilityInfo) => {
const isWifi = info.netCap.bearerTypes.includes(connection.NetBearType.BEARER_WIFI);
const isCellular = info.netCap.bearerTypes.includes(connection.NetBearType.BEARER_CELLULAR);
});
netCon.register(() => {}); // activate subscriptions
// Cleanup
netCon.unregister(() => {});
WebSocket
import { webSocket } from '@kit.NetworkKit';
const ws = webSocket.createWebSocket();
ws.on('open', () => { ws.send('hello'); });
ws.on('message', (err, data: string | ArrayBuffer) => {
console.info('received:', typeof data === 'string' ? data : 'binary');
});
ws.on('close', (err, { code, reason }) => { /* closed */ });
ws.on('error', (err) => { /* handle */ });
ws.connect('wss://example.com/ws', { header: { 'Authorization': 'Bearer xxx' } });
// Send
ws.send(JSON.stringify({ type: 'chat', text: 'hi' }));
// Close
ws.close();
種類を指定してアプリを起動する(startAbilityByType)
import { common } from '@kit.AbilityKit';
const context = getContext(this) as common.UIAbilityContext;
// Open navigation app with destination
context.startAbilityByType('navigation', {
sceneType: 1,
destinationLatitude: 39.9042,
destinationLongitude: 116.4074,
destinationName: 'Beijing',
} as Record<string, Object>);
// Open browser
context.startAbilityByType('browser', {
uri: 'https://example.com',
} as Record<string, Object>);
対応する種類:navigation、browser、email、finance、transit など。
権限
module.json5 → requestPermissions で宣言します。ユーザーの許可が必要な権限は、実行時に abilityAccessCtrl.createAtManager().requestPermissionsFromUser(context, [...]) で要求します。
アトミックサービス/メタサービス(原子化サービス/元サービス)
インストール不要の小規模(≤10MB)アプリで、以下から起動できます。
- サービスカード(
FormExtensionAbilityで構築されたホーム画面ウィジェット) - 検索、スキャン、NFC、デバイス間の継続利用
module.json5 で "installationFree": true を設定します。ビルド時に、複数の HAP を含む .app パッケージが生成されます。
分散機能
- デバイス間の継続(流転)—
continueAbility/onContinue/onNewWantライフサイクル - 分散データオブジェクト — デバイス間で同期される状態
- 分散ファイルシステム — 共有サンドボックス
- デバイスマネージャー — 信頼済みデバイスを検出
パッケージの種類
| 種類 | 用途 |
|---|---|
| HAP(Harmony Ability Package) | 実行可能なモジュール:entry または feature タイプ |
| HSP(Harmony Shared Package) | アプリ内の共有モジュール。実行時に動的リンクされます |
| HAR(Harmony Archive) | 静的ライブラリ。ビルド時に HAP にパッケージ化されます |
AppGallery Connect(华为应用市场)経由で配布します。
公式サンプル:https://developer.huawei.com/consumer/cn/samples/
sys.symbol — アイコングリフシステム
HarmonyOS Symbol は、マルチレイヤーカラーと 7 種類のアニメーションに対応した、1500 種類以上のベクターアイコンフォントです。
SymbolGlyph($r('sys.symbol.bell_fill'))
.fontSize(24)
.fontColor([Color.Blue, Color.Green])
.symbolEffect(new BounceSymbolEffect(EffectScope.WHOLE), true)
動作が確認されている名前(SDK 6.0.1):xmark · plus · minus · checkmark · chevron_right · chevron_left · star · star_fill · bell · bell_fill · doc · video · mic · mic_fill · clock · trash · pencil · camera · person
存在しない名前(よくある間違い):photo · doc_richtext · sparkles · checklist · image(代わりに doc または camera を使用してください) · location_fill(代わりにテキストラベルを使用してください)
有効な名前はこちらで確認できます:https://developer.huawei.com/consumer/cn/design/harmonyos-symbol
DevEco Studio 6.x — プロジェクトのセットアップ
DevEco Studio 6.x では、追加の Hvigor インフラストラクチャファイルが必要です。これらがないと、IDE に「工程结构及配置需要升级」と表示されます。
現在の API 24 Release ツールチェーン(HarmonyOS 6.1.1、2026/05/26):
- DevEco Studio:6.1.1 Release (6.1.1.280)
- HarmonyOS SDK:6.1.1 Release(OpenHarmony SDK
Ohos_sdk_public 6.1.1.125、API 24 Release) - Hvigor / hvigorw:6.24.2
- ohpm:6.1.2.268
- Node.js:18.20.1
compileSdkVersion:6.1.1(24)targetSdkVersion:4.0.0(10) ~ 6.1.1(24)
推奨ワークフロー: 必ず DevEco Studio でひな形を生成してから(新建项目 → Empty Ability)、ソースファイルをコピーしてください。これにより、古い Hvigor ラッパー/プラグインのバージョンを避けられます。
hvigorfile.ts(ルートおよび各モジュール)
// root
import { appTasks } from '@ohos/hvigor-ohos-plugin';
export default { system: appTasks, plugins:[] }
// entry/hvigorfile.ts
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
export default { system: hapTasks, plugins:[] }
hvigor/wrapper/hvigor-config.json5
{
// Use the exact version generated by DevEco Studio for your SDK.
// API 24 Release uses Hvigor 6.24.2.
"hvigorVersion": "6.24.2",
"dependencies": {
// Keep this aligned with the DevEco-generated scaffold.
"@ohos/hvigor-ohos-plugin": "<generated-by-deveco>"
}
}
build-profile.json5 — SDK バージョンの配置
SDK バージョンは1 か所だけに設定します。app の下、または各 product の中のいずれか一方です。両方に設定すると「Sync Failed」が発生します。
API 24 の DevEco Studio では、.ets ファイルに対して厳密な構文チェックのみを実行し、チェック時のエンドツーエンドのコンパイル時間を短縮するため、プロジェクトレベルの build-profile.json5 の strictMode に strictCheckerOnly が追加されています。
{
"app": {
"compileSdkVersion": "6.1.1(24)",
"compatibleSdkVersion": "4.0.0(10)",
"targetSdkVersion": "6.1.1(24)",
"strictMode": {
"strictCheckerOnly": true
}
}
}
API 24 では、複数環境の依存関係を管理するため、プロジェクトレベルの oh-package.json5 に properties も追加されています。パッケージファイルを重複させる代わりに、環境固有の依存関係の値をここで管理してください。
ArkTS 厳密モードのコンパイラーエラー(SDK 6.0.1)
オブジェクトリテラルを型として使用できません
// ❌
parseOutput(): { text: string; tags: string[] } { ... }
// ✅
interface ParsedOutput { text: string; tags: string[]; }
parseOutput(): ParsedOutput { ... }
any / unknown は使用できません
const task = JSON.parse(rawStr) as AgentTask; // ✅ cast immediately
navDestination には @Builder 関数参照が必要です
// ❌ inline lambda
.navDestination((name, param) => { if (name==='X') MyPage() })
// ✅ top-level @Builder
@Builder function PageRouter(name: string) { if (name==='X') MyPage() }
Navigation(stack) { ... }.navDestination(PageRouter)
@Entry の build() ルートはコンテナーである必要があります
Stack()、Column()、または Row() で囲んでください。カスタムコンポーネントだけではコンテナーになりません。
display.width はピクセル単位であり、vp ではありません
function isTablet(): boolean {
const dm = display.getDefaultDisplaySync();
return (dm.width / dm.densityPixels) >= 840;
}
すべての user_grant 権限には reason と usedScene が必要です
{
"name": "ohos.permission.READ_MEDIA",
"reason": "$string:permission_media_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
}
INTERNET は system_grant であるため、追加のフィールドは不要です。
よくある落とし穴
-
FA モデルと Stage モデルを混在させないでください — FA はレガシーです。HarmonyOS NEXT は Stage のみをサポートします。
-
ArkUI コールバック内の
this— アロー関数が必須です。通常のfunction () {}ではthisが失われます。 -
ネストされたオブジェクトでの
@State— ネストされたプロパティへの変更では更新がトリガーされません。@Observed/@ObjectLinkを使用するか、オブジェクト全体を再代入してください。 -
配列項目の更新 — 項目を置き換える(
arr[i] = newItem)か、項目のクラスに@Observedを使用してください。 -
リソース参照 — 文字列パスではなく、
$r('app.string.foo')、$r('app.media.icon')を使用してください。 -
コンポーネント内の
getContext(this)はUIAbilityContextを返します。明示的にキャストしてください。 -
build()内での非同期処理は禁止されています。aboutToAppear()でデータを読み込み、@Stateに保存してください。 -
ユーザー許可が必要な権限は、宣言と実行時のリクエストの両方が必要です。
-
ohpm は npm に似たパッケージマネージャーです。依存関係は
oh-package.json5に記述します。 -
デバイスでのプレビュー — DevEco Previewer では完全にはシミュレートできません。必ず実際の HarmonyOS デバイスまたはエミュレーターでテストしてください。
-
Navigation に
hideSideBarはありません — 代わりに.hideBackButton(true)を使用してください。 -
promptAction.showToast()は非推奨です — 代わりにgetUIContext().getPromptAction().showToast(...)を使用してください。安全のため try-catch で囲んでください。 -
フローティング FAB ボタンがリストの最後の項目に重なります — 主要なアクションボタンには
Navigation.menus()を使用するか、FAB の高さとマージンの合計に等しい下部パディングを List に追加してください。 -
on/offには名前付きコールバックを使用してください — 無名関数は登録解除できません。常に参照を保存してください。// BAD: 無名関数は off() で登録解除できません session.on('stateChange', (state) => { ... }); // GOOD: 名前付き参照 const cb = (state: StateType) => { ... }; session.on('stateChange', cb); session.off('stateChange', cb); -
状態の変更をまとめてください — 複数の
@Stateの変更は、再レンダリングを複数回トリガーします。一時変数に蓄積して、一度だけ代入してください。// BAD: 3 回再レンダリング this.list.push(a); this.list.push(b); this.list.push(c); // GOOD: 1 回再レンダリング const tmp = [...this.list, a, b, c]; this.list = tmp; -
状態デコレーターの選択優先順位 —
@State+@Prop/@Link/@ObjectLink(親子)>@Provide+@Consume(深いネスト)>LocalStorage(ページ単位)>AppStorage(グローバル)。頻繁に変化するデータにはAppStorageを使用しないでください。 -
単位変換 —
this.getUIContext().px2vp(value)を介してpx2vp(px)/vp2px(vp)を使用します。画面密度:display.getDefaultDisplaySync().densityPixels。 -
画面をオンのままにする — 動画の再生中/ナビゲーション中は
win.setWindowKeepScreenOn(true)を設定し、一時停止時にリセットしてください。
安定性 — クラッシュの種類とエラー処理
| 種類 | 説明 |
|---|---|
| JS_ERROR | ArkTS/JS の実行時例外(最も一般的)— TypeError: Cannot read property 'x' of undefined |
| CPP_CRASH | ネイティブ C/C++ のクラッシュ(SIGSEGV、SIGABRT) |
| APP_FREEZE | メインスレッドが 6 秒を超えてブロックされた状態(ANR 相当)。主な原因:スレッドロック(57%)、システムリソース(14%)、メインスレッドの重い処理(9%) |
| OOM | メモリ不足による強制終了 |
グローバルエラーハンドラー:
import { errorManager } from '@kit.AbilityKit';
const observer: errorManager.ErrorObserver = {
onUnhandledException(errMsg: string): void {
console.error('Uncaught: ' + errMsg);
},
onException?(errObject: Error): void { // API 10+
console.error(errObject.name + ': ' + errObject.message);
}
};
const observerId = errorManager.on('error', observer);
クラッシュイベントのサブスクリプション(HiAppEvent):
import { hiAppEvent } from '@kit.PerformanceAnalysisKit';
hiAppEvent.addWatcher({
name: "crashWatcher",
appEventFilters: [{
domain: hiAppEvent.domain.OS,
names: [hiAppEvent.event.APP_CRASH]
}],
onReceive: (domain, appEventGroups) => { /* process crash */ }
});
バックグラウンドタスク — 4 種類
| 種類 | API | 期間 | ユースケース |
|---|---|---|---|
| 一時的 | requestSuspendDelay | 最大約 3 分 | データの保存、ログのアップロード |
| 継続的 | startBackgroundRunning | 無制限(通知が必要) | 音楽、ナビゲーション、録音 |
| 遅延実行 | workScheduler.startWork | システムが決定 | 定期的な同期、クリーンアップ |
| エージェントリマインダー | ReminderRequestTimer | システムが管理 | アラーム、タイマー |
継続的タスクの例:
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { wantAgent, WantAgent } from '@kit.AbilityKit';
// module.json5: "abilities": [{ "backgroundModes": ["audioRecording"] }]
// permission: ohos.permission.KEEP_BACKGROUND_RUNNING
const info: wantAgent.WantAgentInfo = {
wants: [{ bundleName: 'com.example.app', abilityName: 'MainAbility' }],
actionType: wantAgent.OperationType.START_ABILITY,
requestCode: 0, actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
const agent = await wantAgent.getWantAgent(info);
backgroundTaskManager.startBackgroundRunning(
this.context, backgroundTaskManager.BackgroundMode.AUDIO_RECORDING, agent
);
9 種類のバックグラウンドモード:dataTransfer · audioPlayback · audioRecording · location · bluetoothInteraction · multiDeviceConnection · taskKeeping(2-in-1 のみ)
ユーザーの利用状況別の遅延タスクの頻度: アクティブ=2h、頻繁=4h、通常=24h、まれ=48h、未使用=禁止。
セキュアコーディングのルール(公式のベストプラクティスより)
- 非対話型の機能には
exported: falseを設定します - 信頼境界を越えるすべてのパラメーターを検証します(Want intents、rpc.RemoteObject)
- パラメーター化クエリを使用し、SQL で文字列を連結することは決して避けます
- HTTP を HTTPS に置き換え、SSL 証明書を検証します
- 個人データをクリップボードに保存しないでください
- 機密性の高い短いデータ(パスワード、トークン)には
Asset Store Kitを使用します - 暗黙的インテントを介して個人データを渡さないようにします
- 本番ビルドではコード難読化を使用します
- システムレベルの保護には、適切な
InputType(.USER_NAME、.Password)を使用します - 本番リリースにデバッグ署名を使用しないでください
権限の確認とリクエストのパターン:
import { abilityAccessCtrl, bundleManager } from '@kit.AbilityKit';
// Check
const atManager = abilityAccessCtrl.createAtManager();
const bundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
const status = await atManager.checkAccessToken(
bundleInfo.appInfo.accessTokenId, 'ohos.permission.CAMERA');
// Step 1 — Request from user
const result = await atManager.requestPermissionsFromUser(context,
['ohos.permission.CAMERA', 'ohos.permission.MICROPHONE']);
if (result.authResults[0] === 0) {
// Granted
} else if (result.dialogShownResults?.[0]) {
// User saw dialog but denied — show in-app guidance, don't re-pop
} else {
// Step 2 — Fallback: open settings dialog (user previously denied permanently)
atManager.requestPermissionOnSetting(context,
['ohos.permission.CAMERA']).then((statuses) => {
// statuses[0]: 0 = granted, -1 = denied
});
}
データ暗号化レベル: EL1(デバイスレベル)→ EL2(ユーザーレベル、デフォルト)→ EL3(ロック中もアクセス可能)→ EL4(ロック中はアクセス不可)
ネットワークセキュリティ設定 — 本番アプリでの HTTPS/証明書ピンニング:
src/main/resources/base/profile/network_config.json を作成します:
{
"network-security-config": {
"domain-config": [{
"domains": [{ "include-subdomains": true, "name": "api.example.com" }],
"trust-anchors": [{ "certificates": "/data/storage/el1/bundle/entry/resources/resfile/ca_cert.pem" }]
}]
}
}
module.json5 で参照します:"metadata": [{ "name": "NetworkSecurityConfig", "resource": "$profile:network_config" }]。
テスト — arkxtest フレームワーク
パッケージ:@ohos/hypium(Mocha スタイル)。3 つのサブフレームワーク:JsUnit(単体テスト)、UiTest(UI 自動化)、PerfTest(パフォーマンス)。
JsUnit
import { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll } from '@ohos/hypium';
export default function abilityTest() {
describe('MyTestSuite', () => {
beforeAll(() => { /* once before all */ });
beforeEach(() => { /* before each */ });
afterEach(() => { /* after each */ });
afterAll(() => { /* once after all */ });
it('sync_test', 0, () => {
expect(1 + 1).assertEqual(2);
});
it('async_test', 0, async (done: Function) => {
let result = await someAsyncOp();
expect(result).assertContain('expected');
done();
});
});
}
主なアサーション: assertEqual(v) · assertContain(v) · assertTrue() · assertFalse() · assertNull() · assertUndefined() · assertNaN() · assertInstanceOf(type) · assertThrowError(fn) · assertDeepEquals(v) · assertClose(v, tolerance) · assertLarger(v) · assertLess(v) · not()(否定) · assertPromiseIsResolved() · assertPromiseIsRejected()
テストファイルは entry/src/ohosTest/ets/test/ に配置します。UI 自動化については、下記の UiTest セクションを参照してください。
仓颉 (Cangjie) は Huawei の新しい言語(ベータ版)です。Cangjie が安定するまでは、すべての本番アプリで ArkTS を使用してください。
ArkCompiler — ランタイムの詳細
- AOT モード — 静的型情報から最適化されたネイティブマシンコードを生成します。JIT のウォームアップは不要です
- LiteActor 並行処理 — スレッドごとに分離されたメモリを持つ Actor モデルです。メッセージパッシングで通信します
TaskPool と Worker の比較
| 特徴 | TaskPool | Worker |
|---|---|---|
| スレッド管理 | 自動(作成/再利用/破棄) | 手動のライフサイクル管理 |
| 最大スレッド数 | 物理コア数に合わせて自動調整 | プロセスあたり最大 64 |
| タスクの実行時間制限 | 3 分(非同期 I/O を除く) | 無制限 |
| 優先度 | HIGH / MEDIUM / LOW / IDLE | API 18+ のみ |
| キャンセル | 対応 | 非対応 |
| スレッドの再利用 | あり | なし |
| タスクグループ | あり | なし |
| 遅延実行/定期実行 | あり | なし |
Worker を使用する場合: タスクが 3 分を超える場合、永続的な状態が必要な場合、または同期タスク間の関連が強い場合です。
@Concurrent のルール
- すべての TaskPool 関数で必須です。使用できるのは
.etsファイルのみです - 使用可能:通常の関数、非同期関数
- 使用禁止: アロー関数、クラスメソッド、無名関数、ジェネレーター関数
- クロージャ変数は使用できません — 外側のスコープを参照できません。ローカル変数、パラメーター、インポートのみ使用できます
- 同じファイル内のほかの関数は呼び出せません(クロージャ違反)。インポートする必要があります
import { taskpool } from '@kit.ArkTS';
@Concurrent
function heavyCalc(n: number): number { return n * n; }
const result = await taskpool.execute(heavyCalc, 42);
// With priority
const task = new taskpool.Task(heavyCalc, 42);
await taskpool.execute(task, taskpool.Priority.HIGH);
// Delayed and periodic
taskpool.executeDelayed(heavyCalc, 2000, 42); // after 2s
taskpool.executePeriodically(heavyCalc, 5000, 42); // every 5s
// TaskGroup — execute multiple tasks as a group
const group = new taskpool.TaskGroup();
group.addTask(heavyCalc, 10);
group.addTask(heavyCalc, 20);
group.addTask(heavyCalc, 30);
const results = await taskpool.execute(group); // returns array of results
長時間実行タスク: TaskPool 内の非同期コード(Promise/IO)には時間制限がありません(CPU バウンドの同期コードのみ 3 分に制限されます)。HarmonyOS 6.0 では、TaskPool での長時間実行非同期タスクが正式にサポートされています。
@Sendable — 共有ヒープを介した参照渡し
SharedHeap(プロセスレベルで、すべてのスレッドからアクセス可能)上のオブジェクトは、1MB のデータでシリアライズより 100 倍高速です。
@Sendable
class SharedData {
value: number = 0; // must be explicitly initialized
}
制約: Sendable クラスのみを継承できます。プロパティの型は、プリミティブ型、Sendable クラス、@arkts.collections コンテナーに限定されます。実行時にプロパティを追加/削除できません。計算プロパティは使用できません。# による private は使用できません(private キーワードを使用してください)。
Worker のパターン
// Main thread
const worker = new worker.ThreadWorker('entry/ets/workers/myWorker.ets');
worker.postMessage({ type: 'compute', data: payload });
worker.onmessage = (e) => { /* handle result */ };
worker.terminate();
// Worker thread (myWorker.ets)
const workerPort = worker.workerPort;
workerPort.onmessage = (e) => {
const result = processData(e.data);
workerPort.postMessage(result);
};
TaskPool と Worker の両方: ワーカースレッドから AppStorage や UI ライブラリにアクセスできません。スレッドコンテキストが異なるため、コンテキストオブジェクトを共有できません。
OHPM — パッケージマネージャー
ohpm install @ohos/axios # install a library
ohpm install # install all from oh-package.json5
oh-package.json5 の例:
{
"name": "entry",
"dependencies": {
"@ohos/axios": "^2.0.0"
}
}
OHPM Central Repository:https://developer.huawei.com/consumer/cn/deveco-service
人気の ohpm ライブラリ
@ohos/axios — HTTP クライアント(ohpm install @ohos/axios、ohos.permission.INTERNET が必要です):
import axios, { AxiosResponse, AxiosError, InternalAxiosRequestConfig } from '@ohos/axios';
// GET (generic response type)
interface UserInfo { id: number; name: string }
axios.get<UserInfo, AxiosResponse<UserInfo>, null>('/user', { params: { ID: 12345 } })
.then((res: AxiosResponse<UserInfo>) => console.info(res.data.name))
.catch((err: AxiosError) => console.error(err.message));
// POST
interface NewUser { firstName: string; lastName: string }
axios.post<string, AxiosResponse<string>, NewUser>('/user',
{ firstName: 'Fred', lastName: 'Flintstone' });
// Create instance (recommended for project-wide use)
const http = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: { 'X-Custom-Header': 'value' },
});
// Interceptors
http.interceptors.request.use((config: InternalAxiosRequestConfig) => {
config.headers['Authorization'] = 'Bearer ' + AppStorage.get<string>('token');
return config;
}, (err: AxiosError) => Promise.reject(err));
http.interceptors.response.use(
(res: AxiosResponse) => res,
(err: AxiosError) => {
if (err.response?.status === 401) { /* redirect to login */ }
return Promise.reject(err);
}
);
// File download
import fs from '@ohos.file.fs';
const filePath = getContext(this).cacheDir + '/file.jpg';
axios({ url: 'https://example.com/file.jpg', method: 'get', filePath,
onDownloadProgress: (e) => {
console.info('Progress: ' + (e.loaded && e.total ? Math.ceil(e.loaded / e.total * 100) : 0) + '%');
}
});
// File upload (FormData)
import { FormData } from '@ohos/axios';
const form = new FormData();
form.append('file', 'internal://cache/photo.jpg');
axios.post<string, AxiosResponse<string>, FormData>('/upload', form, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (e) => console.info(e.loaded + '/' + e.total),
});
// Cancel request
const ctrl = new AbortController();
axios.get('/slow', { signal: ctrl.signal });
ctrl.abort();
responseType:'string'|'object'|'array_buffer'。相互 TLS には API 11 以降が必要です。
@ohos/pulltorefresh — プルダウン更新+追加読み込み(ohpm install @ohos/pulltorefresh、List/Scroll/Tabs/Grid/WaterFlow をサポート、API 12 以降):
import { PullToRefresh, PullToRefreshType } from '@ohos/pulltorefresh';
@Component
struct NewsPage {
@State data: string[] = ['Item 1', 'Item 2', 'Item 3'];
@State refreshing: boolean = false;
build() {
PullToRefresh({
pullToRefreshType: PullToRefreshType.LIST,
refreshing: $refreshing,
customList: () => { this.ListBuilder(); },
onRefresh: () => new Promise<string>((resolve) => {
setTimeout(() => {
this.data = ['Refreshed 1', 'Refreshed 2'];
resolve('Done');
}, 1000);
}),
onLoadMore: () => new Promise<string>((resolve) => {
setTimeout(() => {
for (let i = 0; i < 10; i++) this.data.push('New ' + this.data.length);
resolve('');
}, 1000);
}),
customLoad: null,
customRefresh: null,
})
}
@Builder ListBuilder() {
List() {
ForEach(this.data, (item: string) => {
ListItem() { Text(item).padding(16) }
})
}
}
}
@ohos/lottie — JSON アニメーション(ohpm install @ohos/lottie、パフォーマンスが 30% 以上向上する @ohos/lottie-turbo もご検討ください):
import lottie, { AnimationItem } from '@ohos/lottie';
@Component
struct LottieDemo {
private canvasCtx = new CanvasRenderingContext2D(new RenderingContextSettings(true));
private animItem: AnimationItem | null = null;
private animName: string = 'myAnim';
aboutToDisappear(): void { lottie.destroy(); }
build() {
Canvas(this.canvasCtx).width(200).height(200)
.onReady(() => {
this.canvasCtx.imageSmoothingEnabled = true;
lottie.destroy(this.animName);
this.animItem = lottie.loadAnimation({
container: this.canvasCtx, renderer: 'canvas',
loop: true, autoplay: false, name: this.animName,
contentMode: 'Contain',
path: 'common/animation.json', // relative to ets parent folder
frameRate: 30,
});
this.animItem.addEventListener('DOMLoaded', () => {
this.animItem?.play();
});
})
}
}
// Controls: .play() .pause() .stop() .goToAndPlay(frame, true) .setSpeed(2)
難読化:obfuscation-rules.txt に
-keep ./oh_modules/@ohos/lottieを追加します
@ohos/imageknife — キャッシュ付き画像読み込み(ohpm install @ohos/imageknife、API 18 以降。後継:imageknifepro):
import { ImageKnifeComponent, ImageKnifeAnimatorComponent } from '@ohos/imageknife';
import { ImageKnife } from '@ohos/imageknife';
// Init file cache once (in EntryAbility.onCreate)
await ImageKnife.getInstance().initFileCache(context, 256, 256 * 1024 * 1024);
// Basic usage
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: 'https://example.com/image.png',
placeholderSrc: $r('app.media.loading'),
errorholderSrc: $r('app.media.error'),
objectFit: ImageFit.Auto,
border: { radius: 50 }, // circular crop
onLoadListener: {
onLoadSuccess: (data) => { console.info('Loaded'); return data; },
onLoadFailed: (err) => console.error('Failed: ' + err),
},
progressListener: (progress: number) => console.info('Progress: ' + progress),
}
}).width(100).height(100)
// GIF / animated WebP
ImageKnifeAnimatorComponent({
imageKnifeOption: { loadSrc: 'https://example.com/anim.gif' }
}).width(300).height(300)
難読化:obfuscation-rules.txt に
-keep ./oh_modules/@ohos/imageknifeを追加します
dayjs — 日付のフォーマット(ohpm install dayjs、標準の npm API):
import dayjs from 'dayjs';
dayjs().format('YYYY-MM-DD HH:mm:ss');
dayjs('2024-01-01').fromNow(); // requires relativeTime plugin
dayjs().add(7, 'day').format('YYYY-MM-DD');
dayjs().diff(dayjs('2024-01-01'), 'day'); // days difference
デバッグとツール
- DevEco Studio — 主要な IDE。エミュレーター、プレビューアー、プロファイラー、HiLog ビューアーを含みます
- hdc — Harmony Device Connector(adb に相当):
hdc shell、hdc file send、hdc hilog - HiLog — ロギング:
hilog.info(0x0001, 'TAG', 'message %{public}s', arg) - 計測ツール:SmartPerf / DevEco Profiler — CPU/GPU/メモリ/エネルギーのプロファイリング
- DevEco Testing — UI 自動化、パフォーマンステスト、モンキー/ストレステスト、互換性テスト
公開
- HarmonyOS 開発者アカウントを取得し、実名認証を完了します
- AppGallery Connect で署名証明書とプロファイルを生成します
build-profile.json5で署名を設定します- リリース用 HAP/APP バンドルをビルドします:
hvigorw assembleApp - AppGallery Connect にアップロードして審査を受けます
主要な参考資料
- 公式ドキュメント:https://developer.huawei.com/consumer/cn/doc/
- ベストプラクティス:https://developer.huawei.com/consumer/cn/best-practices
- サンプル:https://developer.huawei.com/consumer/cn/samples/
- Codelabs:https://developer.huawei.com/consumer/cn/codelabsPortal/serviceTypes
- ArkTS ガイド:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts
- ArkUI ガイド:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkui
- 状態管理:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management-overview
- ナビゲーション:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-set-navigation-routing
- 並行処理:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-concurrency
- SDK / Kits:https://developer.huawei.com/consumer/cn/sdk
- OHPM レジストリ:https://developer.huawei.com/consumer/cn/deveco-service
- DevEco Studio:https://developer.huawei.com/consumer/cn/deveco-studio/
- シンボルアイコン:https://developer.huawei.com/consumer/cn/design/harmonyos-symbol
- AppGallery Connect:https://developer.huawei.com/consumer/cn/agconnect
- StateStore:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-global-state-management-state-store
- GitCode サンプル:https://gitcode.com/HarmonyOS_Samples
公式サンプルプロジェクト(GitCode)
https://gitcode.com/HarmonyOS_Samples/<name> で、430 以上のオープンソース HarmonyOS サンプルを利用できます。カテゴリ別の主要プロジェクト:
オーディオとビデオ
- AVCodecVideo — AVCodec によるビデオ再生/録画(H.264/H.265、AudioVivid)
- AdaptiveVideo — ショートビデオの没入型再生と回転再生
- AudioCast — 外部デバイスへのオーディオキャスト
- AudioToVideoSync — オーディオとビデオの同期
- DecodePlayControl — サーフェスモードのビデオ再生制御
- PlayShortVideosBasedOnVideoComponent — ショートビデオプレーヤー(プログレスバー、フルスクリーン、再生速度、自動再生)
- SmoothSwitchShortVideos — プリロードによるショートビデオのスムーズな切り替え
- MusicHome — アダプティブな音楽アルバムアプリ(1 回の開発で複数デバイスに対応)
- MusicCard — Form Kit による音楽サービスウィジェット
- NetAdaptiveVideoStream — アダプティブビットレートのビデオストリーミング
- HMOS_LiveStream — ライブストリーミング(配信者+視聴者)
- UseAVTranscoderVideo — ビデオのトランスコード
- SwipePlayer — スワイプで切り替えるビデオプレーヤー
- VideoCast — 外部デバイスへのビデオキャスト
カメラと画像
- CustomCamera — Camera Kit:プレビュー、フラッシュ、フォーカス、露出、前面/背面カメラの切り替え
- ImageGetAndSave — 画像の取得と保存
- PicturePreview — ズームとスワイプに対応した画像プレビュー
- PixelMapImageEdit — PixelMap のエンコード/デコードによる画像編集
- SmartPhotoPicker — PhotoPicker による写真のおすすめ
- MultiPictureBeautification — 複数デバイスでの画像補正
- LongSnapshotPractice — ロングスクリーンショットの実装
- MultipleImage — Swiper による複数画像のカルーセル
- ImageToVideo — 画像を合成してビデオを作成
- CoreVisionKitOCR — OCR によるテキストの自動入力
UI コンポーネントとレイアウト
- CommonListFlows — List コンポーネントを使用した一般的なリストのシナリオ
- ListScrollComponent — 遅延読み込みに対応したスクロール可能なリスト
- GridScrollComponent — パフォーマンスを最適化したグリッドスクロール
- WaterFlowScrollComponent — ウォーターフォールフローレイアウト
- SwiperPerformance — Swiper のパフォーマンス最適化
- ComponentEncapsulation — コンポーネントのカプセル化パターン
- CommentReply — RichEditor によるコメント返信(テキスト、絵文字、@メンション)
- DialogHub — 汎用ダイアログライブラリ
- CustomizeKeyboard — カスタムキーボードの実装
- DragFramework — 画像、リッチテキスト、リストのドラッグ&ドロップ
- TextExpand — 展開/折りたたみ可能なテキスト
- PureTabs — タブベースのナビゲーション UI
- FoldedHover — 折りたたみ式デバイスのホバーモード
- CardInfoRefresh — サービスウィジェットの作成、操作、更新(Form Kit)
- PageFlip — ページめくり効果
- ResponsiveLayout — 複数のデバイスタイプに対応するレスポンシブレイアウト
- Immersive — 没入型の全画面 UI
アプリのアーキテクチャとナビゲーション
- AppLifecycleManagement — アプリのライフサイクル状態管理
- AppStartUp — 起動タスクの初期化(AppStartup)
- StageModelContext — ステージモデルコンテキストの使用
- HMRouter — HMRouter によるページナビゲーション
- NavigationSettings — Navigation を使用した設定アプリ(小ウィンドウ/大ウィンドウ)
- JumpBetweenApps — App Linking によるアプリ間の遷移
- CrossModuleReference — ネイティブ HAR/HSP モジュール間の参照
- CrossModuleResourceAccess — モジュール間のリソースアクセス($r、resourceManager)
- DynamicComponent — 動的なコンポーネントの作成
- DesktopShortcut — module.json5 によるデスクトップショートカットのエントリー
- EmebedAbility — FullScreenLaunchComponent による組み込み型アトミックサービス
データとストレージ
- KVStore — キーと値のデータベースの読み書き
- DatabaseReadWrite — C-API によるリレーショナルデータベース
- DataCache — データキャッシュによるコールドスタートの高速化
- BackupRestore — バックアップ/復元フレームワークによるデータ移行
- GenerateSandboxFile — サンドボックスファイルの生成
- NativeFileIO — ネイティブ側でのファイルの読み書き
- NativeFileAccess — ネイティブ側でのファイルアクセス
- TurboTransJSON — 高速な JSON シリアライズ(turbo_trans)
ネットワークと通信
- NetworkReconnection — アプリのネットワーク再接続
- NetBoost — 複数ネットワークの同時利用による高速化
- RcpFileTransfer — Remote Communication Kit を介したファイルのアップロード/ダウンロード
- RemoteCommunicationPlatform — RCP ネットワークリクエスト(フォーム、証明書、DNS)
- WebCrossDomain — ArkWeb インターセプターと Cookie を介した Web クロスドメイン通信
- MultiWeb — レスポンシブな複数 Web レイアウト
- OnlineEditorCollaboration — デバイス間の共同編集に対応したオンライン文書編集
セキュリティと認証
- UserAuth — 顔認証/指紋認証とパスワード保管庫の自動入力
- PermissionApplication — 権限リクエストのフロー
- AntiPeep — 機密情報の覗き見防止
- UniversalKeystoreCollection — HUKS キー管理(暗号化/復号、署名/検証)
- DigitalShield — 取引の生体認証
- DeviceSecurityKit_sampleCode_SafetyDetectDemo_ArkTS — デバイス環境と URL の安全性検出
AI と ML
- MindSporeLiteArkTS — オンデバイス画像分類(MindSpore Lite ArkTS API)
- MindSporeLiteCpp — オンデバイス画像分類(MindSpore Lite C++ API)
- MSLiteHumanSegmentation — オンデバイス人物セグメンテーション
- MSLiteSceneRecognition — オンデバイスシーン検出
- MSLiteStyleTransform — オンデバイス画像スタイル変換
- RAG_QA — オンデバイスのナレッジ処理を用いた RAG ベースの質疑応答
マルチデバイスとウェアラブル
- MultiDeviceCamera — プレビューの回転に対応した、スマートフォン、折りたたみ式デバイス、タブレットでのカメラ利用
- MultiDeviceCommunication — 一度の開発で複数デバイスに対応する IM アプリ
- Phone_Connection — スマートフォンとウォッチの通信、心拍数モニタリング
- SmartWatchCarControl — ウォッチ用車両操作アプリ
- SmartWatchMap — ウォッチ用マップアプリ
- SmartWatchTakeTaxi — ウォッチ用タクシーアプリ
- WearableBus — ウォッチ用バス乗換アプリ
- WearableMusic — ウォッチ用音楽アプリ
- APILevelAdapt — 複数の API バージョンとの互換性(ArkTS + Native)
並行処理とパフォーマンス
- UseTaskPool — TaskPool を介したマルチスレッドタスク
- UseSendable — スレッド間通信と UI 更新のための Sendable
- MultiThreadIO — TaskPool + @Sendable によるデータベースとファイルの I/O
- NativeSubMainThreadCommunication — Native サブスレッドから UI メインスレッドへの通信
- FunctionFlowRuntimeKit-SampleCode-ConcurrentQueue — FFRT 並行キュー
- FunctionFlowRuntimeKit-SampleCode-TaskGraph — FFRT タスクグラフの依存関係
- UtilizeHWCEfficiently — 低消費電力の HWC 合成
システムとプラットフォーム
- BackTaskImplement — アプリの継続性を保つバックグラウンドタスク
- LiveViewLockScreen — ロック画面のライブビュー
- IntentsKitGameRevisit — Intent ベースのゲーム再訪おすすめ
- ContinuePublish — デバイス間のコンテンツ公開(アプリ継続)
- HiTraceMeterPrefTag — HiTraceMeter によるパフォーマンス追跡
- ObtainingDeviceID — デバイス識別子の取得
- BluetoothLowEnergy — BLE デバイスの接続と通信
- NFCTag — NFC によるアプリ起動
- QueryAppPackageInfo — アプリパッケージ情報の照会
- SystemEnvVarSubscriber — システム環境変数の購読
- DesktopExtensionKit-samplecode — Desktop Extension Kit を介したステータスバー連携
グラフィックスと 3D
- Graphics3D — 3D エンジン API の使用
- DocsSample_XComponent — XComponent のセルフレンダリングと AI 分析
- DocsSample_Graphics — グラフィックスサブシステムのサンプル
Web とハイブリッド
- H5Launch — H5 のコールドスタート高速化
- ExecutingJSWithJSVM — JSVM-API:エンジンの作成、JS の実行、破棄
- DocsSample_ArkWeb — ArkWeb コンポーネントのサンプル
- SmallWindowScene — 小ウィンドウ(フローティング)シナリオ
カタログ全体:
https://gitcode.com/HarmonyOS_Samples—git clone https://gitcode.com/HarmonyOS_Samples/<name>.gitで任意のサンプルをクローンできます
API 21(SDK 6.0.1)— 確認済みのコンパイルエラーと修正
以下のエラーは、実機の Mate 70 Pro ビルドで検証済みです。以下の項目はすべて assembleDevHqf で ArkTS Compiler Error を引き起こしました。
DataChangeListener には新旧両方のメソッド名が必要です
API 21 では、DataChangeListener を implements するすべてのクラスに、以下をすべて含める必要があります。
// New names (current)
onDataReloaded(): void
onDataAdd(index: number): void
onDataDelete(index: number): void
onDataChange(index: number): void
onDataMove(from: number, to: number): void
// Deprecated aliases — still required by the interface in API 21
onDataAdded(index: number): void
onDataDeleted(index: number): void
onDataChanged(index: number): void
onDataMoved(from: number, to: number): void
onDatasetChange(dataOperations: DataOperation[]): void
これはテスト用スタブ(NoopListener など)を含むすべてのクラスに適用されます。
notificationManager.addSlot() — NotificationSlot ではなく SlotType を渡します
// ❌ API 9 style
await notificationManager.addSlot({ type: SlotType.SOCIAL_COMMUNICATION, desc: '...' })
// ✅ API 12+ style
await notificationManager.addSlot(notificationManager.SlotType.SOCIAL_COMMUNICATION)
また、型がリテラル型に絞り込まれるのを防ぐため、定数には必ず明示的に型注釈を付けてください。
// ❌ infers as SlotType.SOCIAL_COMMUNICATION — not assignable to SlotType
const SLOT_ID = notificationManager.SlotType.SOCIAL_COMMUNICATION
// ✅
const SLOT_ID: notificationManager.SlotType = notificationManager.SlotType.SOCIAL_COMMUNICATION
NotificationRequest.slotType — 互換性のない SlotType モジュール
NotificationRequest.slotType は @ohos.notification.SlotType(旧モジュール)として型付けされていますが、notificationManager.SlotType は @ohos.notificationManager(新モジュール)のものです。これらは名目上異なる型であり、新しい型を古い型に代入するとコンパイルエラーになります。slotType は省略してください(省略可能です)。
const request: notificationManager.NotificationRequest = {
id: NOTIFICATION_ID,
// slotType omitted — routes to the slot created by addSlot() automatically
content: { ... }
}
API 21 では NotificationSlotLevel は notificationManager に存在しません。使用箇所を削除してください。
Permissions 型 — @ohos.bundleManager はインポートできません
import type { Permissions } from '@ohos.bundleManager' は「Cannot find module」エラーになります。bundleManager は @kit.AbilityKit 経由でのみアクセスできます。回避策として、値がすべて有効な Permissions リテラルであるローカルの部分型を宣言します。
// ✅ subtype of Permissions — assignable to the SDK's Permissions parameter
type AppPermission = 'ohos.permission.CAMERA' | 'ohos.permission.APPROXIMATELY_LOCATION'
async function hasPermission(name: AppPermission): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager()
const bundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
const status = atManager.checkAccessTokenSync(bundleInfo.appInfo.accessTokenId, name)
return status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
}
requestPermissionsFromUser — void & Promise オーバーロードの交差
この関数には、コールバックオーバーロード(void を返します)と Promise オーバーロード(Promise<PermissionRequestResult> を返します)の両方があります。TypeScript がこれらを void & Promise<…> として交差させることがあり、その結果、.authResults が存在しないと判断されます。また、API 21 では abilityAccessCtrl.PermissionRequestResult は名前空間からエクスポートされていません。
修正方法:結果インターフェースをローカルで宣言し、キャストします。
interface PermResult {
authResults: abilityAccessCtrl.GrantStatus[]
}
async function requestPermission(ctx: common.UIAbilityContext, name: AppPermission): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager()
const result = await (atManager.requestPermissionsFromUser(ctx, [name]) as Promise<PermResult>)
return result.authResults[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
}
getContext(this) は非推奨です
すべてのコンポーネントメソッドで次のように置き換えてください。
// ❌
const ctx = getContext(this) as common.UIAbilityContext
// ✅
const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext
モジュールレベル(コンポーネント外)の関数には、代わりにパラメーターとして ctx を渡す必要があります。
マルチデバイス/折りたたみ式画面への対応(API 21)
ブレークポイントの検出
import { display } from '@kit.ArkUI'
export const BP_SM = 'sm' // < 600 vp — phone portrait
export const BP_MD = 'md' // < 840 vp — phone landscape / small tablet
export const BP_LG = 'lg' // >= 840 vp — large tablet / foldable unfolded
export function getBreakpoint(): string {
try {
const d = display.getDefaultDisplaySync()
const widthVp = d.width / d.densityPixels
if (widthVp < 600) return BP_SM
if (widthVp < 840) return BP_MD
return BP_LG
} catch (e) {
return BP_SM
}
}
折りたたみ式画面 — 開閉イベントを監視する
// display.on callback receives (id: number) — NOT empty params
private displayListener: (id: number) => void = (_id: number) => {
this.breakpoint = getBreakpoint()
}
aboutToAppear(): void {
this.breakpoint = getBreakpoint()
display.on('change', this.displayListener)
}
aboutToDisappear(): void {
display.off('change', this.displayListener)
}
レスポンシブレイアウトの切り替え(.visibility ではなく if/else)
スマートフォンとタブレットのレイアウトを切り替えるときは、必ず if/else を使用してください。.visibility(Visibility.None) は非表示にしますが、レイアウト計算は行われます。リソースを浪費し、計測時の不具合を引き起こすこともあります。
if (this.breakpoint === BP_SM) {
// Phone: single-column List + LazyForEach
List({ space: 8 }) { LazyForEach(...) }
} else {
// Tablet/foldable: GridRow with responsive columns
Scroll() {
GridRow({
columns: { sm: 1, md: 2, lg: 3 },
gutter: { x: 12, y: 12 },
breakpoints: { value: ['600vp', '840vp'] }
}) {
ForEach(this.gridItems, (item) => { GridCol() { MyCard({ record: item }) } })
}
}
}
GridRow/GridCol は LazyForEach をサポートしません
GridRow/GridCol はレスポンシブレイアウト用のコンテナーであり、遅延ローダーではありません。内部では ForEach を使用してください。データ変更に応じてグリッドを更新するには、DataChangeListener 経由で更新する @State gridItems: T[] を保持します。
class GridRefreshListener implements DataChangeListener {
private updateFn: () => void
constructor(fn: () => void) { this.updateFn = fn }
// Must implement ALL methods — see API 21 DataChangeListener note above
onDataReloaded(): void { this.updateFn() }
onDataAdd(_i: number): void { this.updateFn() }
onDataAdded(_i: number): void { this.updateFn() }
// ... all other methods
onDataChange(_i: number): void { /* @ObjectLink handles per-item updates */ }
onDataChanged(_i: number): void { }
}
// In @Entry @Component:
@State gridItems: MyRecord[] = []
private gridListener = new GridRefreshListener(() => {
this.gridItems = [...this.dataSource.getAllData()]
})
aboutToAppear() { this.dataSource.registerDataChangeListener(this.gridListener) }
aboutToDisappear() { this.dataSource.unregisterDataChangeListener(this.gridListener) }
@Provide/@Consume でブレークポイントを共有する
@Entry のルートで宣言し、任意の NavDestination 子コンポーネントで使用します。
// Root
@Provide('breakpoint') breakpoint: string = BP_SM
// Detail page (NavDestination)
@Consume('breakpoint') breakpoint: string
Navigation と NavDestination は同じコンポーネントサブツリーに属するため、@Provide/@Consume はその間で機能します。
Location Kit(geoLocationManager)
import { geoLocationManager } from '@kit.LocationKit'
// Permission required: ohos.permission.APPROXIMATELY_LOCATION (user_grant)
// module.json5: reason + usedScene required
const pos = await geoLocationManager.getCurrentLocation({
priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
scenario: geoLocationManager.LocationRequestScenario.UNSET,
timeoutMs: 10000
})
const addresses = await geoLocationManager.getAddressesFromLocation({
latitude: pos.latitude,
longitude: pos.longitude,
maxItems: 1
})
// Build readable location string from GeoAddress fields:
// administrativeArea (省) → subAdministrativeArea (市) → locality (区) → subLocality → placeName
Weather Service Kit — 気象データ API
デバイスの位置情報を使用する場合は、
ohos.permission.LOCATION(またはAPPROXIMATELY_LOCATION)が必要です。
import { weatherService } from '@kit.WeatherServiceKit';
const weatherRequest: weatherService.WeatherRequest = {
location: { latitude: 39.9042, longitude: 116.4074 }, // Beijing
limitedDatasets: [
weatherService.Dataset.CURRENT, // current conditions
weatherService.Dataset.DAILY, // daily forecast
weatherService.Dataset.HOURLY, // hourly forecast
weatherService.Dataset.ALERTS, // severe weather alerts
weatherService.Dataset.INDICES, // life indices (UV, air quality...)
weatherService.Dataset.TIDES, // coastal tides
weatherService.Dataset.MINUTE, // minute-level precipitation
],
};
try {
const weather = await weatherService.getWeather(weatherRequest);
// weather.currentWeather.temperature, .humidity, .conditionCode, ...
// weather.dailyForecast?.days[] (each: tempMax/tempMin/precipitation/sunrise/sunset)
// weather.hourlyForecast?.hours[]
// weather.weatherAlerts?.alerts[] (severity, summary)
} catch (err) {
console.error('Weather fetch failed:', err);
}
Notification Kit(notificationManager)
import { notificationManager } from '@kit.NotificationKit'
// 1. Create slot (call once at app init)
await notificationManager.addSlot(notificationManager.SlotType.SOCIAL_COMMUNICATION)
// 2. Check and request notification enable
const enabled = await notificationManager.isNotificationEnabled()
if (!enabled) await notificationManager.requestEnableNotification() // deprecated but functional
// 3. Publish
const request: notificationManager.NotificationRequest = {
id: 1001,
// OMIT slotType — see API 21 type mismatch note above
content: {
notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: { title: '标题', text: '正文', additionalText: '副文本' }
},
deliveryTime: Date.now(),
showDeliveryTime: true,
tapDismissed: true
}
await notificationManager.publish(request)
// 4. Cancel
await notificationManager.cancel(1001)
relationalStore(SQLite)
import { relationalStore } from '@kit.ArkData'
import { common } from '@kit.AbilityKit'
// Init (call in aboutToAppear / UIAbility.onCreate)
const store = await relationalStore.getRdbStore(ctx, {
name: 'my_db.db',
securityLevel: relationalStore.SecurityLevel.S1
})
await store.executeSql('CREATE TABLE IF NOT EXISTS records (...)')
// Insert
const values: relationalStore.ValuesBucket = { id: '1', title: 'Hello' }
await store.insert('records', values)
// Query (ordered)
const predicates = new relationalStore.RdbPredicates('records')
predicates.orderByDesc('timestamp')
const rs = await store.query(predicates, ['id', 'title', 'timestamp'])
while (rs.goToNextRow()) {
const id = rs.getString(rs.getColumnIndex('id'))
}
rs.close()
// Update
const predicates2 = new relationalStore.RdbPredicates('records')
predicates2.equalTo('id', '1')
await store.update({ title: 'Updated' }, predicates2)
// Delete
const predicates3 = new relationalStore.RdbPredicates('records')
predicates3.equalTo('id', '1')
await store.delete(predicates3)
preferences(キーと値の設定)
import { preferences } from '@kit.ArkData'
const store = await preferences.getPreferences(ctx, 'user_settings')
// Read (with default)
const val = (await store.get('sort_order', 'time_desc')) as string
// Write + flush
await store.put('sort_order', 'time_asc')
await store.flush()
UiTest — よく使われるパターン(arkxtest)
import { Driver, ON, Component } from '@ohos.UiTest'
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'
const DELEGATOR = AbilityDelegatorRegistry.getAbilityDelegator()
// Launch app before tests
beforeAll(async (done: Function) => {
await DELEGATOR.startAbility({ bundleName: 'com.example.app', abilityName: 'EntryAbility' })
await new Promise(r => setTimeout(r, 2000)) // wait for UI to render
done()
})
it('example', 0, async (done: Function) => {
const driver = Driver.create()
// Find by text / type / content description
const btn = await driver.findComponent(ON.text('发布'))
const input = await driver.findComponent(ON.type('TextInput'))
const items = await driver.findComponents(ON.type('SymbolGlyph'))
// Actions — ALL must be awaited
await btn.click()
await input.inputText('hello')
// Wait after state-changing actions
await new Promise(r => setTimeout(r, 800))
// Component refs become stale after state changes — re-find
const btnAfter = await driver.findComponent(ON.text('发布'))
// Get bounds for position-based selection
const bounds = await btn.getBounds() // { top, left, right, bottom }
// Navigate back
await driver.pressBack()
done()
})
主なルール:
it()内の UiTest API 呼び出しはすべてawaitしてください- 状態を変更するクリックや入力の後は、コンポーネント参照が古くなることがあります。必ず
findComponentを再実行してください - テストは実機またはエミュレーターでのみ実行できます。Previewer は UiTest をサポートしていません
Driver.create()は各itブロックで新しく作成してください(テスト間で共有しないでください)- アサーションの前に、新しいページが描画されるよう、画面遷移後は
sleep/setTimeoutを使用してください
ArkWeb — Web コンポーネント
@kit.ArkWeb の Web コンポーネントを使用して、ArkTS に Web コンテンツを埋め込みます。
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct BrowserPage {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: 'https://example.com', controller: this.controller })
.javaScriptAccess(true)
.domStorageAccess(true)
.fileAccess(true)
.onPageBegin((event) => { console.log('loading:', event?.url); })
.onPageEnd((event) => { console.log('loaded:', event?.url); })
.onErrorReceive((event) => { console.error('web error:', event?.error.getErrorInfo()); })
.darkMode(WebDarkMode.Auto) // follow system dark mode
.forceDarkAccess(true)
}
}
}
JS ↔ ArkTS ブリッジ(javaScriptProxy)
// Register ArkTS object callable from JS
Web({ src: 'https://example.com', controller: this.controller })
.javaScriptProxy({
object: {
callNative: (msg: string) => {
console.log('from JS:', msg);
return 'ArkTS received: ' + msg;
}
},
name: 'NativeBridge',
methodList: ['callNative'],
controller: this.controller
})
// In the web page: window.NativeBridge.callNative('hello');
// Call JS from ArkTS
this.controller.runJavaScript('window.updateUI("data")', (err, result) => {
console.log('JS result:', result);
});
カスタム User-Agent と Cookie
// Append to default UA
webview.WebviewController.setCustomUserAgent(
webview.WebviewController.getDefaultUserAgent() + ' MyApp/1.0'
);
// Manage cookies
import { webCookie } from '@kit.ArkWeb';
webCookie.setCookie('https://example.com', 'token=abc; path=/');
webCookie.saveCookieAsync(); // persist to disk
リソースリクエストをインターセプトする
Web({ src: '...', controller: this.controller })
.onInterceptRequest((event) => {
if (event?.request.getRequestUrl().includes('/api/')) {
// Return custom response
const resp = new WebResourceResponse();
resp.setResponseData('{"intercepted":true}');
resp.setResponseMimeType('application/json');
resp.setResponseEncoding('utf-8');
resp.setResponseCode(200);
resp.setReasonMessage('OK');
return resp;
}
return null; // null = load normally
})
Form Kit — ArkTS サービスカード(服务卡片)
サービスカードはサンドボックス化された FormExtensionAbility プロセスで実行され、ArkUI のサブセットを使用します。
FormExtensionAbility のライフサイクル
// entry/src/main/ets/formextensionability/EntryFormAbility.ets
import { FormExtensionAbility, formBindingData, FormInfo, formProvider } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want: Want) {
// Called when user adds card to home screen
const formData = { title: 'Hello', count: 0 };
return formBindingData.createFormBindingData(formData);
}
onCastToNormalForm(formId: string) { }
onUpdateForm(formId: string) {
// Periodic/requested update
const data = formBindingData.createFormBindingData({ count: Date.now() });
formProvider.updateForm(formId, data);
}
onRemoveForm(formId: string) { }
onFormEvent(formId: string, message: string) {
// Triggered by postCardAction in the card UI
console.log('card event:', formId, message);
}
}
カード UI(EntryFormAbility/pages/Card.ets)
// Cards are ArkUI components but with restricted APIs (no @State mutation from events)
// Use postCardAction to route events back to FormExtensionAbility
@Entry
@Component
struct CardPage {
@LocalStorageProp('title') title: string = '';
@LocalStorageProp('count') count: number = 0;
build() {
Column({ space: 8 }) {
Text(this.title).fontSize(16).fontWeight(FontWeight.Bold)
Text(`Count: ${this.count}`).fontSize(14)
Button('+1')
.onClick(() => {
postCardAction(this, {
action: 'message',
params: { event: 'increment' }
});
})
}.padding(12)
}
}
module.json5 — フォームの宣言
{
"extensionAbilities": [{
"name": "EntryFormAbility",
"srcEntry": "./ets/formextensionability/EntryFormAbility.ets",
"type": "form",
"metadata": [{
"name": "ohos.extension.form",
"resource": "$profile:form_config"
}]
}]
}
resources/base/profile/form_config.json
{
"forms": [{
"name": "widget",
"displayName": "$string:widget_display_name",
"description": "$string:widget_desc",
"src": "./ets/formextensionability/pages/Card.ets",
"uiSyntax": "arkts",
"window": { "designWidth": 720, "autoDesignWidth": true },
"colorMode": "auto",
"isDynamic": true,
"updateEnabled": true,
"scheduledUpdateTime": "10:30",
"updateDuration": 1,
"defaultDimension": "2*2",
"supportDimensions": ["1*2", "2*2", "2*4"]
}]
}
UIDesignKit — アイコン処理と HdsNavigation
HarmonyOS 5.0 以降(API 12 以降)で利用できます。Huawei Design System のコンポーネントを提供します。
hdsDrawable — アイコンの適応処理
import { hdsDrawable } from '@kit.UIDesignKit';
// Render app icon with system-consistent adaptive shape (squircle, circle, etc.)
const drawable = new hdsDrawable.HdsAdaptiveIconDrawable(
context, // UIAbilityContext or ApplicationContext
iconResource, // Resource ($r('app.media.icon'))
{ size: 48 } // options: size in vp
);
const pixelMap = await drawable.getPixelMap();
HdsNavigation — システムスタイルのナビゲーションコンポーネント
import { HdsNavigation, HdsNavigationItem } from '@kit.UIDesignKit';
@Entry
@Component
struct MainPage {
@State currentIndex: number = 0;
private tabs: HdsNavigationItem[] = [
{ icon: $r('app.media.home'), selectedIcon: $r('app.media.home_filled'), label: '首页' },
{ icon: $r('app.media.mine'), selectedIcon: $r('app.media.mine_filled'), label: '我的' },
];
build() {
Column() {
// Page content
Blank()
HdsNavigation({
items: this.tabs,
selectedIndex: this.currentIndex,
onItemClick: (index: number) => { this.currentIndex = index; }
})
}.height('100%')
}
}
HdsNavigation は、動的なぼかし背景、カスタムコンテンツ領域(バッジ、ドットインジケーター)、項目上のメッセージ数バッジに対応しています。
Map Kit — MapComponent
import { mapCommon, map } from '@kit.MapKit';
import { AsyncCallback } from '@kit.BasicServicesKit';
@Entry
@Component
struct MapPage {
private mapOptions: mapCommon.MapOptions = {
position: {
target: { latitude: 39.9042, longitude: 116.4074 }, // Beijing
zoom: 12
}
};
private mapController?: map.MapComponentController;
build() {
Column() {
MapComponent({
mapOptions: this.mapOptions,
mapCallback: (err, controller) => {
if (!err) {
this.mapController = controller;
this.addMarker();
}
}
}).width('100%').layoutWeight(1)
}
}
private addMarker() {
this.mapController?.addMarker({
position: { latitude: 39.9042, longitude: 116.4074 },
title: 'Tiananmen',
snippet: 'Beijing city center'
});
}
}
module.json5 で必要な権限:ohos.permission.LOCATION と ohos.permission.APPROXIMATELY_LOCATION。
コールドスタートの最適化
HarmonyOS では、コールドスタートを アプリの起動 → 最初のフレームの描画 として計測します。目標はミドルレンジのデバイスで 1000ms 未満です。
遅延インポート(import())
// ❌ Eager — all modules parsed at startup even if unused
import { HeavyModule } from '../utils/HeavyModule';
// ✓ Lazy — parsed only when first used
let heavyModule: typeof import('../utils/HeavyModule') | null = null;
async function useHeavy() {
if (!heavyModule) {
heavyModule = await import('../utils/HeavyModule');
}
heavyModule.HeavyModule.doWork();
}
ネットワークリクエスト — 最初のフレームの描画後まで延期
// EntryAbility.ets
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', (err) => {
if (!err) {
// First frame committed — now safe to start network
AppStartupData.prefetch();
}
});
}
その他のコールドスタートに関するルール
onCreate()/onWindowStageCreate()で負荷の高い同期処理を避けてください — 10ms を超えるタスクには TaskPool を使用します- モジュールの読み込み時にグローバルシングルトンを構築するのは最小限に抑えます
- リスト項目コンポーネントで
@Reusableを使用し、初回表示時の再計測/再レイアウトを避けます - 描画ループ内で
hilogを呼び出さないでください(I/O コストがかかります) - DevEco Profiler の Launch タスクでプロファイルし、正確なフレームのタイムラインを確認します
メモリの最適化
onMemoryLevel コールバック
// AbilityStage.ets or UIAbility.ets
onMemoryLevel(level: AbilityConstant.MemoryLevel): void {
if (level === AbilityConstant.MemoryLevel.MEMORY_LEVEL_CRITICAL) {
// Release non-essential caches immediately
ImageCache.instance.clear();
DataCache.instance.trimToSize(10);
} else if (level === AbilityConstant.MemoryLevel.MEMORY_LEVEL_LOW) {
DataCache.instance.trimToSize(50);
}
}
LRUCache — 上限付き画像/データキャッシュ
import { util } from '@kit.ArkTS';
class ImageCache {
static instance = new ImageCache();
private lru = new util.LRUCache<string, PixelMap>(50); // max 50 entries
put(key: string, pm: PixelMap) { this.lru.put(key, pm); }
get(key: string): PixelMap | undefined { return this.lru.get(key); }
clear() { this.lru.clear(); }
trimToSize(n: number) {
while (this.lru.length > n) {
this.lru.afterRemoval(false, this.lru.keys()[0], undefined, undefined);
}
}
}
再利用可能メモリ(大きなビットマップ)
import { image } from '@kit.ImageKit';
// Create PixelMap as purgeable — OS can reclaim when memory is tight,
// and will regenerate it from the source on next access
const pixelMap = await image.createPixelMap(buffer, {
size: { width: 1920, height: 1080 },
editable: false
});
// No extra API needed — PixelMap is automatically purgeable when editable=false
// and created from a decodable source (file path or buffer)
メモリに関する一般的なルール
- リークを防ぐため、
aboutToDisappear()/onBackground()でリスナーの登録を解除します - コンポーネントが存続し続ける原因になるため、長期間存続するクロージャ内で
thisをキャプチャしないでください - 繰り返し描画する場合は、新たに作成するのではなく PixelMap オブジェクトを再利用します
- サムネイルには
image.ImageSourceと遅延デコードを使用し、フル解像度ではデコードしないでください - TaskPool のスレッド間でヒープは共有されません —
@Sendableオブジェクトはコピーを避けますが、所有権を移転します
Camera Kit
CameraPicker — 権限不要の写真/動画撮影
写真または動画を撮影する最も簡単な方法です — システムのカメラ UI を起動します。カメラ権限は不要です。
import { camera, cameraPicker as picker } from '@kit.CameraKit';
import { fileIo, fileUri } from '@kit.CoreFileKit';
// Create a temp file to receive the capture result
const pathDir = getContext(this).filesDir;
const filePath = pathDir + `/${Date.now()}.tmp`;
fileIo.createRandomAccessFileSync(filePath, fileIo.OpenMode.CREATE);
const pickerProfile: picker.PickerProfile = {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
saveUri: fileUri.getUriFromPath(filePath) // omit to save to media library
};
// Launch system camera — user takes photo/video and confirms
const result = await picker.pick(
getContext(this),
[picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO],
pickerProfile
);
if (result.resultCode === 0) {
const uri = result.resultUri; // file URI of captured media
const isPhoto = result.mediaType === picker.PickerMediaType.PHOTO;
}
完全なカメラセッション(プレビュー+写真撮影)
ohos.permission.CAMERA が必要です。フロー:CameraManager → CameraInput → Session → PreviewOutput/PhotoOutput。
import { camera } from '@kit.CameraKit';
// 1. Get camera manager and device
const cameraManager = camera.getCameraManager(context);
const cameras = cameraManager.getSupportedCameras();
const cameraDevice = cameras[0];
// 2. Create input and open
const cameraInput = cameraManager.createCameraInput(cameraDevice);
await cameraInput.open();
// 3. Get output capabilities
const capability = cameraManager.getSupportedOutputCapability(
cameraDevice, camera.SceneMode.NORMAL_PHOTO
);
// 4. Create preview output (surfaceId from XComponent)
const previewOutput = cameraManager.createPreviewOutput(
capability.previewProfiles[0], surfaceId
);
// 5. Create photo output
const photoOutput = cameraManager.createPhotoOutput(capability.photoProfiles[0]);
// 6. Build and start session
const session = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
session.beginConfig();
session.addInput(cameraInput);
session.addOutput(previewOutput);
session.addOutput(photoOutput);
await session.commitConfig();
await session.start();
// 7. Capture photo
photoOutput.on('photoAvailable', (err, photo) => {
const imageObj = photo.main;
imageObj.getComponent(image.ComponentType.JPEG, (err, component) => {
const buffer: ArrayBuffer = component.byteBuffer!;
// Save buffer to file, then release
imageObj.release();
});
});
photoOutput.capture({ quality: camera.QualityLevel.QUALITY_LEVEL_HIGH });
// 8. Cleanup: session.stop() → cameraInput.close() → previewOutput.release() → session.release()
カメラプレビュー用の XComponent
@Entry @Component
struct CameraPreview {
private ctrl = new XComponentController();
private surfaceId = '';
build() {
XComponent({ type: XComponentType.SURFACE, controller: this.ctrl })
.onLoad(() => {
this.surfaceId = this.ctrl.getXComponentSurfaceId();
// Use this.surfaceId to create previewOutput
})
.width('100%').height('100%')
}
}
主な権限:ohos.permission.CAMERA(写真/動画)、ohos.permission.MICROPHONE(音声録音)。
Audio Kit — 再生と録音
AudioRenderer(ArkTS)— PCM 再生
import { audio } from '@kit.AudioKit';
const rendererOptions: audio.AudioRendererOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000,
channels: audio.AudioChannel.CHANNEL_2,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
},
rendererInfo: {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // determines volume type & focus
rendererFlags: 0
}
};
const renderer = await audio.createAudioRenderer(rendererOptions);
renderer.on('writeData', (buffer: ArrayBuffer) => {
// Fill buffer with PCM data
return audio.AudioDataCallbackResult.VALID;
});
await renderer.start();
// ... renderer.pause() / renderer.stop() / renderer.release()
オーディオフォーカス(InterruptEvent)
システムは StreamUsage に基づいてオーディオフォーカスを自動的に管理します。フォーカスの変更を監視します。
renderer.on('audioInterrupt', (event: audio.InterruptEvent) => {
if (event.forceType === audio.InterruptForceType.INTERRUPT_FORCE) {
switch (event.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_PAUSE:
// System paused us — update UI to paused state
break;
case audio.InterruptHint.INTERRUPT_HINT_STOP:
// Permanently lost focus — stop playback
break;
case audio.InterruptHint.INTERRUPT_HINT_DUCK:
// Volume lowered to 20% — optional UI update
break;
case audio.InterruptHint.INTERRUPT_HINT_UNDUCK:
// Volume restored
break;
}
} else if (event.hintType === audio.InterruptHint.INTERRUPT_HINT_RESUME) {
// Can resume playback (SHARE type — app decides)
await renderer.start();
}
});
StreamUsage → 音量タイプのマッピング
| StreamUsage | 音量タイプ | 一般的な用途 |
|---|---|---|
MUSIC / MOVIE / AUDIOBOOK / GAME | メディア | 音楽、動画、オーディオブック、ゲームの BGM |
VOICE_COMMUNICATION | 通話 | VoIP 通話 |
RINGTONE / NOTIFICATION | 着信音 | 着信、通知 |
ALARM | アラーム | アラーム(Bluetooth 接続時もスピーカーから再生) |
NAVIGATION | — | ナビの音声(音楽の音量を下げますが、一時停止しません) |
AudioSession — カスタムフォーカス戦略
const audioManager = audio.getAudioManager();
const sessionManager = audioManager.getSessionManager();
// Activate session with custom concurrency mode
const strategy: audio.AudioSessionStrategy = {
concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY_PAUSE_OTHERS
};
await sessionManager.activateAudioSession(strategy);
// Monitor deactivation
sessionManager.on('audioSessionDeactivated', (event) => {
console.info('Session deactivated:', event.reason);
});
// When done
await sessionManager.deactivateAudioSession();
同時再生モード:CONCURRENCY_DEFAULT、CONCURRENCY_MIX_WITH_OTHERS、CONCURRENCY_DUCK_OTHERS、CONCURRENCY_PAUSE_OTHERS。
バックグラウンド再生の要件
バックグラウンドでオーディオを再生するアプリは、必ず次の処理を行ってください。
- メディア/ゲームのストリーム(
MUSIC/MOVIE/AUDIOBOOK/GAME)の場合:AVSession を統合し、長時間タスク(AUDIO_PLAYBACK)もリクエストします - その他の音声ストリームの場合:
AUDIO_PLAYBACKの長時間タスクのみをリクエストします - これらを行わない場合、アプリがバックグラウンドに移行したとき、システムによってミュートされ、フリーズします
コードの難読化(ArkGuard)
build-profile.json5 → arkOptions.obfuscation で有効にします。ArkGuard は ArkTS/TS/JS の名前の難読化、コード圧縮、コメント削除に対応しています。
主な難読化オプション
# obfuscation-rules.txt
-enable-property-obfuscation # obfuscate property names
-enable-toplevel-obfuscation # obfuscate top-level scope names
-enable-export-obfuscation # obfuscate import/export names
-enable-filename-obfuscation # obfuscate file/folder names
-enable-string-property-obfuscation # also obfuscate string literal property names
-compact # remove whitespace and newlines
-remove-log # delete console.* statements
-print-namecache ./nameCache.json # save name mapping (keep for crash analysis!)
一般的なホワイトリスト設定シナリオ(-keep-property-name)
実行時エラーを避けるため、以下のプロパティは必ずホワイトリストに登録してください。
-keep-property-name
# 1. Dynamic property access
# obj['x' + i] → keep x0, x1, x2 ...
# Object.defineProperty(obj, 'y', {}) → keep y
# 2. JSON parsing fields
# JSON.parse / JSON.stringify → keep all serialized field names
# 3. Network request fields
# http.request extraData: { username, password } → keep field names
# 4. Database fields (ValuesBucket keys)
# relationalStore column names → keep
# 5. NAPI / .so interop
# import from 'libentry.so' → keep exported function names
# 6. ArkUI @Component @State properties are auto-preserved (no action needed)
# SDK API names are auto-preserved (no action needed)
ファイル名のホワイトリスト(-keep-file-name)
-keep-file-name
# System-loaded files that must not be renamed:
# - Worker thread files
# - Ability entry files (auto-collected since DevEco 5.0.3.500)
# - Dynamic import paths: const m = await import('./SomePath')
# - System route table paths (pageSourceFile in route_map.json, auto since API 20)
ソース内の特定の名前を保持する(@KeepSymbol)
// Since API 19: use comments to mark names as non-obfuscatable
// @KeepSymbol
class ImportantClass {
// @KeepSymbol
criticalProp: string = '';
}
注意事項
- ArkGuard のプロパティホワイトリストはグローバルに適用されます。
nameを保持すると、すべてのファイルにあるnameという名前のプロパティが保持されます - プロパティ難読化を有効にして HAR をビルドすると、
enumのメンバーは自動的にホワイトリストに収集されます - 難読化されたクラッシュスタックをデコードするため、リリースごとに必ず
nameCache.jsonを保存してください -compactを指定するとすべての改行が削除されるため、クラッシュスタックの行番号が役に立たなくなります(リリースビルドでは列情報は提供されません)- SDK API 定数(例:
'ohos.want.action.home')に一致する文字列プロパティは自動的にホワイトリストに登録されません。プロパティキーとして使用する場合は手動で追加してください
Scan Kit — バーコードのスキャンと生成
デフォルト UI でのスキャン(カメラ権限は不要)
システムのスキャン UI を起動します。カメラは事前に許可されているため、権限リクエストは不要です。
import { scanCore, scanBarcode } from '@kit.ScanKit';
const options: scanBarcode.ScanOptions = {
scanTypes: [scanCore.ScanType.ALL],
enableMultiMode: true,
enableAlbum: true
};
try {
const result = await scanBarcode.startScanForResult(
getContext(this), // or this.getUIContext().getHostContext()
options
);
// result.originalValue — decoded string
// result.scanType — code type (QR, EAN-13, etc.)
console.info('Scan result:', result.originalValue);
} catch (err) {
console.error('Scan failed:', err.code, err.message);
}
対応するコードの種類:QR Code、Data Matrix、PDF417、Aztec、EAN-8、EAN-13、UPC-A、UPC-E、Codabar、Code 39/93/128、ITF-14。
画像のデコード(写真内のバーコードを検出)
import { scanCore, scanBarcode, detectBarcode } from '@kit.ScanKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
// Pick an image from gallery
const picker = new photoAccessHelper.PhotoViewPicker();
const pickerResult = await picker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 1
});
// Decode barcode from selected image
const inputImage: detectBarcode.InputImage = { uri: pickerResult.photoUris[0] };
const results = await detectBarcode.decode(inputImage, {
scanTypes: [scanCore.ScanType.ALL],
enableMultiMode: true
});
// results is Array<scanBarcode.ScanResult>
カスタム UI でのスキャン(ohos.permission.CAMERAが必要)
スキャン UI を完全に制御するには、@kit.ScanKitのcustomScanを使用します。
import { scanCore, scanBarcode, customScan } from '@kit.ScanKit';
// 1. Init
customScan.init({ scanTypes: [scanCore.ScanType.ALL], enableMultiMode: true });
// 2. Start with XComponent surfaceId
const viewControl: customScan.ViewControl = { width, height, surfaceId };
const results = await customScan.start(viewControl);
// 3. Control: customScan.openFlashLight() / closeFlashLight()
// customScan.setZoom(2.0) / getZoom()
// customScan.setFocusPoint({x, y}) / resetFocus()
// customScan.stop() / rescan()
// 4. Release when done
await customScan.release();
バーコードの生成
import { scanCore, generateBarcode } from '@kit.ScanKit';
import { image } from '@kit.ImageKit';
const pixelMap: image.PixelMap = await generateBarcode.createBarcode('https://example.com', {
scanType: scanCore.ScanType.QR_CODE,
height: 400,
width: 400
});
// Use pixelMap directly in Image component: Image(this.pixelMap)
生成に対応する形式:QR Code、EAN-8、EAN-13、UPC-A、UPC-E、Codabar、Code 39/93/128、ITF-14、Data Matrix、PDF417、Aztec。
Account Kit — Huawei ID ログイン
module.json5でクライアント ID を設定する
{
"module": {
"name": "entry",
"type": "entry",
"metadata": [{
"name": "client_id",
"value": "YOUR_CLIENT_ID" // from AGC console
}]
}
}
ワンクリックログイン(法人開発者向け、ゲーム以外のアプリ)
タップ 1 回で電話番号と UnionID を取得します。手動署名と AGC 権限の承認が必要です。
import { authentication } from '@kit.AccountKit';
import { util } from '@kit.ArkTS';
// Step 1: Get anonymous phone number (for display on login page)
const authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
authRequest.scopes = ['quickLoginAnonymousPhone'];
authRequest.state = util.generateRandomUUID();
authRequest.forceAuthorization = false; // must be false for one-click login
const controller = new authentication.AuthenticationController();
const response = await controller.executeRequest(authRequest);
const anonymousPhone = response.data?.extraInfo?.quickLoginAnonymousPhone as string;
// Display anonymousPhone on login page, e.g. "188****1234"
// Step 2: Show LoginWithHuaweiIDButton — user taps to get Authorization Code
import { loginComponentManager, LoginWithHuaweiIDButton } from '@kit.AccountKit';
LoginWithHuaweiIDButton({
params: {
style: loginComponentManager.Style.BUTTON_RED,
borderRadius: 24,
loginType: loginComponentManager.LoginType.QUICK_LOGIN, // one-click login
supportDarkMode: true
},
controller: this.controller // LoginWithHuaweiIDButtonController
})
// In controller callback: response.authorizationCode → send to server
// Server calls /oauth2/v6/quickLogin/getPhoneNumber to get full phone + UnionID
難読化のホワイトリスト — プロパティ難読化が有効な場合は、obfuscation-rules.txtに追加します。
-keep-property-name
quickLoginAnonymousPhone
Huawei Account ログイン(すべての開発者向け)
LoginWithHuaweiIDButtonまたは API を介して UnionID/OpenID を取得します。法人開発者と個人開発者に対応しています。
// Using LoginWithHuaweiIDButton component (recommended)
LoginWithHuaweiIDButton({
params: {
style: loginComponentManager.Style.BUTTON_RED,
borderRadius: 24,
loginType: loginComponentManager.LoginType.ID, // standard login
supportDarkMode: true
},
controller: this.controller
})
// callback returns authorizationCode → server exchanges for UnionID/OpenID
// Using custom button with API
import { authentication } from '@kit.AccountKit';
const loginRequest = new authentication.HuaweiIDProvider().createLoginWithHuaweiIDRequest();
loginRequest.forceLogin = true; // true = show login page if not logged in
loginRequest.state = util.generateRandomUUID();
const controller = new authentication.AuthenticationController(getContext(this));
const response = await controller.executeRequest(loginRequest);
const authCode = response.data?.authorizationCode;
// Send authCode to server → server gets UnionID/OpenID via Access Token
サイレントログイン
ユーザー操作なしで、再インストールやデバイスの切り替えを行ったユーザーの UnionID を取得します。
const loginRequest = new authentication.HuaweiIDProvider().createLoginWithHuaweiIDRequest();
loginRequest.forceLogin = false; // false = silent, no UI if not logged in
loginRequest.state = util.generateRandomUUID();
const controller = new authentication.AuthenticationController();
const response = await controller.executeRequest(loginRequest);
const authCode = response.data?.authorizationCode;
// If error.code === 1001502001 → user not logged in, show other login methods
よくあるエラーコード
| コード | 意味 | 対処方法 |
|---|---|---|
1001502001 | Huawei Account にログインしていません | 他のログイン方法を表示します |
1001502005 | ネットワークエラー | 再試行するか、他の方法を表示します |
1001502012 | ユーザーがキャンセルしました | 対処は不要です |
1001500001 | 証明書のフィンガープリントの確認に失敗しました | 署名設定を確認します |
1001502014 | スコープまたは権限が不足しています | AGC 権限の承認を確認します |
1005300001 | ユーザーがプロトコルに同意しませんでした | 同意ダイアログを表示します |
主な概念
| ID の種類 | スコープ | 用途 |
|---|---|---|
| OpenID | アプリごとに一意 | 1 つのアプリ内でユーザーを識別する |
| UnionID | 開発者ごとに一意 | 同じ開発者の複数のアプリ間でユーザーを識別する |
| GroupUnionID | アカウントグループごとに一意 | 関連する開発者間でユーザーを識別する |
プラットフォームをまたいでユーザーデータの連続性を保つには、OpenID よりもUnionIDを優先してください。
アプリ継続(应用接续) — デバイス間の移行
デバイス間でタスクを引き継げるようにします。デバイス A からデバイス B に UIAbility の状態を移行します。
module.json5で継続を有効にする
{
"module": {
"abilities": [{
"name": "EntryAbility",
"continuable": true // enable cross-device migration
}]
}
}
移行元デバイス — 移行データを保存する
// In UIAbility
onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
// Save data to migrate (keep under 100KB, use distributed data object for larger data)
wantParam['currentPage'] = 'DetailPage';
wantParam['articleId'] = this.articleId;
// Check target app version compatibility
const targetVersion = wantParam['version'] as number;
if (targetVersion < 2) {
return AbilityConstant.OnContinueResult.MISMATCH;
}
return AbilityConstant.OnContinueResult.AGREE;
}
移行先デバイス — データを復元する
// Cold start
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
// Restore migrated data
this.articleId = want.parameters?.['articleId'] as number;
// Restore page stack
this.context.restoreWindowStage(this.storage);
}
}
// Hot start (single-instance)
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam) {
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
this.articleId = want.parameters?.['articleId'] as number;
this.context.restoreWindowStage(this.storage);
}
}
移行の動的な制御
// Disable migration on certain pages
this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE);
// Re-enable when on a migrateable page
this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE);
Ability 名が異なる場合のデバイス間移行
module.json5のcontinueTypeを使用して、デバイス間で異なる Ability を関連付けます。
// Device A
{ "name": "PhoneAbility", "continueType": ["myApp_main"] }
// Device B
{ "name": "TabletAbility", "continueType": ["myApp_main"] }
前提条件
- 両方のデバイスで同じ Huawei Account にログインしていること
- Wi-Fi と Bluetooth が有効になっていること(または「マルチデバイスコラボレーション拡張」が有効になっていること)
- 「設定 → マルチデバイスコラボレーション → 継続」が有効になっていること
- 両方のデバイスにアプリがインストールされていること
Push Kit — プッシュ通知
プッシュトークンの取得
UIAbility の onCreate() で呼び出します。トークンは、プッシュメッセージの送信先となるデバイスとアプリを識別します。
import { pushService } from '@kit.PushKit';
// In EntryAbility.onCreate()
pushService.getToken().then((token: string) => {
console.info('Push token:', token);
// Upload token to your app server for sending push messages
}).catch((err: BusinessError) => {
console.error('Failed to get push token:', err.code, err.message);
});
前提条件:まず AGC コンソールで Push Service を有効にしてください。有効になっていない場合、getToken() はエラー 1000900010 を返します。
トークンは、アプリの再インストール、端末の初期化、deleteToken() の呼び出し後に getToken() を再度呼び出すと変更されます。サーバー側のトークンを最新に保つため、アプリの起動ごとに必ず getToken() を呼び出してください。
プッシュメッセージの受信(module.json5)
トークンの更新とプッシュデータを受信するには、action.ohos.push.listener を設定した UIAbility を構成します。
"skills": [{ "actions": ["action.ohos.push.listener"] }]
Push Kit は、通知メッセージ、音声ブロードキャスト、カードの更新、バックグラウンドメッセージ、ライブビュー、アプリ内通話メッセージに対応しています。
AVSession Kit — メディア再生の制御(バックグラウンド音声の再生に必須)
重要:バックグラウンドで音声/動画を再生するすべてのアプリは、AVSession を作成する必要があります。作成しない場合、アプリがバックグラウンドに移行すると、システムによって音声が強制的に一時停止されます。
セッションの作成と有効化
import { avSession as AVSessionManager } from '@kit.AVSessionKit';
// Create session — type: 'audio' | 'video' | 'voice_call'
const session = await AVSessionManager.createAVSession(context, 'MyPlayer', 'audio');
// Set metadata (required — without it, playback controls won't appear)
await session.setAVMetadata({
assetId: 'song_001',
title: 'Song Title',
artist: 'Artist Name',
mediaImage: 'https://example.com/cover.jpg',
duration: 240000 // ms
});
// Set playback state
await session.setAVPlaybackState({
state: AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY,
position: { elapsedTime: 0, updateTime: Date.now() },
speed: 1.0
});
// Register control commands BEFORE activating
session.on('play', () => { /* resume playback */ });
session.on('pause', () => { /* pause playback */ });
session.on('playNext', () => { /* next track */ });
session.on('playPrevious', () => { /* previous track */ });
session.on('seek', (position: number) => { /* seek to position ms */ });
// Activate — must be called AFTER metadata + commands are set
await session.activate();
バックグラウンド再生の要件
メディアストリーム(MUSIC/MOVIE/AUDIOBOOK/GAME)の場合:
- AVSession を作成します(上記を参照)
- Background Tasks Kit で
AUDIO_PLAYBACKの長時間実行タスクを要求します - どちらも必須です。いずれかが欠けると、バックグラウンドで音声がミュートされます
未対応コマンド
アプリで対応していないコマンドの登録を解除するには、session.off() を使用します。システムの再生センターでは、対応するボタンがグレー表示になります。
session.off('playPrevious'); // no "previous" button
session.off('toggleFavorite'); // no favorite button
リソースへのアクセス — $r() と $rawfile()
リソースディレクトリの構造
resources/
├─ base/ # default resources (always matched)
│ ├─ element/ # string.json, color.json, float.json, etc.
│ ├─ media/ # images, audio, video
│ └─ profile/ # custom JSON config files
├─ zh_CN/element/ # Chinese locale override
├─ en_US/element/ # English locale override
├─ dark/element/ # dark mode override
├─ rawfile/ # raw files (not compiled, accessed by path)
└─ resfile/ # installed to sandbox, read-only access
修飾子の順序:MCC_MNC → language_script_region → orientation → device → colorMode → density。
リソースへのアクセス
// App resources: $r('app.type.name')
Text($r('app.string.hello_world'))
.fontSize($r('app.float.text_size_body'))
.fontColor($r('app.color.primary'))
Image($r('app.media.app_icon'))
// With format args: $r('app.string.greeting', 'Alice', 5)
// For string "Hello, %1$s! You have %2$d messages."
Text($r('app.string.greeting', 'Alice', 5))
// Plural: $r('app.plural.item_count', count, count)
Text($r('app.plural.item_count', 2, 2)) // "2 items"
// Raw files: $rawfile('path/relative/to/rawfile/')
Image($rawfile('images/banner.png'))
// System resources: $r('sys.type.name')
Text('Hello')
.fontColor($r('sys.color.ohos_id_color_emphasize'))
.fontSize($r('sys.float.ohos_id_text_size_headline1'))
// Cross-HSP module resources: $r('[moduleName].type.name')
Text($r('[library].string.shared_text'))
ResourceManager によるプログラムからのアクセス
const resMgr = getContext(this).resourceManager;
const str = resMgr.getStringByNameSync('hello_world');
const rawFd = resMgr.getRawFd('data.json'); // returns {fd, offset, length}
アプリケーションのファイルパス(Context プロパティ)
| Context プロパティ | パス | 用途 |
|---|---|---|
filesDir | base/files/ | アプリの永続データ(アプリの更新後も保持されます) |
cacheDir | base/cache/ | キャッシュ(空き容量が少ない場合、システムによって自動削除されることがあります) |
tempDir | base/temp/ | 一時ファイル(アプリの終了時に削除されます) |
databaseDir | database/ | データベースファイル(relationalStore など) |
preferencesDir | base/preferences/ | Preferences の KV ストア |
bundleCodeDir | bundle/ | インストール済み HAP リソース(読み取り専用) |
distributedFilesDir | distributedfiles/ | デバイス間で共有するファイル |
Payment Kit — Huawei Pay(実物商品およびサービス)
実物商品/サービス(ホテル、乗車、請求書など)のみが対象です。仮想商品には、代わりに IAP Kit を使用してください。
import { paymentService } from '@kit.PaymentKit';
import { common } from '@kit.AbilityKit';
// orderStr is built by your server after calling Huawei Pay pre-order API
// Contains: app_id, merc_no, prepay_id, timestamp, noncestr, sign
const orderStr = '{"app_id":"...","merc_no":"...","prepay_id":"...","timestamp":"...","noncestr":"...","sign":"..."}';
const context = getContext(this) as common.UIAbilityContext;
paymentService.requestPayment(context, orderStr)
.then(() => { console.info('Payment succeeded'); })
.catch((err: BusinessError) => { console.error('Payment failed:', err.code, err.message); });
サーバーのフロー:サーバーが /api/v2/aggr/preorder/create/app を呼び出して prepayId を取得し、署名付きの orderStr を作成してから、クライアントに返します。
対応端末:Phone、Tablet、PC/2in1。中国本土のみで利用できます。
Core Vision Kit — OCR、顔検出、被写体のセグメンテーション
@kit.CoreVisionKit によるオンデバイス AI 機能です。中国本土のみで利用でき、シミュレーターには対応していません。
テキスト認識(OCR)
import { textRecognition } from '@kit.CoreVisionKit';
// Initialize once (e.g., in aboutToAppear)
await textRecognition.init();
// Recognize text from PixelMap
const visionInfo: textRecognition.VisionInfo = { pixelMap: myPixelMap };
const result = await textRecognition.recognizeText(visionInfo, {
isDirectionDetectionSupported: false
});
console.info('OCR result:', result.value); // full recognized text string
// Release when done (e.g., in aboutToDisappear)
await textRecognition.release();
対応言語:中国語(簡体字/繁体字)、英語、日本語、韓国語。入力形式:JPEG/PNG。720p 以上を推奨します。
顔検出
import { faceDetector } from '@kit.CoreVisionKit';
await faceDetector.init();
const faces: faceDetector.Face[] = await faceDetector.detect({ pixelMap: myPixelMap });
// Each face: faceRectangle, landmark positions, euler angles, confidence
await faceDetector.release();
被写体のセグメンテーション
import { subjectSegmentation } from '@kit.CoreVisionKit';
await subjectSegmentation.init();
const result = await subjectSegmentation.doSegmentation(
{ pixelMap: myPixelMap },
{ maxCount: 5, enableSubjectDetails: true, enableSubjectForegroundImage: true }
);
// result.fullSubject.foregroundImage — PixelMap with background removed
// result.subjectCount, result.subjectDetails[i].subjectRectangle
await subjectSegmentation.release();
fileIo — アプリケーションのファイル読み書き
@kit.CoreFileKit による基本的なファイル操作です。すべてのパスには、Context プロパティ(filesDir、cacheDir など)の値を使用してください。
import { fileIo as fs } from '@kit.CoreFileKit';
const context = getContext(this);
// Write a file
const filePath = context.filesDir + '/data.json';
const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
fs.writeSync(file.fd, JSON.stringify({ key: 'value' }));
fs.closeSync(file);
// Read a file
const readFile = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
const buf = new ArrayBuffer(4096);
const readLen = fs.readSync(readFile.fd, buf);
const content = String.fromCharCode(...new Uint8Array(buf.slice(0, readLen)));
fs.closeSync(readFile);
// Check existence
const exists = fs.accessSync(filePath);
// List directory
const entries = fs.listFileSync(context.filesDir);
// Copy file
fs.copyFileSync(filePath, context.cacheDir + '/data_backup.json');
// Delete file
fs.unlinkSync(filePath);
// Stat file (size, mtime)
const stat = fs.statSync(filePath);
console.info(`size: ${stat.size}, mtime: ${stat.mtime}`);
Picker URI からのファイルの読み込み
// After picker returns a URI (temporary read-only permission)
const file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
const buf = new ArrayBuffer(4096);
const len = fs.readSync(file.fd, buf);
fs.closeSync(file);
AVPlayer — 統合型の音声/動画再生
@kit.MediaKit の AVPlayer は mp4/mp3/mkv/mpeg-ts などを処理します。ソースを指定するだけで、手動でデコードする必要はありません。
import { media } from '@kit.MediaKit';
// Create player
const avPlayer = await media.createAVPlayer();
// Set callbacks
avPlayer.on('stateChange', (state: string) => {
switch (state) {
case 'initialized': // source set, prepare now
avPlayer.prepare();
break;
case 'prepared': // ready to play
avPlayer.play();
break;
case 'completed': // playback finished
avPlayer.release();
break;
}
});
avPlayer.on('error', (err) => {
console.error('AVPlayer error:', err.message);
avPlayer.release();
});
// Set source — local file (fd)
const file = fs.openSync(context.filesDir + '/video.mp4', fs.OpenMode.READ_ONLY);
avPlayer.fdSrc = { fd: file.fd, offset: 0, length: fs.statSync(file.fd).size };
// Or network URL
avPlayer.url = 'https://example.com/audio.mp3';
ビデオサーフェス(XComponent)を使用した AVPlayer
avPlayer.on('stateChange', (state: string) => {
if (state === 'initialized') {
avPlayer.surfaceId = xComponentSurfaceId; // from XComponent.onLoad
avPlayer.prepare();
} else if (state === 'prepared') {
avPlayer.play();
}
});
avPlayer.url = 'https://example.com/video.mp4';
AVPlayer の操作
avPlayer.pause();
avPlayer.play();
avPlayer.seek(30000); // seek to 30s (ms)
avPlayer.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_2_00_X);
avPlayer.setVolume(0.5); // 0.0 ~ 1.0
avPlayer.stop(); // stop → can prepare() again
avPlayer.release(); // release all resources
AVRecorder — 音声/動画の録画
import { media } from '@kit.MediaKit';
const recorder = await media.createAVRecorder();
const config: media.AVRecorderConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
profile: {
audioBitrate: 48000,
audioChannels: 2,
audioCodec: media.CodecMimeType.AUDIO_AAC,
audioSampleRate: 48000,
fileFormat: media.ContainerFormatType.CFT_MPEG_4,
videoBitrate: 2000000,
videoCodec: media.CodecMimeType.VIDEO_AVC,
videoFrameWidth: 1920,
videoFrameHeight: 1080,
videoFrameRate: 30
},
url: `fd://${file.fd}`, // file descriptor for output
rotation: 0
};
await recorder.prepare(config);
// For video: const surfaceId = await recorder.getInputSurface();
await recorder.start();
// ... recording ...
await recorder.stop();
await recorder.release();
バックグラウンド再生では、AVSession の作成と AUDIO_PLAYBACK の長時間実行タスクの要求が必要です(AVSession Kit のセクションを参照)。
Image Kit — デコード、変換、エンコード
画像を PixelMap にデコード
import { image } from '@kit.ImageKit';
import { fileIo as fs } from '@kit.CoreFileKit';
// From file path
const imageSource = image.createImageSource(context.filesDir + '/photo.jpg');
const pixelMap = await imageSource.createPixelMap({
editable: true,
desiredPixelFormat: image.PixelMapFormat.RGBA_8888
});
// From file descriptor
const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
const imageSource2 = image.createImageSource(file.fd);
// From ArrayBuffer (e.g., from network response)
const imageSource3 = image.createImageSource(arrayBuffer);
// From rawfile
const rawFd = context.resourceManager.getRawFd('image.png');
const imageSource4 = image.createImageSource(rawFd);
// Get image info
const info = await pixelMap.getImageInfo();
console.info(`${info.size.width} x ${info.size.height}`);
PixelMap の変換
pixelMap.crop({ x: 0, y: 0, size: { width: 400, height: 400 } }); // crop
pixelMap.scale(0.5, 0.5); // scale to 50%
pixelMap.rotate(90); // rotate 90° clockwise
pixelMap.flip(true, false); // horizontal flip
pixelMap.flip(false, true); // vertical flip
pixelMap.opacity(0.5); // set 50% opacity
pixelMap.translate(100, 100); // offset by 100px
PixelMap をファイルにエンコード
import { image } from '@kit.ImageKit';
const packer = image.createImagePacker();
const packOpts: image.PackingOption = { format: 'image/jpeg', quality: 90 };
const data: ArrayBuffer = await packer.packing(pixelMap, packOpts);
// Write data to file via fs.writeSync
packer.release();
リソースの解放
pixelMap.release();
imageSource.release();
App Linking — ディープリンクとアプリ間のナビゲーション
module.json5 でディープリンクを設定
{
"module": {
"abilities": [{
"name": "EntryAbility",
"skills": [{
"entities": ["entity.system.home", "entity.system.browsable"],
"actions": ["ohos.want.action.home", "ohos.want.action.viewData"],
"uris": [{ "scheme": "https", "host": "example.com", "path": "/detail" }],
"domainVerify": true // enable App Linking verification
}]
}]
}
}
受信リンクを処理 — コールドスタート(onCreate)
import { url } from '@kit.ArkTS';
export default class EntryAbility extends UIAbility {
private targetPage: string = '';
private linkParams: Record<string, string> = {};
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.parseUri(want);
}
private parseUri(want: Want): void {
if (want?.uri) {
const urlObj = url.URL.parseURL(want.uri);
this.linkParams = Object.fromEntries(urlObj.params.entries());
this.targetPage = urlObj.pathname; // e.g. "/detail"
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
const page = this.targetPage === '/detail' ? 'pages/Detail' : 'pages/Index';
if (this.linkParams['id']) {
AppStorage.setOrCreate('linkId', this.linkParams['id']);
}
windowStage.loadContent(page);
}
}
リンクを処理 — アプリがすでに起動中(onNewWant)
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.parseUri(want);
if (this.linkParams['id']) {
AppStorage.setOrCreate('linkId', this.linkParams['id']);
AppStorage.setOrCreate('newWantFlag', true); // notify UI to navigate
}
}
ページは@StorageLinkと@Watchを使用してnewWantFlagを監視し、それに応じて遷移します。
Share Kit — アプリ間でコンテンツを共有
import { systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor } from '@kit.ArkData';
// Share a hyperlink
const shareData = new systemShare.SharedData({
utd: uniformTypeDescriptor.UniformDataType.HYPERLINK,
content: 'https://example.com/article/123',
title: 'Article Title',
description: 'Article preview text',
});
const controller = new systemShare.ShareController(shareData);
controller.show(this.context, {
previewMode: systemShare.SharePreviewMode.DEFAULT,
selectionMode: systemShare.SelectionMode.SINGLE,
});
対応するUTDタイプ:HYPERLINK、PLAIN_TEXT、HTML、IMAGE(ファイルのuriを渡します)、FILE。
カスタムダイアログ — openCustomDialogとopenBindSheet
openCustomDialog(汎用モーダル/非モーダル)
import { ComponentContent } from '@kit.ArkUI';
// 1. Define dialog content via @Builder
@Builder
function dialogContentBuilder(params: { message: string; close: () => void }) {
Column({ space: 16 }) {
Text(params.message).fontSize(16)
Button('OK').onClick(() => params.close())
}
.padding(24)
.backgroundColor(Color.White)
.borderRadius(16)
}
// 2. Open dialog
const uiContext = this.getUIContext();
const contentNode = new ComponentContent(uiContext, wrapBuilder(dialogContentBuilder), {
message: 'Hello',
close: () => uiContext.getPromptAction().closeCustomDialog(contentNode),
});
uiContext.getPromptAction().openCustomDialog(contentNode, {
alignment: DialogAlignment.Center,
isModal: true,
autoCancel: true, // tap outside to close
});
openBindSheet(画面下部のハーフモーダルシート)
const uiContext = this.getUIContext();
const sheetNode = new ComponentContent(uiContext, wrapBuilder(sheetBuilder), params);
uiContext.openBindSheet(sheetNode, {
title: { title: 'Select Option' },
height: SheetSize.MEDIUM,
preferType: SheetType.BOTTOM,
detents: [SheetSize.MEDIUM, SheetSize.LARGE, 200], // draggable heights
backgroundColor: '#F1F3F5',
}, targetComponentId);
bindContentCover(全画面モーダルオーバーレイ)
@State isPresented: boolean = false;
@Builder
fullScreenContent() {
Column() {
Text('Full Screen Modal').fontSize(20)
Button('Close').onClick(() => { this.isPresented = false; })
}.width('100%').height('100%').backgroundColor(Color.White)
}
// Trigger:
Button('Show').onClick(() => { this.isPresented = true; })
.bindContentCover($$this.isPresented, this.fullScreenContent(), {
modalTransition: ModalTransition.DEFAULT, // .NONE, .ALPHA
})
注:非推奨の
CustomDialogや@ohos.promptActionは使用しないでください。代わりにUIContext.getPromptAction().openCustomDialog()、UIContext.openBindSheet()、.bindContentCover()を使用してください。
キーボードレイアウトへの対応(ソフトキーボードへの対応)
キーボード回避モードを設定(UIAbility内)
import { KeyboardAvoidMode } from '@kit.ArkUI';
// OFFSET = page lifts up (default); RESIZE = page compresses; NONE = keyboard overlaps
windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE);
キーボードに合わせてコンポーネントが移動しないようにする
Row() { /* title bar — should stay fixed */ }
.expandSafeArea([SafeAreaType.KEYBOARD])
.zIndex(1)
キーボードの高さを監視
import { window } from '@kit.ArkUI';
window.getLastWindow(this.getUIContext().getHostContext()).then(win => {
win.on('keyboardHeightChange', (height: number) => {
this.keyboardHeight = this.getUIContext().px2vp(height);
});
});
フォーカス制御
TextInput().defaultFocus(true) // auto-focus on page load
this.getUIContext().getFocusController().requestFocus('inputId'); // programmatic focus
this.getUIContext().getFocusController().clearFocus(); // dismiss keyboard
ダークモードへの対応(ダークモードへの対応)
リソース修飾子を使用する方法(推奨)
ライトモードの色や画像をresources/base/に、ダークモード用のバリエーション(同じファイル名)をresources/dark/に配置します。
resources/base/element/color.json → { "color": [{ "name": "bg_color", "value": "#FFFFFF" }] }
resources/dark/element/color.json → { "color": [{ "name": "bg_color", "value": "#1A1A1A" }] }
resources/base/media/icon.png → light icon
resources/dark/media/icon.png → dark icon
使用例:$r('app.color.bg_color')/$r('app.media.icon') — システムテーマに合わせて自動的に切り替わります。
カラーモードの変更を検出して対応
// In EntryAbility — store current mode
onCreate(): void {
AppStorage.setOrCreate('currentColorMode', this.context.config.colorMode);
}
onConfigurationUpdate(newConfig: Configuration): void {
AppStorage.setOrCreate('currentColorMode', newConfig.colorMode);
}
// In component — watch for changes
@StorageProp('currentColorMode') @Watch('onColorModeChange')
currentColorMode: number = ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET;
onColorModeChange(): void {
const isDark = this.currentColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK;
// update status bar, custom logic, etc.
}
モードをプログラムで切り替え
this.getUIContext().getHostContext()?.getApplicationContext()
.setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_DARK); // or COLOR_MODE_LIGHT / COLOR_MODE_NOT_SET
バックグラウンドでのアップロードとダウンロード(request.agent)
import { request } from '@kit.BasicServicesKit';
バックグラウンドアップロード(一時停止/再開に対応)
const config: request.agent.Config = {
action: request.agent.Action.UPLOAD,
url: 'https://example.com/upload',
mode: request.agent.Mode.BACKGROUND,
method: 'POST',
data: formItems, // Array of FormItem
};
const task = await request.agent.create(context, config);
task.on('progress', (progress) => { /* track */ });
task.on('completed', (progress) => { /* done */ });
await task.start();
await task.pause(); // pause
await task.resume(); // resume with breakpoint
バックグラウンドダウンロード(中断位置から自動再開)
const config: request.agent.Config = {
action: request.agent.Action.DOWNLOAD,
url: 'https://example.com/file.zip',
mode: request.agent.Mode.BACKGROUND,
saveas: `./downloads/file.zip`,
overwrite: true,
gauge: true,
};
const task = await request.agent.create(context, config);
task.on('progress', (progress) => { /* track */ });
await task.start();
デスクトップショートカット
1. resources/base/profile/shortcuts_config.jsonを作成
{
"shortcuts": [
{
"shortcutId": "id_scan",
"label": "$string:shortcut_scan",
"icon": "$media:ic_scan",
"wants": [{
"bundleName": "com.example.myapp",
"moduleName": "entry",
"abilityName": "EntryAbility",
"parameters": { "shortcutKey": "ScanPage" }
}]
}
]
}
2. module.json5に登録
"abilities": [{
"name": "EntryAbility",
"metadata": [{
"name": "ohos.ability.shortcuts",
"resource": "$profile:shortcuts_config"
}]
}]
3. ショートカットのパラメーターに応じて遷移先を設定
// In EntryAbility — onCreate / onNewWant
const shortcutKey = want?.parameters?.shortcutKey as string;
if (shortcutKey === 'ScanPage') {
AppStorage.setOrCreate('targetPage', 'pages/Scan');
}
ページは@StorageProp('targetPage')を読み取り、それに応じて遷移します。
モジュール間でのリソースアクセス
HARリソースにアクセス(ローカルリソースと同様)
Text($r('app.string.string_in_har'))
Image($r('app.media.image_in_har'))
// Better performance with .id for resourceManager:
this.context.resourceManager.getStringSync($r('app.string.string_in_har').id);
HSPリソースにアクセス(モジュール名をプレフィックスに指定)
Text($r('[hsp1].string.string_in_hsp'))
Image($r('[hsp1].media.image_in_hsp'))
または、createModuleContextを使用します。
import { common } from '@kit.AbilityKit';
const hspContext = await common.application.createModuleContext(this.context, 'hsp1');
const str = hspContext.resourceManager.getStringByNameSync('string_in_hsp');
カスタムフォント
// 1. Register font (in EntryAbility onWindowStageCreate or component aboutToAppear)
const uiContext = windowStage.getMainWindowSync().getUIContext();
uiContext.getFont().registerFont({
familyName: 'MyCustomFont',
familySrc: $rawfile('MyCustomFont.ttf'),
});
// 2. Use in component
Text('Hello').fontFamily('MyCustomFont')
フォントサイズをシステム設定に合わせるか無視するかは、profile/configuration.jsonで設定します。
{ "configuration": { "fontSizeScale": "followSystem", "fontSizeMaxScale": "2" } }
app.json5で参照します:"configuration": "$profile:configuration"。
画面の向き(縦向き/横向きの切り替え)
import { window } from '@kit.ArkUI';
// Get window instance
const win = await window.getLastWindow(this.context);
// Set orientation
win.setPreferredOrientation(window.Orientation.USER_ROTATION_LANDSCAPE); // enter landscape
win.setPreferredOrientation(window.Orientation.USER_ROTATION_PORTRAIT); // back to portrait
win.setPreferredOrientation(window.Orientation.AUTO_ROTATION); // follow sensor
// Monitor window size for layout adaptation
win.on('windowSizeChange', (size) => {
const orientation = display.getDefaultDisplaySync().orientation;
this.isLandscape = (orientation === display.Orientation.LANDSCAPE ||
orientation === display.Orientation.LANDSCAPE_INVERTED);
});
module.json5でも設定できます:"abilities": [{ "orientation": "portrait" }]。
オプション:portrait、landscape、auto_rotation、auto_rotation_landscape、follow_desktop。
クリップボード — pasteboardの読み書き
import { pasteboard } from '@kit.BasicServicesKit';
// Write text to clipboard
const pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, 'Hello World');
const board = pasteboard.getSystemPasteboard();
await board.setData(pasteData);
// Read from clipboard
const data = await board.getData();
if (data.hasType(pasteboard.MIMETYPE_TEXT_PLAIN)) {
const text = data.getPrimaryText();
}
対応するMIMEタイプ:MIMETYPE_TEXT_PLAIN、MIMETYPE_TEXT_HTML、MIMETYPE_TEXT_URI、MIMETYPE_PIXELMAP。
ジェスチャーの競合解決(ジェスチャーの競合処理)
hitTestBehavior — タッチイベントへの応答を制御
Stack() {
BottomComponent().hitTestBehavior(HitTestMode.None) // skip self, pass to sibling
TopComponent().hitTestBehavior(HitTestMode.Transparent) // self responds AND passes to sibling
}
| モード | 動作 |
|---|---|
Default | 自身が応答し、兄弟要素へのイベント伝播をブロックします |
Transparent | 自身が応答し、兄弟要素へのイベント伝播をブロックしません |
None | 自身をスキップし、兄弟要素にイベントを渡します |
Block | 自身のみが応答し、すべてのイベント伝播を停止します |
ジェスチャーのバインド優先度
// Parent takes priority over child for same gesture type
ParentComponent()
.priorityGesture(TapGesture().onAction(() => { /* parent handles */ }))
// Both parent and child respond simultaneously
ParentComponent()
.parallelGesture(PanGesture().onAction(() => { /* parent also handles */ }))
// Block child gestures entirely
ParentComponent()
.gesture(TapGesture(), GestureMask.IgnoreInternal)
GestureGroupのモード
// Sequential: all must succeed in order
GestureGroup(GestureMode.Sequence, LongPressGesture(), PanGesture())
// Parallel: all run simultaneously
GestureGroup(GestureMode.Parallel, PinchGesture(), RotationGesture())
// Exclusive: first to succeed wins
GestureGroup(GestureMode.Exclusive, TapGesture(), SwipeGesture())
注:システムジェスチャー(onClick、onTouch、drag、bindMenu)は、同じタイプのカスタムジェスチャーより常に優先されます。
イマーシブウィンドウ(没入表示/全画面表示/セーフエリア回避)
コンポーネントをステータスバーとナビゲーションバーの領域まで拡張
// Method 1 — expandSafeArea (simplest, component-level)
Column() { /* content */ }
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
// Method 2 — window-level fullscreen (affects all pages)
const win = windowStage.getMainWindowSync();
win.setWindowLayoutFullScreen(true);
手動で余白を設定するためにセーフエリアの寸法を取得
import { window } from '@kit.ArkUI';
const win = await window.getLastWindow(context);
const systemAvoid = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
const topHeight = px2vp(systemAvoid.topRect.height); // status bar height
const bottomHeight = px2vp(systemAvoid.bottomRect.height); // navigation bar height
// Listen for changes (e.g. split screen, fold/unfold)
win.on('avoidAreaChange', (options: window.AvoidAreaOptions) => {
if (options.type === window.AvoidAreaType.TYPE_SYSTEM) {
// update top/bottom padding
}
});
システムバーを非表示/表示
win.setSpecificSystemBarEnabled('status', false); // hide status bar
win.setSpecificSystemBarEnabled('navigationIndicator', false); // hide nav indicator
ステータスバーのテキスト色(ライト/ダークコンテンツ)
win.setWindowSystemBarProperties({
statusBarContentColor: '#FFFFFF', // white text for dark backgrounds
});
リストの一般的な操作
スワイプして削除(左スワイプアクション)
ListItem() { /* content */ }
.swipeAction({
end: {
builder: () => {
Button('Delete').backgroundColor(Color.Red)
.onClick(() => {
animateTo({ duration: 300 }, () => {
this.dataList.splice(index, 1);
});
})
},
actionAreaDistance: 56,
},
edgeEffect: SwipeEdgeEffect.Spring,
})
ドラッグして並べ替え
List() {
ForEach(this.dataList, (item: string, index: number) => {
ListItem() { Text(item) }
})
}
.onItemDragStart((event: ItemDragInfo, itemIndex: number) => {
this.dragIndex = itemIndex;
})
.onItemDragMove((event: ItemDragInfo, itemIndex: number, insertIndex: number) => {
animateTo({ duration: 200 }, () => {
const tmp = this.dataList.splice(this.dragIndex, 1);
this.dataList.splice(insertIndex, 0, tmp[0]);
this.dragIndex = insertIndex;
});
})
下に引いて更新
Refresh({ refreshing: $$this.isRefreshing }) {
List() { /* items */ }
}
.onRefreshing(() => {
// fetch new data...
this.isRefreshing = false;
})
リストの最下部までスクロール(チャット形式)
const scroller = new Scroller();
List({ scroller }) { /* items */ }
// After new message:
scroller.scrollEdge(Edge.Bottom);
// Or scroll to specific index:
scroller.scrollToIndex(this.messages.length - 1);
データ挿入時にスクロール位置を維持(LazyForEach)
List() { LazyForEach(this.dataSource, ...) }
.maintainVisibleContentPosition(true) // new items at top don't shift visible content
ListItemGroupとスティッキーヘッダー(グループ化されたリスト)
@Builder sectionHeader(title: string) {
Text(title).fontSize(14).fontColor('#99000000')
.width('100%').padding({ left: 16, top: 8, bottom: 8 })
.backgroundColor('#F1F3F5')
}
List() {
ForEach(this.groups, (group: GroupData) => {
ListItemGroup({ header: this.sectionHeader(group.title) }) {
ForEach(group.items, (item: ItemData) => {
ListItem() { Text(item.name) }
})
}
})
}
.sticky(StickyStyle.Header) // header sticks to top when scrolling
onReachEnd — 追加データを読み込む
List() { LazyForEach(this.dataSource, ...) }
.onReachEnd(() => {
if (!this.isLoading) {
this.isLoading = true;
this.loadNextPage().then(() => { this.isLoading = false; });
}
})