云计算百科
云计算领域专业知识百科平台

HarmonyOS5 凭什么学鸿蒙—— Context详解

一、引言

在我搞Android的时期,Context是属于每天都在烦恼的东西了,也是走了不少弯路,看了不少文章。现在搞鸿蒙了,嘻嘻嘻,也有Context。这是为啥也是带着好奇心,总结出一些经验。再次分享给大家,当然因为篇幅的原因,不会讲太细,和大家一起了解下~~~

如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,如果你想支持下一期请务必点赞~,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏

二、Context的重要性

在鸿蒙ArkUI开发中,Context是一个贯穿整个应用开发过程的核心概念。它不仅仅是应用上下文的容器,更是连接应用各个组件的桥梁,是应用能够正常运行的基石。

为什么Context如此重要?

想象一下,当你开发一个应用时,你需要:

  • 知道应用当前的状态(前台/后台)

  • 访问应用的资源文件

  • 获取文件存储路径

  • 监听系统事件

  • 启动其他应用或服务

所有这些操作都需要一个统一的入口点,而这个入口点就是Context。没有Context,应用就像失去了"身份证",无法证明自己的身份,也无法访问系统提供的各种能力。

二、Context的基本概念:应用上下文的基石 (PPT标题一样,诡异的错觉)

Context在ArkUI中是一个抽象概念,它代表了一个应用或组件运行时的环境信息集合。这个集合包含了应用的基本信息、配置参数、系统能力等,同时提供了操作这些信息的方法接口。

Context的本质

Context本质上是一个"信息中心",它:

  • 存储应用运行时的状态信息

  • 提供访问系统能力的接口

  • 管理应用的生命周期事件

  • 协调不同组件之间的交互

Context的分类体系

ArkUI的Context体系可以分为三个主要层次:

  • 配置层Context:负责解析应用配置,提供基础信息

  • 渲染层Context:处理UI渲染、事件调度等

  • 应用层Context:提供给开发者直接使用的高级接口

三、Context的体系结构:多层次的上下文管理

看图时间!

Untitled diagram _ Mermaid Chart-2025-08-14-151847

1、为什么需要分层?一个真实的问题场景

让我从一个实际的开发问题开始讲起。

假设你正在开发一个鸿蒙应用,这个应用需要:

  • 在启动时读取配置文件,了解自己的基本信息

  • 在UI渲染时处理用户交互事件

  • 在业务逻辑中访问系统资源

如果所有功能都放在一个Context类中,会发生什么?

// 糟糕的设计:所有功能混在一起
class BadContext {
public:
// 配置相关
void ParseConfig();
AppInfo GetAppInfo();

// UI相关
void HandleTouchEvent();
void RenderUI();

// 系统资源相关
void AccessFileSystem();
void RequestPermission();

// 结果:这个类变得臃肿、难以维护
// 而且不同模块的开发者在修改时容易相互影响
};

  • 职责混乱:一个类承担了太多不同的职责
  • 耦合严重:配置解析、UI渲染、资源访问混在一起
  • 难以测试:无法单独测试某个功能模块
  • 扩展困难:添加新功能时容易破坏现有代码

作为一个系统级的东西,出现任意一个都是一种灾难了,这就是为什么ArkUI需要分层架构的原因。

2、从问题到解决方案

让我们分析一下应用运行时的不同关注点:

配置关注点:应用启动时需要知道"我是谁"(包名、版本、权限等)

渲染关注点:UI需要知道"如何显示"(颜色模式、字体大小、动画等)

业务关注点:开发者需要知道"能做什么"(启动其他应用、访问文件等)

这些关注点天然就是分离的,它们有不同的生命周期、不同的变化频率、不同的使用场景。

3、设计分层策略

基于关注点分离,我们可以设计这样的分层:

应用层 Context (ApplicationContext, UIAbilityContext)
↓ 依赖
UI层 Context (UIContext, PipelineContext)
↓ 依赖
配置层 Context (FaContext, StageContext)

为什么这样分层?

  • 配置层最稳定:应用配置在安装后很少变化
  • UI层变化中等:UI状态会随用户操作变化,但相对可控
  • 应用层最活跃:业务逻辑经常变化,需要最大的灵活性

Untitled diagram _ Mermaid Chart-2025-08-15-091842

4、设计接口契约

分层之后,我们需要定义清晰的接口契约。让我用一个具体的例子来说明:

// 配置层:只关心"是什么"
class Context {
public:
virtual void Parse(const std::string& contents) = 0;
// 注意:这里没有UI相关的方法,也没有业务逻辑方法
};

// UI层:关心"如何显示"
class UIContext {
public:
virtual void RunScopeUITask(Task&& task) = 0;
virtual ColorMode GetColorMode() = 0;
// 注意:这里没有配置解析方法,也没有业务逻辑方法
};

