一、引言
在我搞Android的时期,Context是属于每天都在烦恼的东西了,也是走了不少弯路,看了不少文章。现在搞鸿蒙了,嘻嘻嘻,也有Context。这是为啥也是带着好奇心,总结出一些经验。再次分享给大家,当然因为篇幅的原因,不会讲太细,和大家一起了解下~~~
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,如果你想支持下一期请务必点赞~,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏
二、Context的重要性
在鸿蒙ArkUI开发中,Context是一个贯穿整个应用开发过程的核心概念。它不仅仅是应用上下文的容器,更是连接应用各个组件的桥梁,是应用能够正常运行的基石。
为什么Context如此重要?
想象一下,当你开发一个应用时,你需要:
-
知道应用当前的状态(前台/后台)
-
访问应用的资源文件
-
获取文件存储路径
-
监听系统事件
-
启动其他应用或服务
所有这些操作都需要一个统一的入口点,而这个入口点就是Context。没有Context,应用就像失去了"身份证",无法证明自己的身份,也无法访问系统提供的各种能力。
二、Context的基本概念:应用上下文的基石 (PPT标题一样,诡异的错觉)
Context在ArkUI中是一个抽象概念,它代表了一个应用或组件运行时的环境信息集合。这个集合包含了应用的基本信息、配置参数、系统能力等,同时提供了操作这些信息的方法接口。
Context的本质
Context本质上是一个"信息中心",它:
-
存储应用运行时的状态信息
-
提供访问系统能力的接口
-
管理应用的生命周期事件
-
协调不同组件之间的交互
Context的分类体系
ArkUI的Context体系可以分为三个主要层次:
-
配置层Context:负责解析应用配置,提供基础信息
-
渲染层Context:处理UI渲染、事件调度等
-
应用层Context:提供给开发者直接使用的高级接口
三、Context的体系结构:多层次的上下文管理
看图时间!

1、为什么需要分层?一个真实的问题场景
让我从一个实际的开发问题开始讲起。
假设你正在开发一个鸿蒙应用,这个应用需要:
-
在启动时读取配置文件,了解自己的基本信息
-
在UI渲染时处理用户交互事件
-
在业务逻辑中访问系统资源
如果所有功能都放在一个Context类中,会发生什么?
cpp
// 糟糕的设计:所有功能混在一起
class BadContext {
public:
// 配置相关
void ParseConfig();
AppInfo GetAppInfo();
// UI相关
void HandleTouchEvent();
void RenderUI();
// 系统资源相关
void AccessFileSystem();
void RequestPermission();
// 结果:这个类变得臃肿、难以维护
// 而且不同模块的开发者在修改时容易相互影响
};
- 职责混乱:一个类承担了太多不同的职责
- 耦合严重:配置解析、UI渲染、资源访问混在一起
- 难以测试:无法单独测试某个功能模块
- 扩展困难:添加新功能时容易破坏现有代码
作为一个系统级的东西,出现任意一个都是一种灾难了,这就是为什么ArkUI需要分层架构的原因。
2、从问题到解决方案
让我们分析一下应用运行时的不同关注点:
配置关注点:应用启动时需要知道"我是谁"(包名、版本、权限等)
渲染关注点:UI需要知道"如何显示"(颜色模式、字体大小、动画等)
业务关注点:开发者需要知道"能做什么"(启动其他应用、访问文件等)
这些关注点天然就是分离的,它们有不同的生命周期、不同的变化频率、不同的使用场景。
3、设计分层策略
基于关注点分离,我们可以设计这样的分层:
apl
应用层 Context (ApplicationContext, UIAbilityContext)
↓ 依赖
UI层 Context (UIContext, PipelineContext)
↓ 依赖
配置层 Context (FaContext, StageContext)
为什么这样分层?
- 配置层最稳定:应用配置在安装后很少变化
- UI层变化中等:UI状态会随用户操作变化,但相对可控
- 应用层最活跃:业务逻辑经常变化,需要最大的灵活性

