HarmonyOS 上下文的使用: 脱离 UI 怎么用 Context?
本文探讨了在鸿蒙开发中,如何在纯逻辑代码中优雅使用Context。首先分析了工具类无法直接获取Context的痛点,随后提出三种解决方案:参数传递法(推荐)、全局单例法(仅限ApplicationContext)和事件总线方案(解决UI弹窗问题)。文章强调三个原则:优先参数传递、全局单例只存ApplicationContext、避免非UI层直接弹窗。最后介绍了官方认证培训资源,为开发者提供完整解决
大家好,我是不想掉发的鸿蒙开发工程师 城中的雾,经过前三期的交流,在 Page 和 Ability 里玩转 Context 应该没问题了,但开发过程中很多开发者朋友在封装工具类时,经常会遇到这种尴尬:
- “我就想在
HttpUtils捕获异常时弹个 Toast,结果发现找不到promptAction。” - “我就想在
LogUtils里拿个filesDir写日志,结果没有 context 没法调 API。”
这一期,我们跳出 UI 的舒适区,聊聊在 纯逻辑代码(架构层) 中如何优雅地使用 Context。
1. 痛点:为什么 Utils 里没有 Context?
在鸿蒙(ArkTS)中,Context 是系统运行环境的引用。它必须依附于某个具体的组件(Ability 或 UI)才能存在。
当你创建一个普通的 .ets 或 .ts 文件时,没有办法直接获取到上下文。
错误示范:
// HttpUtil.ets
export class HttpUtil {
static request(url: string) {
// ❌ 报错:这里是静态方法,哪来的 this.context?
// 也不能 new Context(),因为 Context 是系统创建的
// this.context.filesDir
}
}
我们要解决的,就是如何把 Context 运送到工具类里去。
2. 方案 A:参数传递法
这是最朴素、也是最推荐的方法。谁调用工具类,谁就负责把 Context 传进来。
场景:获取资源字符串
我们封装一个工具函数,用于读取 resources 目录下的 JSON 或字符串。
工具类写法:
// ResUtils.ets
import { common } from '@kit.AbilityKit';
export class ResUtils {
// 显式要求传入 Context
static getString(context: common.Context, resId: number): string {
// 即使传入的是 AbilityContext 或 UIContext,都能用 resourceManager
return context.resourceManager.getStringSync(resId);
}
}
调用方写法:
// Index.ets
@Entry
@Component
struct Index {
build() {
Button('读取文字')
.onClick(() => {
// 把当前的 Context 传过去
let str = ResUtils.getString(getContext(this), $r('app.string.test_str').id);
})
}
}
- 优点:内存安全,生命周期清晰,不会持有过期的 Context。
- 缺点:每个方法都要多传一个参数,写起来有点累(参数透传地狱)。
3. 方案 B:全局单例法
如果你觉得每次传参太麻烦,而且你需要的是 ApplicationContext(比如写日志、存首选项,这些操作不需要依赖特定 UI),那么可以搞一个全局单例来持有它。
警告:
不要在全局单例中持有 AbilityContext 或 UIContext!会导致严重的内存泄漏。
只能持有 ApplicationContext。
第一步:封装单例类
// GlobalContext.ets
import { common } from '@kit.AbilityKit';
export class GlobalContext {
private static instance: GlobalContext;
private _appContext: common.ApplicationContext | undefined;
private constructor() {}
public static get(): GlobalContext {
if (!GlobalContext.instance) {
GlobalContext.instance = new GlobalContext();
}
return GlobalContext.instance;
}
// 初始化方法
public init(context: common.Context) {
this._appContext = context.getApplicationContext();
}
// 获取 ApplicationContext
public getAppContext(): common.ApplicationContext {
if (!this._appContext) {
throw new Error("GlobalContext not initialized! Call init() in EntryAbility.");
}
return this._appContext;
}
}
第二步:在入口初始化
在 EntryAbility.ets 的 onCreate 中尽早初始化。
// EntryAbility.ets
import { GlobalContext } from '../utils/GlobalContext';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 初始化全局上下文
GlobalContext.get().init(this.context);
}
}
第三步:随处使用
现在,你可以在任何 .ets 文件中使用了。
// LogUtils.ets
import { GlobalContext } from './GlobalContext';
export function writeLog(msg: string) {
// 不需要传参,直接拿
let context = GlobalContext.get().getAppContext();
let dir = context.filesDir;
// ... 写入文件逻辑 ...
}
4. 终极难题:如何在 Utils 里弹窗?
这是架构篇最大的坑。
很多同学想在 HttpUtil 的拦截器里写:
if (code == 401) showToast(“请登录”)
尝试 1:用 GlobalContext 弹窗?(失败)
// 错误
GlobalContext.get().getAppContext().getPromptAction() // 报错!ApplicationContext 没有界面能力
尝试 2:传 UIContext 进去?(可行但麻烦)
需要把 uiContext 一路传到网络请求层,代码侵入性太强。
推荐方案:事件总线 (EventHub)
利用我们在第一期讲过的 EventHub,让 Utils 发送“信号”,让 UI 层自己去弹窗。这样既解耦,又安全。
工具类 (HttpUtil.ets):
import { GlobalContext } from './GlobalContext';
function handleByInterceptor(error: Error) {
// 1. 获取全局上下文
let appCtx = GlobalContext.get().getAppContext();
// 2. 发送一个全局事件,比如 "SHOW_TOAST"
appCtx.eventHub.emit('SHOW_TOAST', '网络连接超时');
}
基类 UI (BasePage.ets 或 EntryAbility):
在应用的主界面或者 Ability 中监听这个事件。
// Index.ets
aboutToAppear() {
let appCtx = getContext(this).getApplicationContext();
// 监听全局弹窗事件
appCtx.eventHub.on('SHOW_TOAST', (msg: string) => {
// 这里是在 UI 内部,可以安全地使用 UIContext 弹窗
this.getUIContext().getPromptAction().showToast({ message: msg });
});
}
这样,HttpUtil 只需要负责通知,具体怎么弹窗、在哪弹窗,交给 UI 层去处理。
5. 总结
- 原则一:尽量使用 参数传递。虽然麻烦,但最干净,不会有副作用。
- 原则二:如果必须使用全局单例,只能存
ApplicationContext。存AbilityContext就是在给内存泄漏埋雷。 - 原则三:非 UI 不弹窗。不要试图在 Utils 里强行持有
UIContext来弹窗。请使用 EventHub 发送事件,或者返回错误码让 UI 层处理。
全系列结语:
至此,《HarmonyOS 上下文》系列就告一段落了。
📚 充电时间
如果有想加入鸿蒙生态的大佬们,快来加入鸿蒙认证吧!初高级证书还没获取的,点这里:
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信中提出,非常感谢您的支持。
更多推荐



所有评论(0)