在这里插入图片描述

每日一句正能量

别站在自己的烦恼里,去仰望别人的幸福。
我们在自己的烦恼里,总觉得别人的生活光鲜亮丽。但事实是,每个人都有自己的一地鸡毛,只是你看不见。你羡慕的人,可能也在某个深夜羡慕着你。幸福不是用来比较的,是用来感受的。低头看看自己拥有的,也许比仰望别人更重要。
愿我们都能在有限的生命里,搭建属于自己的美好,与自己同频,从容地走向每一个明天。

一、引言:从持久化状态到环境感知

在前两篇《LocalStorage 页面级状态》和《PersistentStorage 持久化状态》中,我们分别解决了折叠屏应用跨组件状态共享跨会话状态保持两大核心问题。通过 LocalStorage,Header、Sidebar、Content 等独立组件实现了对折叠态的实时联动;通过 PersistentStorage,用户的主题偏好、字体设置、侧边栏状态在应用重启后原封不动自动恢复。

然而,一个完整的折叠屏应用还需要回答第三个关键问题:当系统环境发生变化时,应用如何自动感知并自适应?

设想这些场景:

  • 用户在系统设置中切换了深色模式,回到应用时期望界面自动变暗,而不是手动去设置面板调整。
  • 用户将系统语言从中文切换为英文,应用内的所有文案应当即时刷新,无需重启。
  • 用户在辅助功能中开启了"大字体",应用内的文字应当自动放大,确保可读性。
  • 用户在折叠屏展开状态下旋转了设备(横竖屏切换),布局应当自动重新计算断点。

这些场景的共同特征是:状态的变化源头不在应用内部,而在操作系统层面。应用需要一种机制,能够实时监听系统环境的变化,并将这些变化自动映射到 UI 组件的刷新逻辑中。

HarmonyOS 提供了两套互补的方案来应对这一需求:

  1. Environment API(API 10+):传统的环境变量注入方案,将系统参数批量写入 AppStorage,组件通过 @StorageProp / @StorageLink 访问。
  2. @Env 装饰器(API 22+):新一代响应式环境变量装饰器,组件内直接声明即可自动感知系统变化,无需手动初始化。

本文将围绕这两套方案的核心机制、使用场景、折叠屏实战,以及企业级最佳实践,展开系统性讲解。


二、Environment 核心原理与架构定位

2.1 在 ArkUI 状态管理中的位置

Environment 是 ArkUI 框架在应用启动时创建的单例对象,它为 AppStorage 提供了一系列描述应用程序运行状态的只读属性。与 LocalStorage(页面级)、AppStorage(应用级)、PersistentStorage(持久化级)不同,Environment 的数据来源不是应用自身,而是操作系统

层级 数据来源 可写性 生命周期 核心作用
LocalStorage 应用自定义 可写 UIAbility 级 页面内组件共享
AppStorage 应用自定义 可写 应用级 全局状态共享
PersistentStorage 应用自定义 可写 跨应用生命周期 状态持久化
Environment 操作系统 只读 跟随系统 环境感知

在这里插入图片描述

核心特征

  • 只读性:Environment 的所有属性都是不可变的,应用只能读取、不能写入。这是合理的——应用无权修改用户的系统语言或主题偏好。
  • 自动同步:当系统环境发生变化时(如用户切换主题、调整字体),Environment 会自动将最新值注入 AppStorage,触发所有绑定组件的 UI 刷新。
  • 简单类型:所有环境变量都是基础类型(booleannumberstringenum),不支持嵌套对象,确保序列化和同步的高效性。

2.2 内置环境变量清单

HarmonyOS 目前提供以下系统环境变量:

环境变量 Key 数据类型 取值范围 典型场景
accessibilityEnabled boolean true / false 无障碍模式适配
colorMode ColorMode LIGHT / DARK 深色/浅色模式切换
fontScale number [0.85, 1.45] 系统字体大小缩放
fontWeightScale number [0.6, 1.6] 系统字体粗细缩放
layoutDirection LayoutDirection LTR / RTL 阿拉伯语等 RTL 布局
languageCode string 小写字母,如 zh 多语言国际化

在这里插入图片描述


