页面预览

前言

在 HarmonyOS 的 Stage 模型中,Context 是连接应用、模块、Ability 和 UI 层的核心纽带。每个组件都有其对应的 Context 类型,提供不同粒度的能力——从获取应用基本信息到启动其他 Ability,从资源管理到 UI 弹框控制。开发者如果混淆了不同 Context 的职责,轻则代码逻辑出错,重则出现运行时异常。本文以 小事记(xiaoshiji_ohos_app) 项目中的 EntryAbility.etsSettingsPage.ets 为切入点,深入解析 ApplicationContextUIAbilityContextAbilityStageContextUIContext 的继承关系、获取方式与最佳实践。

核心特点:

  • 简单易用:API 设计直观,上手成本低
  • 性能优异:底层优化充分,运行效率高
  • 扩展性强:支持自定义配置和扩展

本文参考 HarmonyOS 官方文档:application-context-stage.mdUIAbility 参考

一、Context 类的继承体系

1.1 类层级结构

HarmonyOS 的 Context 采用分层继承设计,每一层在基类基础上扩展特定能力:

Context(基类)
├── ApplicationContext(应用级别)
├── AbilityStageContext(模块级别)
├── UIAbilityContext(Ability 级别)
│   └── 通过 getHostContext() 获取
└── ExtensionContext(扩展能力级别)
    ├── BackupExtensionContext
    ├── ServiceExtensionContext
    └── FormExtensionContext

UIContext 是独立的 UI 上下文,与上述继承体系无直接关系,它属于 ArkUI 框架的 UI 实例上下文。

Context 类型 作用域 获取方式 关键能力
Context(基类) 全局 所有子类继承 resourceManagerapplicationInfoarea(文件分区)
ApplicationContext 应用进程 this.context.getApplicationContext() 设置颜色模式、语言、监听前后台、清除数据
AbilityStageContext 模块 AbilityStage 的 this.context 获取模块信息、createModuleContext()
UIAbilityContext Ability UIAbility 的 this.context startAbility()connectServiceExtensionAbility()terminateSelf()
UIContext UI 实例 getUIContext() 弹框、字体、键盘避让、获取宿主 Context

1.2 不同类型 Context 不可互转

官方文档明确指出:不同类型的 Context 具有不同的能力,不可相互替代或强行转换

// ❌ 错误:UIAbilityContext 没有 setFontSizeScale 方法
let uiAbilityContext = this.context as common.UIAbilityContext;
uiAbilityContext.setFontSizeScale(1.0); // 编译错误!

// ✅ 正确:先获取 ApplicationContext
let applicationContext = this.context.getApplicationContext();
applicationContext.setFontSizeScale(1.0, 1.0);

二、ApplicationContext:应用全局上下文

2.1 获取方式

ApplicationContext 是应用级别的全局上下文,在整个应用进程中唯一。官方提供了两种获取方式:

方式一:从 API 14 起,直接使用静态方法获取

import { application } from '@kit.AbilityKit';

let appContext = application.getApplicationContext();

方式二:通过已有的 Context 实例获取(兼容所有版本)

// 在 UIAbility 中获取
export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    let appContext = this.context.getApplicationContext();
    // 使用 ApplicationContext 设置颜色模式
    appContext.setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
  }
}

2.2 核心能力详解

设置颜色模式 — 小事记项目在 onCreate 中调用了 setColorMode

// EntryAbility.ets — 使用 ApplicationContext 设置颜色模式
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  try {
    this.context.getApplicationContext().setColorMode(
      ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET
    );
  } catch (err) {
    hilog.error(DOMAIN, 'testTag', 
      'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
  }
}

COLOR_MODE_NOT_SET 表示跟随系统设置,还有 COLOR_MODE_LIGHT(浅色)和 COLOR_MODE_DARK(深色)模式。

获取应用文件路径

let appContext = this.context.getApplicationContext();
let filesDir = appContext.filesDir;  // 应用文件目录
let cacheDir = appContext.cacheDir;  // 缓存目录
let tempDir = appContext.tempDir;    // 临时目录

监听应用前后台变化

let appContext = this.context.getApplicationContext();
appContext.on('abilityLifecycle', (abilityLifecycleInfo) => {
  if (abilityLifecycleInfo.lifecycleState === 'foreground') {
    console.log('应用进入前台');
  } else if (abilityLifecycleInfo.lifecycleState === 'background') {
    console.log('应用进入后台');
  }
});