// 应用层:关心"能做什么"
class ApplicationContext {
public:
virtual void setFontSizeScale(float scale) = 0;
virtual ResourceManager getResourceManager() = 0;
// 注意:这里没有UI渲染方法,也没有配置解析方法
};

接口设计的原则:

  • 每个接口只暴露必要的功能

  • 接口之间没有循环依赖

  • 高层接口可以组合低层接口,但不能直接访问低层实现

5、配置层的作用

配置层Context的核心作用是解析应用配置,提供基础信息。这里有一个关键的设计问题:如何让配置解析既灵活又高效?

// 配置层Context的设计
class Context {
public:
virtual void Parse(const std::string& contents) = 0;
static RefPtr<Context> CreateContext(bool isStage, const std::string& rootDir);

private:
Context() = default; // 构造函数私有化,强制使用工厂方法
};

// 具体实现
class StageContext : public Context {
public:
void Parse(const std::string& contents) override {
auto rootJson = JsonUtil::ParseJsonString(contents);
if (!rootJson || !rootJson>IsValid()) {
LOGW("The format of stage application config is illegal.");
return;
}

// 解析应用信息
appInfo_>Parse(rootJson>GetValue("app"));
// 解析模块信息
hapModuleInfo_>Parse(rootJson>GetValue("module"));
// 解析包信息
pkgContextInfo_>Parse(rootJson>GetValue("package"));
}

private:
RefPtr<StageAppInfo> appInfo_;
RefPtr<StageHapModuleInfo> hapModuleInfo_;
RefPtr<StagePkgContextInfo> pkgContextInfo_;
};

  • 抽象基类:定义统一的解析接口
  • 具体实现:根据不同的配置格式提供不同的解析逻辑
  • 工厂方法:封装对象创建逻辑,客户端不需要知道具体类

配置层Context解析的信息是有层次结构的:

// 应用信息:应用级别的基本信息
class StageAppInfo {
public:
std::string GetBundleName() const; // 包名
std::string GetVersionName() const; // 版本名称
uint32_t GetVersionCode() const; // 版本号
std::string GetVendor() const; // 厂商信息
uint32_t GetMinAPIVersion() const; // 最低API版本
uint32_t GetTargetAPIVersion() const; // 目标API版本
};

// 模块信息:模块级别的配置信息
class StageHapModuleInfo {
public:
std::string GetName() const; // 模块名称
std::string GetType() const; // 模块类型
std::string GetSrcEntry() const; // 入口文件
std::string GetDescription() const; // 模块描述
};

// 包信息:包级别的上下文信息
class StagePkgContextInfo {
public:
std::string GetName() const; // 包名称
std::string GetType() const; // 包类型
std::string GetMainElement() const; // 主元素
};

信息层次的意义:

  • 应用级信息:整个应用共享,相对稳定

  • 模块级信息:特定模块的配置,可能因模块而异

  • 包级信息:包的整体配置,影响整个包的行为

四、UI层Context

1、为什么UI需要专门的Context?

这里有一个更深层的问题:为什么UI操作需要专门的Context,而不是直接使用底层的Context?

让我用一个具体的例子来说明:

场景:用户点击按钮,需要改变页面颜色

// 错误的做法:直接在UI组件中操作 (伪代码!!!!)
Button().onClick(()=> {
// 问题1:这里如何知道当前的颜色模式?
// 问题2:如何确保颜色变化在整个UI中同步?
// 问题3:如何协调动画和渲染?

// 直接操作会导致:
// – 状态不一致
// – 性能问题
// – 难以调试
})

正确的做法:通过UIContext协调

(还是伪代码!!!!)
Button().onClick(()=> {
// 通过UIContext获取当前状态
auto uiContext = GetUIContext();
ColorMode currentMode = uiContext->GetColorMode();

// 通过UIContext调度UI任务
uiContext->RunScopeUITask([this, currentMode]() {
// 在正确的时机执行UI更新
UpdateColor(currentMode);
});

// 通过UIContext请求下一帧渲染
uiContext->RequestFrame();
})

UIContext的设计体现了两个重要的设计原则:

原则1:单一职责

class UIContext {
public:
// 只负责UI相关的操作
virtual void RunScopeUITask(Task&& task) = 0;
virtual void OnBackPressed() = 0;
virtual ColorMode GetColorMode() = 0;
virtual float GetFontScale() = 0;

// 不负责配置解析、文件访问等
// 这些功能由其他Context负责
};

原则2:代理模式