三、Environment API 实战:从注入到消费

3.1 在 UIAbility 中初始化环境变量

最佳实践是在 EntryAbilityonWindowStageCreate 中,通过 Environment.envProps() 批量将系统环境变量注入 AppStorage:

// entry/src/main/ets/entryability/EntryAbility.ets
import { UIAbility } from '@kit.AbilityKit';
import { window, Environment, ColorMode, LayoutDirection } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 第一步:批量注入系统环境变量到 AppStorage
    // 必须在 loadContent 之前调用!
    // 必须在任何组件通过 @StorageLink 访问之前调用!
    Environment.envProps([
      { key: 'colorMode', defaultValue: ColorMode.LIGHT },
      { key: 'languageCode', defaultValue: 'zh' },
      { key: 'fontScale', defaultValue: 1.0 },
      { key: 'fontWeightScale', defaultValue: 1.0 },
      { key: 'layoutDirection', defaultValue: LayoutDirection.LTR },
      { key: 'accessibilityEnabled', defaultValue: false },
    ]);

    // 第二步:加载页面
    windowStage.loadContent('pages/Index');
  }
}

关键要点

  1. 批量注入优于单条注入envProps 接收数组参数,一次调用完成所有环境变量的初始化,减少框架内部开销。
  2. 必须提供默认值:如果系统当前无法读取某个环境变量(如极端情况),框架会使用默认值兜底,避免组件初始化失败。
  3. 初始化顺序铁律Environment.envProps() 必须在 windowStage.loadContent() 之前调用,必须在任何组件通过 @StorageLink / @StorageProp 访问环境变量之前调用。

3.2 在组件中消费环境变量

环境变量注入 AppStorage 后,组件侧的使用方式与普通的 AppStorage 状态完全一致:

// entry/src/main/ets/pages/Index.ets
import { ColorMode, LayoutDirection } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  // 使用 @StorageProp 单向只读绑定(推荐,因为环境变量不可写入)
  @StorageProp('colorMode') colorMode: ColorMode = ColorMode.LIGHT;
  @StorageProp('languageCode') languageCode: string = 'zh';
  @StorageProp('fontScale') fontScale: number = 1.0;
  @StorageProp('fontWeightScale') fontWeightScale: number = 1.0;
  @StorageProp('layoutDirection') layoutDirection: LayoutDirection = LayoutDirection.LTR;
  @StorageProp('accessibilityEnabled') accessibilityEnabled: boolean = false;

  // 颜色资源映射
  private getThemeColors() {
    if (this.colorMode === ColorMode.DARK) {
      return {
        background: '#1A1A2E',
        surface: '#2D2D3A',
        textPrimary: '#FFFFFF',
        textSecondary: '#B0BEC5',
        accent: '#FF9800',
        divider: '#424242',
      };
    } else {
      return {
        background: '#F5F5F5',
        surface: '#FFFFFF',
        textPrimary: '#1A1A2E',
        textSecondary: '#616161',
        accent: '#1565C0',
        divider: '#E0E0E0',
      };
    }
  }

  build() {
    const colors = this.getThemeColors();
    
    Column() {
      // 根据系统语言显示对应文案
      Text(this.languageCode === 'zh' ? '鸿蒙折叠屏商城' : 'HarmonyOS Foldable Store')
        .fontSize(24 * this.fontScale)
        .fontWeight(FontWeight.Bold)
        .fontColor(colors.textPrimary)
      
      // 根据系统字体缩放调整所有文字
      Text(this.languageCode === 'zh' ? '最新推荐' : 'Recommended')
        .fontSize(18 * this.fontScale)
        .fontColor(colors.textSecondary)
      
      // 根据无障碍模式调整交互元素大小
      if (this.accessibilityEnabled) {
        // 无障碍模式:增大点击区域、提高对比度
        Button(this.languageCode === 'zh' ? '立即购买' : 'Buy Now')
          .width('100%')
          .height(56)
          .fontSize(18 * this.fontScale)
          .fontWeight(FontWeight.Bold)
          .backgroundColor(colors.accent)
          .fontColor('#FFFFFF')
      } else {
        // 普通模式:标准尺寸
        Button(this.languageCode === 'zh' ? '立即购买' : 'Buy Now')
          .width('80%')
          .height(48)
          .fontSize(16 * this.fontScale)
          .backgroundColor(colors.accent)
          .fontColor('#FFFFFF')
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor(colors.background)
    .padding(16)
    // 根据布局方向调整排列
    .direction(this.layoutDirection === LayoutDirection.RTL ? Direction.Rtl : Direction.Ltr)
  }
}

核心体验:用户在系统设置中切换深色模式后,回到应用,界面已经自动适配为深色主题,无需任何手动操作。这是 Environment 的零代码感知能力——开发者只需在初始化时注入一次,后续所有系统变化自动触发 UI 刷新。


四、折叠屏场景实战:深色模式自适应适配

4.1 为什么折叠屏更需要环境感知?

折叠屏设备相比普通手机,在环境适配上面临两个独特挑战:

挑战一:屏幕尺寸差异巨大

展开态可能达到 8 英寸、1536px 宽度,折叠态可能只有 6.5 英寸、720px 宽度。同一套深色模式配色,在展开态大屏上可能对比度过高刺眼,在折叠态小屏上可能对比度不足难以辨认。需要根据屏幕尺寸动态调整色值。

挑战二:多窗口独立环境

折叠屏分屏模式下,左右两个应用窗口可能处于不同的显示环境中(例如左屏是深色模式、右屏是浅色模式——虽然系统级主题通常是统一的,但窗口焦点、缩放系数等可能不同)。每个窗口需要独立感知自身环境。

4.2 完整实战代码

// entry/src/main/ets/entryability/EntryAbility.ets
import { UIAbility } from '@kit.AbilityKit';
import { window, Environment, ColorMode, LayoutDirection } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 第一步:注入系统环境变量
    Environment.envProps([
      { key: 'colorMode', defaultValue: ColorMode.LIGHT },
      { key: 'languageCode', defaultValue: 'zh' },
      { key: 'fontScale', defaultValue: 1.0 },
      { key: 'fontWeightScale', defaultValue: 1.0 },
      { key: 'layoutDirection', defaultValue: LayoutDirection.LTR },
      { key: 'accessibilityEnabled', defaultValue: false },
    ]);

    // 第二步:监听窗口尺寸变化(折叠屏核心)
    windowStage.getMainWindow().then((win) => {
      win.on('windowSizeChange', (size: window.Size) => {
        const width = size.width;
        const isExpanded = width >= 800;
        
        AppStorage.setOrCreate('screenWidth', width);
        AppStorage.setOrCreate('isExpanded', isExpanded);
        AppStorage.setOrCreate('foldState', 
          width >= 1200 ? 'full' : width >= 800 ? 'half' : 'compact'
        );
      });
    });

    windowStage.loadContent('pages/Index');
  }
}
// entry/src/main/ets/store/ThemeAdapter.ets
import { ColorMode } from '@kit.ArkUI';

// 折叠屏专用主题适配器
export class ThemeAdapter {
  // 根据 colorMode + 屏幕尺寸,动态生成主题色值
  static getColors(colorMode: ColorMode, isExpanded: boolean) {
    if (colorMode === ColorMode.DARK) {
      return {
        // 展开态大屏:降低对比度,保护视力
        background: isExpanded ? '#12121A' : '#1A1A2E',
        surface: isExpanded ? '#1E1E2E' : '#2D2D3A',
        textPrimary: isExpanded ? '#E0E0E0' : '#FFFFFF',
        textSecondary: isExpanded ? '#9E9E9E' : '#B0BEC5',
        accent: '#FF9800',
        divider: isExpanded ? '#333333' : '#424242',
        shadow: '#00000020',
      };
    } else {
      return {
        background: '#F5F5F5',
        surface: '#FFFFFF',
        textPrimary: '#1A1A2E',
        textSecondary: '#616161',
        accent: '#1565C0',
        divider: '#E0E0E0',
        shadow: '#00000010',
      };
    }
  }

