一个工程面向手机与二合一:HarmonyOS 6 部署配置和本地手记

1. 前言

随身手记最基本的承诺,是刚写下的话下次还在。这个收尾练习就用一段本地手记,把页面布局与部署配置接到一次真正的读写里:编辑、保存、换窗口大小,再结束进程重新打开,看看刚才的文字是否回来。

这篇把工程配置与运行表现分别确认。示例做成一页随身手记,核心操作是编辑、保存和重启后读取。宽窗口增加设备信息区,紧凑窗口专心编辑,让同一份业务代码在不同空间里保持清楚的使用方式。

2. 手机与二合一部署声明

先检查包是否声明了目标设备。模块的 deviceTypes 使用 phone 和 2in1,分别对应手机与二合一设备类别。配置中需要的是平台定义的值;产品名称、显示屏尺寸和窗口宽度都不能替代这两个条目。部署声明明确之后,再检查同一份手记页怎样布局、怎样保存。

下面是 entry/src/main/module.json5 的完整配置。页面入口、资源引用和模块信息沿用 Empty Ability 的基本结构,设备类型明确列在同一个模块中。

json 复制代码
{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone",
      "2in1"
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:layered_image",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:startIcon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "ohos.want.action.home"
            ]
          }
        ]
      }
    ],
    "extensionAbilities": [
      {
        "name": "EntryBackupAbility",
        "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
        "type": "backup",
        "exported": false,
        "metadata": [
          {
            "name": "ohos.extension.backup",
            "resource": "$profile:backup_config"
          }
        ],
      }
    ]
  }
}

工程使用 HarmonyOS 6.1.1、API 24。能够安装还取决于目标设备系统版本、模块类型和包的其他要求,deviceTypes 表达的是支持范围,编译与实际安装仍然需要分别验证。

纯 ArkTS 页面也不等于可以忽略部署差异。以后加入原生库、特殊系统能力或设备专属交互,应继续确认对应设备与架构的支持情况。把安装声明、编译、运行和业务验证逐项对齐,比仅看到一个欢迎页面更可靠。

配置文件最好与实际包一起检查。入口页面、模块名、资源引用和设备类型共同组成可部署的工程,单独复制一项 deviceTypes 并不会自动替其他条件兜底。当前已经用二合一模拟器完成读写和宽窄窗口验证,手机端继续使用同一页面实现,但安装与输入体验仍应在对应环境另行核对。

3. Preferences写入与落盘

只显示设备名称,比较难判断页面能不能正常使用。因此,这里保留一项实际业务:在 TextArea 中编辑一段文字,点击保存后写入当前设备的 Preferences,重新启动应用再读取。

读取使用固定的首选项名称 journal 与键 note。第一次打开时使用默认手记,已有内容时显示本机保存的值。获取存储实例是异步过程,因此保存方法先检查实例是否就绪,再执行写入,不提前显示"保存成功"。

写入时先 put,再等待 flush 完成,随后更新反馈;出现异常则显示保存失败。put 对应首选项中的值更新,flush 将待保存的数据写入持久化文件,这两个步骤在示例里明确接在一起。

每台设备保存自己的手记。以后把同一个应用安装到另一台设备,它会使用那台设备自己的应用数据;如果产品需要跨设备同步,还应继续设计账号、同步服务或其他数据协同机制。

继续接入真实业务时,还需要检查空内容、较长文字、读取失败与存储失败。本例已给出未就绪和异常反馈,但 Preferences 仍更适合轻量数据;如果手记发展成大量记录、复杂搜索与分类,就应该重新评估数据模型与存储方式。

3.1 手记编辑区与窗口代码

信息区显示 deviceInfo.deviceType,帮助确认当前运行环境;是否显示这一块区域,则按页面内容宽度是否达到 780 vp 判断。二合一设备上的窄窗口,也会收起辅助信息,将空间留给编辑器。

这里没有按"设备属于电脑"就始终固定两栏。真实用户可以把应用摆在屏幕一角,也可以同时打开多个窗口。只看设备类别而忽略当前空间,会让一个本来能正常运行的应用,仍然在小窗口里显得拥挤。