class UIContextImpl : public UIContext {
private:
PipelineContext* context_; // 持有对PipelineContext的引用

public:
void RunScopeUITask(Task&& task) override {
// 不是自己执行,而是委托给PipelineContext
context_->GetTaskExecutor()->PostTask(std::move(task));
}

ColorMode GetColorMode() override {
// 不是自己存储,而是从PipelineContext获取
return static_cast<ColorMode>(context_->GetColorMode());
}

float GetFontScale() override {
// 不是自己存储,而是从PipelineContext获取
return context_->GetFontScale();
}
};

为什么使用代理模式?

  • 职责分离:UIContext负责接口定义,PipelineContext负责具体实现
  • 生命周期管理:UIContext的生命周期可以独立于PipelineContext
  • 接口稳定性:UIContext的接口可以保持稳定,而PipelineContext可以频繁变化

2、任务调度的巧妙设计

UI任务调度是UIContext的核心功能,这里有一个关键问题:如何确保UI任务在正确的时机执行?

// 问题场景:多个UI操作同时发生
void HandleUserInteraction() {
// 用户快速点击了多个按钮
button1->OnClick(); // 改变颜色
button2->OnClick(); // 改变大小
button3->OnClick(); // 改变位置

// 问题:这些操作如何协调?
// 1. 是否应该合并?
// 2. 执行顺序如何保证?
// 3. 如何避免重复渲染?
}

// 解决方案:通过UIContext的任务调度
void HandleUserInteraction() {
auto uiContext = GetUIContext();

// 同步任务:立即执行
uiContext->RunScopeUITaskSync([this]() {
button1->OnClick();
});

// 异步任务:在下一帧执行
uiContext->RunScopeUITask([this]() {
button2->OnClick();
});

// 延迟任务:延迟执行
uiContext->RunScopeUIDelayedTask([this]() {
button3->OnClick();
}, 100); // 延迟100ms
}

任务调度的优势:

  • 性能优化:可以合并多个UI操作,减少重绘
  • 时序控制:精确控制UI操作的执行时机
  • 资源管理:避免UI操作阻塞主线程

3、PipelineContext:UI渲染的核心

PipelineContext是UI渲染管道的核心,它负责:

class PipelineContext {
public:
// 获取UIContext
RefPtr<Kit::UIContext> GetUIContext();

// 处理返回键事件
bool OnBackPressed();

// 获取实例ID
int32_t GetInstanceId();

// 获取任务执行器
const RefPtr<TaskExecutor>& GetTaskExecutor();

// 添加布局后任务
void AddAfterLayoutTask(Task&& task, bool isFlushInImplicitAnimationTask = false);

// 请求下一帧
void RequestFrame();

// 获取UI状态
int32_t GetLocalColorMode();
int32_t GetColorMode();
float GetFontScale();

// 获取容器模态框信息
void GetContainerModalButtonsRect(NG::RectF& containerModal, NG::RectF& buttonsRect);

// 生命周期回调管理
void RegisterArkUIObjectLifecycleCallback(ArkUIObjectLifecycleCallback&& callback);
void UnregisterArkUIObjectLifecycleCallback();
};

PipelineContext的核心作用:

  • 渲染协调:管理UI组件的渲染生命周期
  • 事件处理:协调用户交互事件的处理
  • 状态管理:维护UI的全局状态(颜色模式、字体大小等)
  • 任务调度:协调UI任务的执行时机

五、应用层Context

1、为什么需要应用层Context?

这里有一个用户体验的问题:如果开发者直接使用底层的Context,会发生什么?

// 问题:直接使用底层Context
class MyAbility {
void SomeFunction() {
// 需要手动处理很多细节
auto configContext = Context::CreateContext(true, "/data/app");
auto appInfo = configContext->GetAppInfo();

// 需要手动管理生命周期
// 需要手动处理错误
// 需要手动协调多个Context
}
};

解决方案

// 应用层Context:隐藏复杂性,提供简单接口
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 简单直接:一行代码获取应用信息
let appInfo = this.context.applicationInfo;

// 简单直接:一行代码设置字体大小
this.context.getApplicationContext().setFontSizeScale(1.2);

// 简单直接:一行代码监听应用状态
this.context.getApplicationContext().on('applicationStateChange', {
onApplicationForeground() { /* … */ },
onApplicationBackground() { /* … */ }
});
}
}

2、应用层Context的设计原则

原则1:隐藏复杂性

// 底层:需要多个步骤
let configContext = Context::CreateContext(true, "/data/app");
let appInfo = configContext>GetAppInfo();
let bundleName = appInfo>GetBundleName();

// 应用层:一步到位
let bundleName = this.context.applicationInfo.bundleName;

原则2:提供语义化的接口

// 底层:技术性接口
context>GetValue("app")>GetValue("bundleName")>GetString();

// 应用层:业务性接口
context.applicationInfo.bundleName
context.applicationInfo.versionName
context.applicationInfo.vendor