  // 根据 fontScale + 屏幕尺寸,计算实际字体大小
  static getFontSize(baseSize: number, fontScale: number, isExpanded: boolean): number {
    // 展开态大屏:字体可以稍小,因为视距更远
    const screenFactor = isExpanded ? 0.95 : 1.0;
    return baseSize * fontScale * screenFactor;
  }

  // 根据 accessibilityEnabled + 屏幕尺寸,计算点击区域
  static getTouchSize(baseSize: number, accessibilityEnabled: boolean, isExpanded: boolean): number {
    if (accessibilityEnabled) {
      // 无障碍模式:最小 56dp
      return Math.max(baseSize * 1.5, 56);
    }
    // 展开态:可以适当放大,方便远距离操作
    return isExpanded ? baseSize * 1.1 : baseSize;
  }
}
// entry/src/main/ets/pages/Index.ets
import { ColorMode, LayoutDirection } from '@kit.ArkUI';
import { ThemeAdapter } from '../store/ThemeAdapter';

@Entry
@Component
struct Index {
  // 系统环境变量(只读)
  @StorageProp('colorMode') colorMode: ColorMode = ColorMode.LIGHT;
  @StorageProp('languageCode') languageCode: string = 'zh';
  @StorageProp('fontScale') fontScale: number = 1.0;
  @StorageProp('fontWeightScale') fontWeightScale: number = 1.0;
  @StorageProp('accessibilityEnabled') accessibilityEnabled: boolean = false;
  
  // 折叠屏状态(内存)
  @StorageLink('isExpanded') isExpanded: boolean = false;
  @StorageLink('foldState') foldState: string = 'compact';

  // 文案资源
  private getText(key: string): string {
    const texts: Record<string, Record<string, string>> = {
      'appName': { 'zh': '鸿蒙商城', 'en': 'Harmony Store', 'ja': 'ハーモニー商店' },
      'category': { 'zh': '分类', 'en': 'Category', 'ja': 'カテゴリ' },
      'cart': { 'zh': '购物车', 'en': 'Cart', 'ja': 'カート' },
      'buyNow': { 'zh': '立即购买', 'en': 'Buy Now', 'ja': '今すぐ購入' },
    };
    return texts[key]?.[this.languageCode] || texts[key]?.['en'] || key;
  }

  build() {
    const colors = ThemeAdapter.getColors(this.colorMode, this.isExpanded);
    const titleSize = ThemeAdapter.getFontSize(24, this.fontScale, this.isExpanded);
    const bodySize = ThemeAdapter.getFontSize(16, this.fontScale, this.isExpanded);
    const buttonHeight = ThemeAdapter.getTouchSize(48, this.accessibilityEnabled, this.isExpanded);

    Column() {
      // 头部
      Row() {
        Text(this.getText('appName'))
          .fontSize(titleSize)
          .fontWeight(FontWeight.Bold * this.fontWeightScale)
          .fontColor(colors.textPrimary)
        
        // 语言指示器
        Text(this.languageCode.toUpperCase())
          .fontSize(bodySize * 0.8)
          .fontColor(colors.textSecondary)
          .backgroundColor(colors.surface)
          .padding({ left: 8, right: 8, top: 4, bottom: 4 })
          .borderRadius(4)
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
      .padding(16)
      .backgroundColor(colors.surface)
      .shadow({ radius: 2, color: colors.shadow, offsetY: 1 })

      // 内容区
      Column({ space: 16 }) {
        // 商品卡片
        Column({ space: 8 }) {
          Image($r('app.media.product'))
            .width(this.isExpanded ? 300 : '100%')
            .height(this.isExpanded ? 300 : 200)
            .borderRadius(12)
          
          Text('Harmony Fold X')
            .fontSize(bodySize)
            .fontColor(colors.textPrimary)
          
          Text('¥ 8999')
            .fontSize(titleSize)
            .fontColor(colors.accent)
            .fontWeight(FontWeight.Bold)
        }
        .width('100%')
        .padding(16)
        .backgroundColor(colors.surface)
        .borderRadius(12)

        // 购买按钮
        Button(this.getText('buyNow'))
          .width('100%')
          .height(buttonHeight)
          .fontSize(bodySize)
          .fontWeight(FontWeight.Bold)
          .backgroundColor(colors.accent)
          .fontColor('#FFFFFF')
          .borderRadius(buttonHeight / 2)
      }
      .layoutWeight(1)
      .padding(16)

      // 底部导航
      Row() {
        Text(this.getText('category'))
          .fontSize(bodySize)
          .fontColor(colors.textSecondary)
        Text(this.getText('cart'))
          .fontSize(bodySize)
          .fontColor(colors.textSecondary)
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceAround)
      .padding(16)
      .backgroundColor(colors.surface)
      .border({ width: { top: 1 }, color: colors.divider })
    }
    .width('100%')
    .height('100%')
    .backgroundColor(colors.background)
    .direction(this.layoutDirection === LayoutDirection.RTL ? Direction.Rtl : Direction.Ltr)
  }
}

在这里插入图片描述

4.3 用户体验闭环