设置应用语言

appContext.setLanguage('zh-Hans-CN');  // 设置为简体中文

2.3 使用场景总结

场景 使用 ApplicationContext 原因
设置颜色模式 ✅ 必须使用 应用级配置,全局生效
设置语言 ✅ 必须使用 应用级配置,全局生效
获取文件目录 ✅ 推荐使用 路径与应用进程绑定
监听前后台 ✅ 必须使用 只有 ApplicationContext 提供 on('abilityLifecycle')
启动其他 Ability ❌ 不可使用 需要 UIAbilityContext.startAbility()
显示弹框 ❌ 不可使用 需要 UIContext.showDialog()

三、UIAbilityContext:Ability 级别上下文

3.1 获取方式

UIAbilityContext 是每个 UIAbility 实例独有的上下文,在 UIAbility 内部通过 this.context 直接获取:

// EntryAbility.ets — 直接访问 this.context
export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // this.context 的类型是 UIAbilityContext
    let abilityInfo = this.context.abilityInfo;
    let hapModuleInfo = this.context.currentHapModuleInfo;
  }
}

在 UI 组件中获取 UIAbilityContext,需要通过 UIContext.getHostContext()

// 在 @Component 中获取 UIAbilityContext
import { common } from '@kit.AbilityKit';

@Entry
@Component
struct SettingsPage {
  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;

  build() {
    Button('启动其他应用')
      .onClick(() => {
        this.context.startAbility({
          bundleName: 'com.example.other',
          abilityName: 'MainAbility'
        });
      })
  }
}

3.2 核心能力详解

启动其他 Ability

// 显式启动
this.context.startAbility({
  bundleName: 'com.example.target',
  abilityName: 'TargetAbility',
  parameters: { key: 'value' }
});

// 隐式启动
this.context.startAbility({
  action: 'ohos.want.action.view',
  uri: 'https://developer.harmonyos.com'
});

连接 ServiceExtensionAbility

let connectionId = this.context.connectServiceExtensionAbility(
  {
    bundleName: 'com.example.service',
    abilityName: 'BackgroundService'
  },
  {
    onConnect: (elementName, proxy) => {
      console.log('Service 连接成功');
    },
    onDisconnect: () => {
      console.log('Service 断开连接');
    }
  }
);

销毁自身

this.context.terminateSelf((err) => {
  if (err.code) {
    console.error(`terminateSelf failed: ${err.message}`);
  }
});

3.3 UIAbilityContext 与 ApplicationContext 的协作

实际开发中,两个 Context 经常配合使用:

// 在 UIAbility 中协作
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  // UIAbilityContext — 记录启动参数
  let abilityName = this.context.abilityInfo.name;
  
  // ApplicationContext — 设置全局配置
  let appContext = this.context.getApplicationContext();
  appContext.setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
}

四、AbilityStageContext:模块级别上下文

4.1 定义与获取

AbilityStage 是模块级别的生命周期入口,在 HAP 包加载时创建。它的 Context 是 AbilityStageContext,提供了模块级别的信息:

import { AbilityStage } from '@kit.AbilityKit';

export default class MyAbilityStage extends AbilityStage {
  onCreate(): void {
    // this.context 是 AbilityStageContext
    let hapModuleInfo = this.context.currentHapModuleInfo;
    console.log(`模块名称: ${hapModuleInfo.moduleName}`);
    console.log(`模块描述: ${hapModuleInfo.description}`);
  }
}

4.2 跨模块 Context 获取

在多 Module 工程中,可以通过 createModuleContext 获取其他模块的 Context:

// 获取其他 Module 的 Context
import { application } from '@kit.AbilityKit';

let moduleName = 'feature_module';
application.createModuleContext(this.context, moduleName)
  .then((moduleContext: common.Context) => {
    // 通过 moduleContext 访问该模块的资源
    let resourceMgr = moduleContext.resourceManager;
  })
  .catch((err: BusinessError) => {
    console.error(`获取模块 Context 失败: ${err.message}`);
  });

提示:如果小事记项目后续扩展为多 Module 架构(如添加 share 功能 HSP 模块),就需要通过 createModuleContext 来跨模块共享数据和资源。

五、UIContext:UI 实例上下文