4、设计接口契约
分层之后,我们需要定义清晰的接口契约。让我用一个具体的例子来说明:
cpp
// 配置层:只关心"是什么"
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的核心作用是解析应用配置,提供基础信息。这里有一个关键的设计问题:如何让配置解析既灵活又高效?
json
// 配置层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解析的信息是有层次结构的:
cpp
// 应用信息:应用级别的基本信息
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?
让我用一个具体的例子来说明:
场景:用户点击按钮,需要改变页面颜色
cpp
// 错误的做法:直接在UI组件中操作 (伪代码!!!!)
Button().onClick(()=> {
// 问题1:这里如何知道当前的颜色模式?
// 问题2:如何确保颜色变化在整个UI中同步?
// 问题3:如何协调动画和渲染?
// 直接操作会导致:
// - 状态不一致
// - 性能问题
// - 难以调试
})
正确的做法:通过UIContext协调
cpp
(还是伪代码!!!!)
Button().onClick(()=> {
// 通过UIContext获取当前状态
auto uiContext = GetUIContext();
ColorMode currentMode = uiContext->GetColorMode();
// 通过UIContext调度UI任务
uiContext->RunScopeUITask([this, currentMode]() {
// 在正确的时机执行UI更新
UpdateColor(currentMode);
});
// 通过UIContext请求下一帧渲染
uiContext->RequestFrame();
})
UIContext的设计体现了两个重要的设计原则:
原则1:单一职责
cpp
class UIContext {
public:
// 只负责UI相关的操作
virtual void RunScopeUITask(Task&& task) = 0;
virtual void OnBackPressed() = 0;
virtual ColorMode GetColorMode() = 0;
virtual float GetFontScale() = 0;
// 不负责配置解析、文件访问等
// 这些功能由其他Context负责
};
原则2:代理模式
cpp
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任务在正确的时机执行?
cpp
// 问题场景:多个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渲染管道的核心,它负责:
cpp
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,会发生什么?
cpp
// 问题:直接使用底层Context
class MyAbility {
void SomeFunction() {
// 需要手动处理很多细节
auto configContext = Context::CreateContext(true, "/data/app");
auto appInfo = configContext->GetAppInfo();
// 需要手动管理生命周期
// 需要手动处理错误
// 需要手动协调多个Context
}
};
解决方案
ts
// 应用层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:隐藏复杂性
ts
// 底层:需要多个步骤
let configContext = Context::CreateContext(true, "/data/app");
let appInfo = configContext->GetAppInfo();
let bundleName = appInfo->GetBundleName();
// 应用层:一步到位
let bundleName = this.context.applicationInfo.bundleName;
原则2:提供语义化的接口
ts
// 底层:技术性接口
context->GetValue("app")->GetValue("bundleName")->GetString();
// 应用层:业务性接口
context.applicationInfo.bundleName
context.applicationInfo.versionName
context.applicationInfo.vendor
原则3:自动管理生命周期
- 应用层Context自动处理
- 配置文件的读取和解析
- 资源的加载和释放
- 生命周期的管理
- 错误的处理和恢复
- 开发者只需要关注业务逻辑
3、应用层Context的具体实现
应用层Context主要包括以下几种类型:
ApplicationContext:应用全局上下文
ts
// 获取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组件上下文
ts
// 直接获取
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:扩展能力上下文
scala
// 在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、分层架构的深层思考
让我们深入思考一下分层架构中的依赖关系:
ts
// 正确的依赖方向
应用层Context → UI层Context → 配置层Context
// 错误的依赖方向(会导致循环依赖)
配置层Context → UI层Context → 应用层Context
为什么这样设计?
- 稳定性原则:底层Context更稳定,变化频率低
- 抽象层次:高层Context提供更抽象的概念,低层Context提供具体实现
- 测试友好:可以独立测试每一层,不需要模拟整个系统
扩展性的考虑
分层架构的一个重要优势是易于扩展。让我用一个具体的例子来说明:
cpp
// 现有架构
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的。留给大家思考吧。今天就到这!当然你也可以催更~
七、总结
看这 -------------------->[如果有想加入鸿蒙生态的大佬们,快来加入鸿蒙认证吧!初高级证书没获取的,点我!!!!!!!!,我真的很需要求求了!]<--------------------看这
没了。
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,如果你想支持下一期请务必点赞~,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