  1. 系统切换深色模式:用户在设置中开启深色模式。
  2. Environment 自动感知:框架检测到 colorMode 变化,自动更新 AppStorage。
  3. 组件自动刷新:所有绑定 @StorageProp('colorMode') 的组件重新 build,调用 ThemeAdapter.getColors() 获取新的色值。
  4. 折叠屏特殊处理ThemeAdapter 根据当前 isExpanded 状态,为展开态和折叠态分别生成合适的对比度色值。
  5. 界面无缝切换:用户回到应用,界面已经完成深色适配,且展开态的色值比折叠态更柔和,保护视力。

五、@Env 装饰器:新一代响应式环境变量(API 22+)

5.1 为什么需要 @Env?

传统的 Environment API 虽然功能完备,但存在两个使用痛点:

  1. 初始化代码分散:必须在 EntryAbility 中手动调用 envProps(),对于大型项目,Ability 文件会变得臃肿。
  2. 间接访问:组件需要通过 AppStorage 间接访问环境变量,多了一层心智负担。

API 22 引入的 @Env 装饰器解决了这些问题。它是一个响应式系统环境变量装饰器,组件内直接声明即可使用,框架自动处理初始化、监听和刷新。

5.2 @Env 与传统 Environment 的对比

对比维度 Environment(API 10+) @Env(API 22+)
声明位置 EntryAbility 中初始化 组件内直接声明
访问方式 @StorageProp('key') @Env(SystemProperties.XXX)
响应式能力 通过 AppStorage 间接响应 组件直接自动响应
监听粒度 整体 key 变化 支持 addMonitor 细粒度属性监听
支持变量 全部环境变量 目前仅支持窗口相关变量
可用版本 API 10+ API 22+

在这里插入图片描述

5.3 @Env 实战:窗口断点自适应

@Env 目前支持以下系统属性(API 版本逐步扩展):

系统属性 说明 起始版本
SystemProperties.BREAK_POINT 窗口尺寸布局断点信息 API 22
SystemProperties.WINDOW_SIZE 窗口大小(单位 vp) API 23
SystemProperties.WINDOW_SIZE_PX 窗口大小(单位 px) API 23
SystemProperties.WINDOW_AVOID_AREA 窗口避让区域(vp) API 23
SystemProperties.WINDOW_AVOID_AREA_PX 窗口避让区域(px) API 23
SystemProperties.WINDOW_DISPLAY_ID 窗口所在屏幕 ID API 26
SystemProperties.WINDOW_SYSTEM_DENSITY 系统显示缩放系数 API 26
SystemProperties.WINDOW_IS_FOCUSED 窗口是否获焦 API 26
// entry/src/main/ets/components/AdaptiveLayout.ets
import { uiObserver, UIUtils } from '@kit.ArkUI';

@Component
struct AdaptiveLayout {
  // 使用 @Env 直接获取窗口断点信息
  @Env(SystemProperties.BREAK_POINT) breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo;
  
  // 使用 @Env 获取窗口大小
  @Env(SystemProperties.WINDOW_SIZE) windowSize: uiObserver.SizeInVP;

  // 监听断点变化
  orientationChange(mon: IMonitor) {
    mon.dirty.forEach((path: string) => {
      console.info(`断点变化: ${path}${mon.value(path)?.before} 变为 ${mon.value(path)?.now}`);
    });
  }

