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

猫猫头啊
发布于 2025-9-4 17:19
浏览
0收藏

一、引言

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

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

二、Context的重要性

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

为什么Context如此重要?

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

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

  • 访问应用的资源文件

  • 获取文件存储路径

  • 监听系统事件

  • 启动其他应用或服务

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

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

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

Context的本质

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

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

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

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

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

Context的分类体系

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

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

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

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

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

看图时间!

HarmonyOS5 凭什么学鸿蒙—— Context详解-鸿蒙开发者社区

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状态会随用户操作变化,但相对可控
  • 应用层最活跃:业务逻辑经常变化,需要最大的灵活性

HarmonyOS5 凭什么学鸿蒙—— Context详解-鸿蒙开发者社区

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

七、总结

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

没了。

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

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