原则3:自动管理生命周期
  • 应用层Context自动处理
  • 配置文件的读取和解析
  • 资源的加载和释放
  • 生命周期的管理
  • 错误的处理和恢复
  • 开发者只需要关注业务逻辑

3、应用层Context的具体实现

应用层Context主要包括以下几种类型:

ApplicationContext:应用全局上下文

// 获取ApplicationContext
let applicationContext = this.context.getApplicationContext();

// 主要功能
applicationContext.setFontSizeScale(1.2); // 设置字体缩放
applicationContext.setColorMode(ColorMode.COLOR_MODE_DARK); // 设置颜色模式
applicationContext.setLanguage('zh-CN'); // 设置语言

// 获取应用信息
let appInfo = applicationContext.applicationInfo;
let resourceManager = applicationContext.resourceManager;

// 获取文件路径
let cacheDir = applicationContext.cacheDir;
let filesDir = applicationContext.filesDir;
let databaseDir = applicationContext.databaseDir;

// 监听应用状态
applicationContext.on('applicationStateChange', {
onApplicationForeground() { /* 应用切换到前台 */ },
onApplicationBackground() { /* 应用切换到后台 */ }
});

UIAbilityContext:UIAbility组件上下文

// 直接获取
let context = this.context;

// 主要功能
let abilityInfo = context.abilityInfo; // 获取Ability信息
let moduleInfo = context.currentHapModuleInfo; // 获取模块信息

// 启动其他Ability
context.startAbility(want).then(() => {
console.log('启动成功');
}).catch((error) => {
console.error('启动失败:', error);
});

// 终止当前Ability
context.terminateSelf().then(() => {
console.log('终止成功');
});

ExtensionContext:扩展能力上下文

// 在ExtensionAbility中获取
export default class MyFormExtensionAbility extends FormExtensionAbility {
onAddForm(want: Want) {
let formExtensionContext = this.context;
let extensionInfo = formExtensionContext.extensionInfo;

// 创建表单数据
let dataObj = { 'temperature': '22°C', 'time': '14:30' };
let formBindingData = formBindingData.createFormBindingData(dataObj);
return formBindingData;
}
}

4、分层架构的深层思考

让我们深入思考一下分层架构中的依赖关系:

// 正确的依赖方向
应用层Context → UI层Context → 配置层Context

// 错误的依赖方向(会导致循环依赖)
配置层Context → UI层Context → 应用层Context

为什么这样设计?

  • 稳定性原则:底层Context更稳定,变化频率低
  • 抽象层次:高层Context提供更抽象的概念,低层Context提供具体实现
  • 测试友好:可以独立测试每一层,不需要模拟整个系统
扩展性的考虑

分层架构的一个重要优势是易于扩展。让我用一个具体的例子来说明:

// 现有架构
class UIContext {
virtual void RunScopeUITask(Task&& task) = 0;
virtual ColorMode GetColorMode() = 0;
};

// 扩展:添加新的UI功能
class UIContext {
virtual void RunScopeUITask(Task&& task) = 0;
virtual ColorMode GetColorMode() = 0;

// 新增:支持手势识别
virtual void RegisterGestureRecognizer(GestureRecognizer* recognizer) = 0;
virtual void UnregisterGestureRecognizer(GestureRecognizer* recognizer) = 0;

// 新增:支持无障碍功能
virtual void SetAccessibilityEnabled(bool enabled) = 0;
virtual bool IsAccessibilityEnabled() const = 0;
};

扩展的优势:

  • 不影响现有代码:新功能完全独立
  • 易于测试:可以单独测试新功能
  • 向后兼容:现有应用继续正常工作

实际应用中的体现:

  • 开发者使用this.context.applicationInfo.bundleName时,不需要知道底层是如何解析配置文件的

  • 开发者调用this.context.startAbility(want)时,不需要知道底层是如何协调UI和系统的

  • 开发者设置applicationContext.setFontSizeScale(1.2)时,不需要知道底层是如何同步整个UI的

六、小思考:getContext传入this和不传入的区别

在深入理解Context体系后,让我们来思考一个看似简单但实际很有深意的问题:getContext(this) 和 getContext() 有什么区别?

啊,累了,写了好多了。本来想扩展下getContext以及getHostContext的。留给大家思考吧。今天就到这!当然你也可以催更~

七、总结

看这 ——————–>[如果有想加入鸿蒙生态的大佬们,快来加入鸿蒙认证吧!初高级证书没获取的,点我!!!!!!!!,我真的很需要求求了!]<——————–看这

加我好友进群沟通有HDE在线答疑 file

没了。

如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,如果你想支持下一期请务必点赞~,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏

赞(0)
未经允许不得转载:网硕互联帮助中心 » HarmonyOS5 凭什么学鸿蒙—— Context详解
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!