  aboutToAppear(): void {
    // @Env 返回的对象是 @ObservedV2 装饰的,属性由 @Trace 装饰
    // 可以使用 addMonitor 细粒度监听属性变化
    UIUtils.addMonitor(
      this.breakpoint,
      ['widthBreakpoint', 'heightBreakpoint'],
      this.orientationChange
    );
  }

  build() {
    Column() {
      // 显示当前断点信息
      Text(`宽度断点: ${this.breakpoint.widthBreakpoint}`)
        .fontSize(16)
      Text(`高度断点: ${this.breakpoint.heightBreakpoint}`)
        .fontSize(16)
      Text(`窗口宽度: ${this.windowSize.width}vp`)
        .fontSize(16)
      Text(`窗口高度: ${this.windowSize.height}vp`)
        .fontSize(16)

      // 根据断点自动切换布局
      if (this.breakpoint.widthBreakpoint === 'lg') {
        // 大屏:三栏布局
        ThreeColumnLayout();
      } else if (this.breakpoint.widthBreakpoint === 'md') {
        // 中屏:双栏布局
        TwoColumnLayout();
      } else {
        // 小屏:单栏布局
        SingleColumnLayout();
      }
    }
  }
}

@Env 的核心优势

  1. 零初始化:无需在 Ability 中写任何注入代码,组件自包含。
  2. 自动响应:窗口尺寸变化时,框架自动通知 @Env 变量更新,触发组件刷新。
  3. 细粒度监听:通过 addMonitor 可以监听具体属性的变化,而非整个对象。

当前限制@Env 目前仅支持窗口相关的系统属性(断点、尺寸、避让区域等)。对于 colorModelanguageCode 等传统环境变量,仍需使用 Environment.envProps() + @StorageProp 方案。


六、企业级最佳实践:集中式环境变量管理

6.1 工程化目录结构

src/main/ets/
├── ability/
│   └── EntryAbility.ets          # 统一初始化 Environment
├── store/
│   ├── EnvConfig.ets             # 环境变量 key 枚举与类型定义
│   ├── ThemeAdapter.ets          # 主题适配器(深色/浅色 + 折叠态)
│   ├── I18nManager.ets           # 国际化管理器
│   └── AccessibilityHelper.ets   # 无障碍辅助工具
├── components/
│   ├── AdaptiveLayout.ets        # 基于 @Env 的自适应布局组件
│   ├── ThemeAwareText.ets        # 自动感知主题的文本组件
│   └── AccessibleButton.ets      # 无障碍适配按钮
└── resources/
    ├── base/element/color.json   # 浅色模式颜色资源
    └── dark/element/color.json   # 深色模式颜色资源

在这里插入图片描述

6.2 类型安全的 Key 管理

// entry/src/main/ets/store/EnvConfig.ets
import { ColorMode, LayoutDirection } from '@kit.ArkUI';

// 环境变量 Key 枚举,避免魔法字符串
export enum EnvKey {
  COLOR_MODE = 'colorMode',
  LANGUAGE_CODE = 'languageCode',
  FONT_SCALE = 'fontScale',
  FONT_WEIGHT_SCALE = 'fontWeightScale',
  LAYOUT_DIRECTION = 'layoutDirection',
  ACCESSIBILITY_ENABLED = 'accessibilityEnabled',
}

// 环境变量默认值配置
export const ENV_DEFAULTS = [
  { key: EnvKey.COLOR_MODE, defaultValue: ColorMode.LIGHT },
  { key: EnvKey.LANGUAGE_CODE, defaultValue: 'zh' },
  { key: EnvKey.FONT_SCALE, defaultValue: 1.0 },
  { key: EnvKey.FONT_WEIGHT_SCALE, defaultValue: 1.0 },
  { key: EnvKey.LAYOUT_DIRECTION, defaultValue: LayoutDirection.LTR },
  { key: EnvKey.ACCESSIBILITY_ENABLED, defaultValue: false },
];

// 类型安全的环境变量访问器
export class EnvAccessor {
  static getColorMode(): ColorMode {
    return AppStorage.get<ColorMode>(EnvKey.COLOR_MODE) || ColorMode.LIGHT;
  }

