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的体系结构:多层次的上下文管理

看图时间!

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的。留给大家思考吧。今天就到这!当然你也可以催更~

七、总结

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

没了。

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

相关推荐
前端世界30 分钟前
鸿蒙任务调度机制深度解析:优先级、时间片、多核与分布式的流畅秘密
分布式·华为·harmonyos
小小小小小星4 小时前
鸿蒙开发之ArkUI框架进阶:从声明式范式到跨端实战
harmonyos·arkui
鸿蒙小灰4 小时前
鸿蒙开发对象字面量类型标注的问题
harmonyos
鸿蒙先行者4 小时前
鸿蒙Next不再兼容安卓APK,开发者该如何应对?
harmonyos
YF云飞6 小时前
.NET 在鸿蒙系统(HarmonyOS Next)上的适配探索与实践
华为·.net·harmonyos
Quarkn10 小时前
鸿蒙原生应用ArkUI之自定义List下拉刷新动效
list·harmonyos·arkts·鸿蒙·arkui
whysqwhw19 小时前
鸿蒙音频播放方式总结
harmonyos
whysqwhw19 小时前
鸿蒙音频录制方式总结
harmonyos
zhanshuo1 天前
HarmonyOS 实战:用 @Observed + @ObjectLink 玩转多组件实时数据更新
harmonyos