宽屏信息区使用固定比例,编辑区分配剩余空间。文字编辑状态一直保存在同一个 note 中,显示或收起辅助区域不会重新初始化它。窗口尺寸变化与本地存储也分开处理,不会每拖一下边缘就写一次首选项文件。

尺寸按钮用于二合一自由窗口中的重复观察。手机上的窗口形态由系统管理,窗口操作可能受到限制,方法保留相应失败反馈。手记的读取、编辑和保存不依赖这些演示按钮。

尺寸回调只安排信息区是否出现,保存方法只处理当前文本。两者分开后,拖动窗口不会制造额外写盘,恢复宽窗也不用重新加载默认文字。手记的生命周期由数据读写管理,版面只改变它在眼前如何展开,这也是这个小业务适合用于检查多端页面的原因。

pages/Index.ets:

typescript 复制代码
import { LengthMetrics } from '@kit.ArkUI';
import { deviceInfo } from '@kit.BasicServicesKit';
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
import { resizeWindow } from '../common/WindowTools';

@Entry
@Component
struct Index {
  @State private viewportWidth: number = 0;
  @State private note: string = '今天也有值得记住的小事。';
  @State private status: string = '正在读取本机手记';
  private store?: preferences.Preferences;
  async aboutToAppear(): Promise<void> {
    try {
      const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
      this.store = await preferences.getPreferences(context, { name: 'journal' });
      this.note = await this.store.get('note', this.note) as string;
      this.status = '已读取本机手记';
    } catch (error) { this.status = '读取失败,请稍后重试'; }
  }
  private async save(): Promise<void> {
    if (!this.store) { this.status = '存储尚未就绪';return; }
    try {
      this.getUIContext().getFocusController().clearFocus();
      await this.store.put('note', this.note);
      await this.store.flush();
      this.status = '已保存在当前设备';
    } catch (error) { this.status = '保存失败,请重试'; }
  }
  private async resize(w: number): Promise<void> {
    this.status = await resizeWindow(this.getUIContext().getHostContext() as common.UIAbilityContext, w, this.getUIContext().vp2px(1));
  }
  @Builder
  information() {
    Column({ space: 20 }) {
      Text('一份代码,随处记下').fontSize(28).fontWeight(FontWeight.Bold).fontColor('#F7F4E7').width('100%');
      Text('支持 phone / 2in1').fontSize(17).fontColor('#D7E5CE').width('100%');
      Text(`当前设备类型:${deviceInfo.deviceType}`).fontSize(15).fontColor('#D7E5CE').id('deviceType').width('100%');
      Text(`内容宽度:${Math.round(this.viewportWidth)} vp`).fontSize(15).fontColor('#D7E5CE').id('viewport').width('100%');
      Text('设备类型决定安装范围,窗口宽度决定当下排版。').fontSize(17).lineHeight(28).fontColor('#F7F4E7').width('100%');
    }.padding(28).width('100%').height('100%').backgroundColor('#587456').borderRadius(24);
  }
  @Builder
  editor() {
    Column({ space: 20 }) {
      Text('随身手记').fontSize(30).fontWeight(FontWeight.Bold).fontColor('#3E4A38').width('100%');
      TextArea({ text: this.note, placeholder: '记下一件小事' }).id('note').layoutWeight(1).width('100%')
        .fontSize(20).backgroundColor('#FCFAF1').onChange((value: string) => { this.note = value; });
      Flex({ wrap: FlexWrap.Wrap, space: { main: LengthMetrics.vp(8), cross: LengthMetrics.vp(8) } }) {
        Button('保存手记').id('save').backgroundColor('#587456').onClick(() => this.save());
        Button('紧凑窗口').id('narrow').onClick(() => this.resize(450));
        Button('展开窗口').id('wide').onClick(() => this.resize(1080));
      }.width('100%');
      Text(this.status).fontSize(13).fontColor('#7F8D70').id('status').width('100%');
    }.padding(26).height('100%').width('100%').backgroundColor('#F3F1DF').borderRadius(24);
  }
  build() {
    Row({ space: 18 }) {
      if (this.viewportWidth >= 780) { Column() { this.information(); }.width('36%'); }
      Column() { this.editor(); }.layoutWeight(1);
    }.padding(22).width('100%').height('100%').backgroundColor('#E3E7D5')
      .onAreaChange((_old: Area, area: Area) => { this.viewportWidth = Number(area.width); });
  }
}