  static getLanguageCode(): string {
    return AppStorage.get<string>(EnvKey.LANGUAGE_CODE) || 'zh';
  }

  static getFontScale(): number {
    return AppStorage.get<number>(EnvKey.FONT_SCALE) || 1.0;
  }

  static getFontWeightScale(): number {
    return AppStorage.get<number>(EnvKey.FONT_WEIGHT_SCALE) || 1.0;
  }

  static getLayoutDirection(): LayoutDirection {
    return AppStorage.get<LayoutDirection>(EnvKey.LAYOUT_DIRECTION) || LayoutDirection.LTR;
  }

  static isAccessibilityEnabled(): boolean {
    return AppStorage.get<boolean>(EnvKey.ACCESSIBILITY_ENABLED) || false;
  }
}

6.3 集中式初始化

// entry/src/main/ets/entryability/EntryAbility.ets
import { UIAbility } from '@kit.AbilityKit';
import { window, Environment, PersistentStorage } from '@kit.ArkUI';
import { ENV_DEFAULTS } from '../store/EnvConfig';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 严格初始化顺序:
    // 1. Environment(系统环境变量)
    // 2. PersistentStorage(持久化状态)
    // 3. AppStorage 业务数据
    // 4. loadContent

    // 第一步:注入系统环境变量
    Environment.envProps(ENV_DEFAULTS);

    // 第二步:持久化用户偏好(注意:不能与 Environment key 冲突)
    PersistentStorage.persistProp('userThemePreference', 'auto'); // auto / light / dark
    PersistentStorage.persistProp('userFontSizePreference', 'medium');

    // 第三步:业务数据初始化
    AppStorage.setOrCreate('isFirstLaunch', false);
    AppStorage.setOrCreate('currentPage', 'home');

    // 第四步:加载页面
    windowStage.loadContent('pages/Index');
  }
}

6.4 主题感知组件封装

// entry/src/main/ets/components/ThemeAwareText.ets
import { ColorMode } from '@kit.ArkUI';
import { ThemeAdapter } from '../store/ThemeAdapter';

@Component
struct ThemeAwareText {
  @StorageProp('colorMode') colorMode: ColorMode = ColorMode.LIGHT;
  @StorageProp('isExpanded') isExpanded: boolean = false;
  
  @Prop text: string = '';
  @Prop fontSize: number = 16;
  @Prop type: 'primary' | 'secondary' | 'accent' = 'primary';

  build() {
    const colors = ThemeAdapter.getColors(this.colorMode, this.isExpanded);
    const colorMap: Record<string, string> = {
      'primary': colors.textPrimary,
      'secondary': colors.textSecondary,
      'accent': colors.accent,
    };

    Text(this.text)
      .fontSize(this.fontSize)
      .fontColor(colorMap[this.type])
  }
}

七、高频踩坑与解决方案

7.1 五大核心陷阱

坑点一:Environment 与 AppStorage 命名冲突

// ❌ 错误:AppStorage 已存在同名属性,Environment 注入失败
AppStorage.setOrCreate('colorMode', 'custom'); // 先创建了
Environment.envProp('colorMode', ColorMode.LIGHT); // 注入失败!静默忽略

// ✅ 正确:Environment 先于任何 AppStorage 操作
Environment.envProps(ENV_DEFAULTS); // 先注入环境变量
AppStorage.setOrCreate('customColorMode', 'custom'); // 业务数据使用不同 key

坑点二:初始化顺序错误

// ❌ 错误:在组件中调用 envProps
@Entry
@Component
struct Index {
  aboutToAppear(): void {
    Environment.envProp('colorMode', ColorMode.LIGHT); // 太晚了!
  }
}

// ✅ 正确:在 EntryAbility.onWindowStageCreate 中,loadContent 之前
onWindowStageCreate(windowStage: window.WindowStage): void {
  Environment.envProps(ENV_DEFAULTS);
  windowStage.loadContent('pages/Index');
}

坑点三:尝试写入环境变量

// ❌ 错误:环境变量是只读的
@StorageLink('colorMode') colorMode: ColorMode = ColorMode.LIGHT;
// ...
this.colorMode = ColorMode.DARK; // 编译通过,但运行时不生效!