5.1 与上述 Context 的本质区别

UIContext 不属于 Context 继承体系,它是 ArkUI 框架中的 UI 实例上下文,负责 UI 层面的操作。

对比维度 Context 体系 UIContext
所属框架 @kit.AbilityKit @kit.ArkUI
作用范围 应用/模块/Ability 生命周期 UI 实例/窗口级别
主要能力 启动 Ability、文件管理、资源管理 弹框、字体、键盘避让、动画
获取方式 this.contextgetHostContext() getUIContext()window.getUIContext()

5.2 获取方式

在 UI 组件中获取

@Entry
@Component
struct HomePage {
  build() {
    Button('显示弹框')
      .onClick(() => {
        // 通过 getUIContext() 获取 UIContext
        this.getUIContext().getPromptAction().showToast({
          message: '操作成功',
          duration: 2000
        });
      })
  }
}

通过 Window 获取

import { window } from '@kit.ArkUI';

// 在 UIAbility 中获取 Window 的 UIContext
onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.getMainWindow().then((mainWindow) => {
    let uiContext = mainWindow.getUIContext();
    // 使用 UIContext
  });
}

5.3 核心能力详解

显示弹框与提示

let uiContext = this.getUIContext();

// Toast 提示
uiContext.getPromptAction().showToast({ message: '保存成功', duration: 2000 });

// 确认对话框
uiContext.getPromptAction().showDialog({
  title: '提示',
  text: '确定要删除这条记录吗?',
  buttons: [
    { text: '取消', color: '#9CA3AF' },
    { text: '确定', color: '#FF6B6B' }
  ]
});

// 操作菜单
uiContext.getPromptAction().showActionMenu({
  title: '请选择操作',
  buttons: [
    { text: '编辑', color: '#7B68EE' },
    { text: '删除', color: '#FF6B6B' }
  ]
});

获取宿主 Context

// 在 UI 组件中获取 UIAbilityContext
let uiAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;

设置键盘避让模式

this.getUIContext().setKeyboardAvoidMode(
  KeyboardAvoidMode.RESIZE  // 键盘弹出时应用窗口向上避让
);

六、实际项目中的 Context 使用模式

6.1 小事记项目的 Context 使用分析

EntryAbility.ets 中,使用了两种 Context 的协作:

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 1. 使用 UIAbilityContext(this.context)获取 ApplicationContext
    let appContext = this.context.getApplicationContext();
    
    // 2. 使用 ApplicationContext 设置颜色模式
    appContext.setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    
    // 3. 使用 UIAbilityContext 记录日志信息
    hilog.info(DOMAIN, 'testTag', 'Ability onCreate');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 4. 使用 WindowStage 加载页面
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 
          'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
    });
  }
}

Context 的职责流转过程如下:

用户点击应用图标
     ↓
系统创建 UIAbility 实例
     ↓
this.context = UIAbilityContext  ←  Ability 级别的上下文
     ↓
this.context.getApplicationContext() → ApplicationContext  ← 应用级别的上下文
     ↓
onWindowStageCreate(windowStage) → WindowStage  ← 窗口管理
     ↓
windowStage.loadContent('pages/Index') → 加载 UI 页面
     ↓
在 UI 组件中 getUIContext() → UIContext  ← UI 实例上下文

6.2 推荐的 Context 获取策略