common/WindowTools.ets:

typescript 复制代码
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';

export async function resizeWindow(context: common.UIAbilityContext, widthVp: number,
  density: number): Promise<string> {
  try {
    const mainWindow = await window.getLastWindow(context);
    const rect = mainWindow.getWindowProperties().windowRect;
    await mainWindow.resize(Math.round(widthVp * density), rect.height);
    return '窗口已调整';
  } catch (error) {
    return '请先将窗口还原为自由窗口,再调整宽度';
  }
}

4. 进程重启后的手记

默认手记是"今天也有值得记住的小事"。演示在这段文字后追加"今天完成了一次窗口适配练习",保存并重启后,追加的内容仍在。读取结果可以同最初的默认文字区分,便于检查本地持久化是否真正完成。

重启恢复比单纯点一次保存更有说明力。按钮文字改变,只能说明页面状态更新了;进程结束后还能读到刚写入的内容,才覆盖到这条手记的本地持久化路径。演示中的文字来自实际编辑,没有在应用重启时重新硬编码进默认值。

检查项 本例观察结果
模块支持范围 phone、2in1
编译环境 HarmonyOS 6.1.1,API 24
本次实际运行 二合一模拟器
宽窄布局 辅助信息区随宽度显示或收起
本地数据 编辑保存后,重启能够读取

动图里的紧凑界面仍然属于二合一自由窗口,展示同一应用在较小区域中的排版。手机端的支持范围由模块配置声明,安装与输入体验可以在相应手机模拟器中继续核对。页面依据可用区域调整布局,数据读写则沿用同一份业务实现。

这几项结果不宜互相代替。窄窗口里能编辑,说明当前布局可以承载输入;重启后还能读到追加文字,说明本地保存路径走通;模块声明两类设备,则说明工程写明了支持范围。把每一项对应到具体证据,后续换设备或更换存储方案时,才知道要重新验证哪一部分。

5. 总结

写下的话能保存,重新打开仍能读到,窗口收窄后也能接着编辑------这就是本例实际跑通的范围。部署声明、可用空间和本地数据分别有自己的依据。以后换成收藏或设置页,也可以挑一条真实操作沿着这三个层次走完,而不只停在欢迎页面上。

相关推荐
行者-全栈开发11 小时前
【鸿蒙心迹】深色模式适配全链路——从配置到自定义(HarmonyOS 7.x)
华为·harmonyos
xx z19 小时前
哔哩哔哩第三方客户端 PiliPlus 实测:23.5MB 轻量版,手机平板电脑通用
智能手机·电脑
行者-全栈开发21 小时前
【鸿蒙心迹】国际化 i18n 与多语言资源管理实战(HarmonyOS 7.x)
ui·华为·重构·国际化·harmonyos·多语言
OH_TPC1 天前
HarmonyOS APP开发---"云客服"远程视频客服App,需要用到这个库
harmonyos
逆回2 天前
给鸿蒙手机做一只 3D 宠物:五个把我卡住的问题
harmonyos
钉先生贾2 天前
性能、体积与稳定性:怎样写出可信的适配结论
华为·harmonyos
笑 总不会犯错2 天前
摆脱多端数据撕裂,鸿蒙分布式数据库同步与冲突合并
harmonyos
用户117910488332 天前
鸿蒙签名失败不是玄学:5 个证据点把 hapsigntool 一次排通 GitHub:swsgbl/hmharness
开源·ai编程·harmonyos
redtank20002 天前
纯血鸿蒙小工具三件套实测:弹幕手持、爱记录与仓库管理软件
学习·华为·智能手机·harmonyos