// ✅ 正确:使用 @StorageProp 单向绑定
@StorageProp('colorMode') colorMode: ColorMode = ColorMode.LIGHT;
// 只读,防止误操作

坑点四:Environment 与 PersistentStorage 顺序颠倒

// ❌ 错误:PersistentStorage 先执行,默认值覆盖环境变量
PersistentStorage.persistProp('colorMode', ColorMode.LIGHT);
Environment.envProps(ENV_DEFAULTS); // 系统真实值被默认值覆盖

// ✅ 正确:Environment 先于 PersistentStorage
Environment.envProps(ENV_DEFAULTS); // 先注入系统真实值
PersistentStorage.persistProp('userTheme', 'auto'); // 再持久化业务数据

坑点五:@Env 与 @StorageProp 混用同一数据源

// ❌ 错误:同一组件同时使用两种方式访问同一数据
@Component
struct BadExample {
  @Env(SystemProperties.BREAK_POINT) breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo;
  @StorageProp('breakpoint') oldBreakpoint: string = ''; // 数据源不一致!
}

// ✅ 正确:窗口相关用 @Env,传统环境变量用 @StorageProp
@Component
struct GoodExample {
  @Env(SystemProperties.BREAK_POINT) breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo; // 窗口断点
  @StorageProp('colorMode') colorMode: ColorMode = ColorMode.LIGHT; // 主题模式
}

7.2 性能优化建议

  1. 避免在 build 中频繁计算ThemeAdapter.getColors() 的结果可以缓存,避免每次 build 都重新生成色值对象。
  2. 使用 @StorageProp 而非 @StorageLink:环境变量是只读的,使用单向绑定可以减少框架的依赖收集开销。
  3. 批量注入优于单条注入envProps 一次完成所有初始化,减少 I/O 次数。
// 缓存主题色值,避免重复计算
@Entry
@Component
struct Index {
  @StorageProp('colorMode') colorMode: ColorMode = ColorMode.LIGHT;
  @StorageProp('isExpanded') isExpanded: boolean = false;
  
  // 使用 @State 缓存计算结果
  @State themeColors: Record<string, string> = {};
  
  aboutToAppear(): void {
    this.updateThemeColors();
  }
  
  onColorModeChange() {
    this.updateThemeColors();
  }
  
  private updateThemeColors(): void {
    this.themeColors = ThemeAdapter.getColors(this.colorMode, this.isExpanded);
  }

  build() {
    Column() {
      Text('标题')
        .fontColor(this.themeColors.textPrimary) // 直接使用缓存值
    }
    .backgroundColor(this.themeColors.background)
  }
}

八、总结

Environment 环境变量管理是 HarmonyOS ArkUI 状态管理体系中不可或缺的一环,它填补了"应用内部状态"与"操作系统环境"之间的感知鸿沟。在折叠屏这种强调自适应体验的设备形态上,Environment 的价值尤为突出:

  1. 零代码感知系统变化:用户切换主题、调整字体、更改语言,应用自动适配,无需任何手动操作。
  2. 与折叠态深度联动:通过 ThemeAdapter 等适配器,将环境变量与折叠屏展开/收起状态结合,生成最优的色值和布局参数。
  3. 双轨方案互补Environment.envProps() 覆盖全部传统环境变量(主题、语言、无障碍等),@Env 覆盖新一代窗口相关变量(断点、尺寸、避让区域等),两者结合实现完整的环境感知能力。
  4. 企业级可维护性:通过集中式初始化、类型安全封装、业务解耦设计,确保大型项目中环境变量管理的规范性和可维护性。

从 LocalStorage 的页面级共享,到 PersistentStorage 的跨会话保持,再到 Environment 的系统环境感知,HarmonyOS 提供了一套由内而外、层层递进的完整状态管理方案。掌握它们的边界、联动与最佳实践,是构建真正自适应、高体验的企业级鸿蒙应用的核心能力。


转载自:https://blog.csdn.net/u014727709/article/details/163507990
欢迎 👍点赞✍评论⭐收藏,欢迎指正

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