代码位置 推荐的 Context 获取方式 原因
UIAbility 内部 直接使用 this.context 类型为 UIAbilityContext,功能最全
UIAbility 内部(应用级操作) this.context.getApplicationContext() 获取应用级能力
AbilityStage 内部 this.context(类型为 AbilityStageContext 模块级操作
@Component 组件(UI 操作) this.getUIContext() 直接访问 UI 相关 API
@Component 组件(Ability 操作) this.getUIContext().getHostContext() 获取 UIAbilityContext
ExtensionAbility 内部 this.context(类型为 ExtensionContext 扩展能力上下文

6.3 常见错误与避免

错误 1:在 UI 组件中直接使用 UIAbility 的 this.context

// ❌ 错误:UI 组件中没有 this.context
@Entry
@Component
struct HomePage {
  aboutToAppear(): void {
    let appContext = this.context.getApplicationContext(); // 编译错误!
  }
}

// ✅ 正确:通过 UIContext 获取宿主 Context
@Entry
@Component
struct HomePage {
  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  
  aboutToAppear(): void {
    let appContext = this.context.getApplicationContext(); // 正确
  }
}

错误 2:混淆 UIContext 与 ApplicationContext

// ❌ 错误:UIContext 没有 setLanguage 方法
let uiContext = this.getUIContext();
uiContext.setLanguage('zh-Hans-CN'); // 编译错误!

// ✅ 正确:获取 ApplicationContext 后调用
let appContext = this.getUIContext().getHostContext()
  .getApplicationContext();
appContext.setLanguage('zh-Hans-CN');

七、Context 在性能与内存中的考量

7.1 Context 的引用传递

Context 持有与泄露是常见的内存问题。ApplicationContext 是单例的,而 UIAbilityContext 在 Ability 销毁后应被释放:

// ❌ 错误:在全局单例中持有 UIAbilityContext
class GlobalManager {
  static context: UIAbilityContext;  // 可能造成泄露
}

// ✅ 正确:使用 ApplicationContext 进行全局存储
class GlobalManager {
  static context: ApplicationContext;  // 应用级别,不会泄露
}

7.2 Context 的等价性判断

由于 Context 存在继承关系,判断两个 Context 是否相等时需要注意:

// 两个不同的 UIAbility 实例,它们的 context 不同
let context1 = ability1.context;
let context2 = ability2.context;
console.log(context1 === context2);  // false

// 但 getApplicationContext() 返回的是同一个对象
let appContext1 = ability1.context.getApplicationContext();
let appContext2 = ability2.context.getApplicationContext();
console.log(appContext1 === appContext2);  // true(单例)

八、实战:在 SettingsPage 中管理 Context

8.1 场景描述

在小事记的 SettingsPage.ets 中,需要实现以下功能:

  1. 读取用户配置(需要 ApplicationContext 获取文件路径)
  2. 跳转到数据备份页面(需要 UIAbilityContext 启动 Ability)
  3. 显示提示弹框(需要 UIContext)

8.2 完整实现

// SettingsPage.ets — 三种 Context 的协作使用
import router from '@ohos.router';
import { common } from '@kit.AbilityKit';
import { BottomTabBar } from '../common/BottomTabBar';

@Entry
@Component
export struct SettingsPage {
  // 获取 UIAbilityContext(用于启动 Ability)
  private uiAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
  
  // 获取 ApplicationContext(用于文件操作)
  private appContext = this.uiAbilityContext.getApplicationContext();

  build() {
    Column() {
      // 设置页面 UI
      Scroll() {
        Column({ space: 16 }) {
          this.buildSettingsCard('data')
        }
        .padding({ bottom: 20 })
      }
      .layoutWeight(1)
      
      BottomTabBar({ currentTab: 'SettingsPage' })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F8F9FA')
  }

  @Builder
  buildSettingsCard(group: string) {
    Column({ space: 0 }) {
      if (group === 'data') {
        Row() {
          Text('数据备份')
          Blank()
          Text('>')
        }
        .height(52)
        .padding({ left: 20, right: 20 })
        .onClick(() => {
          // 使用 UIAbilityContext 的路由能力
          router.pushUrl({ url: 'pages/DataBackupPage' });
        })
      }
    }
    .backgroundColor(Color.White)
    .borderRadius(16)
    .margin({ left: 20, right: 20 })
  }
}

总结

本文从 xiaoshiji_ohos_app 项目的源码出发,深入解析了 HarmonyOS Stage 模型中 Context 类层级体系 的设计与使用。核心要点如下:

  1. Context 体系分为五层:基类 Context → ApplicationContext / AbilityStageContext / UIAbilityContext / ExtensionContext,各层职责分明,不可互转
  2. UIContext 是独立的 UI 上下文,不属于 Context 继承体系,专用于 UI 操作(弹框、字体、键盘避让)
  3. 获取策略:UIAbility 中直接使用 this.context,UI 组件中通过 getUIContext().getHostContext() 获取 Ability 上下文
  4. 内存管理:ApplicationContext 是单例安全的,而 UIAbilityContext 在 Ability 销毁后应被释放,避免在全局单例中持有

下一篇文章将深入解析 module.json5 配置,详解 Ability 声明、skills 隐式匹配和 extensionAbilities 的配置技巧。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

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

更多推荐