在鸿蒙(HarmonyOS)原生应用开发中,随着业务复杂度的提升,组件间的依赖关系会变得错综复杂。传统的硬编码依赖方式会导致代码耦合度高、难以测试和维护。依赖注入(Dependency Injection, DI)通过控制反转(IoC)机制,将对象的创建和依赖关系的管理交由专门的容器来处理,是解决这些问题的最佳实践。

在鸿蒙 ArkTS 中,我们可以利用装饰器(Decorators)和反射(Reflect)机制,实现一个轻量级的依赖注入框架。

一、 核心概念与架构设计

一个基础的 DI 框架主要包含三个核心部分:

  1. 服务标识(Service):用于标记哪些类是“可被注入的服务”。
  2. 注入标识(Inject):用于标记哪些属性需要由容器自动注入。
  3. IoC 容器(Container):负责管理服务的注册、生命周期(如单例、瞬时)以及自动解析依赖关系。

一个“用户资料展示”的实战场景

1. IoC 容器(Container):全局的“服务管家”

容器是整个 DI 框架的核心,负责记录“谁提供了什么服务”以及“如何创建服务”。

class DIContainer {
  private services: Map<string, any> = new Map();

  // 1. 注册服务(将接口标识与具体的类或实例绑定)
  register(identifier: string, service: any): void {
    this.services.set(identifier, service);
  }

  // 2. 解析服务(根据标识获取具体的服务实例)
  resolve<T>(identifier: string): T {
    const service = this.services.get(identifier);
    if (!service) {
      throw new Error(`Service [${identifier}] 未注册!`);
    }
    return service as T;
  }
}

// 导出一个全局单例容器,供整个应用使用
export const container = new DIContainer();

2. 服务标识(Service):标记“可被注入的能力”

通常会定义一个接口或抽象类作为标识,并让具体的服务类去实现它。

// 1. 定义服务标识(接口)
interface IStorageService {
  getData(key: string): string;
  saveData(key: string, value: string): void;
}

// 2. 具体的服务实现
class LocalStorageService implements IStorageService {
  getData(key: string): string {
    return `从本地获取的 ${key}`;
  }
  saveData(key: string, value: string): void {
    console.log(`已保存到本地: ${key} = ${value}`);
  }
}

// 3. 在应用启动时,向容器注册服务
container.register<IStorageService>('IStorageService', new LocalStorageService());

3. 注入标识(Inject):在业务层“自动获取依赖”

在 ViewModel 或 UI 组件中,我们不再手动 new 具体的服务类,而是通过容器获取。

class UserViewModel {
  // 传统方式(高耦合):
  // private storage = new LocalStorageService(); 

  // DI 方式(低耦合):通过容器解析获取
  private storage: IStorageService = container.resolve<IStorageService>('IStorageService');

  loadUserProfile() {
    // 业务层只关心接口,不关心底层是本地存储还是网络存储
    return this.storage.getData('user_profile');
  }
}

完整串联:在鸿蒙 View 层中的使用

@Component
struct UserPage {
  @State profileInfo: string = '加载中...';
  private viewModel: UserViewModel = new UserViewModel();

  aboutToAppear() {
    // 调用 ViewModel 的方法,ViewModel 内部自动使用了注入的 Storage 服务
    this.profileInfo = this.viewModel.loadUserProfile();
  }

  build() {
    Column({ space: 20 }) {
      Text(this.profileInfo).fontSize(20)
      Button('切换为网络存储')
        .onClick(() => {
          // 动态替换服务实现(依赖倒置的威力)
          container.register<IStorageService>('IStorageService', new NetworkStorageService());
          this.profileInfo = this.viewModel.loadUserProfile();
        })
    }
  }
}

二、 轻量级 DI 框架代码实现

// 1. 服务标识装饰器:将类标记为服务,并可选地指定一个唯一标识符
function Service(identifier?: string): ClassDecorator {
  return function (target: any) {
    Reflect.defineMetadata('di:service', true, target);
    if (identifier) {
      Reflect.defineMetadata('di:identifier', identifier, target);
    }
  };
}

// 2. 注入装饰器:标记需要注入的属性,并记录其依赖的类型或标识符
function Inject(identifier?: string): PropertyDecorator {
  return function (target: any, propertyKey: string | symbol) {
    const serviceIdentifier = identifier || Reflect.getMetadata('design:type', target, propertyKey);
    Reflect.defineMetadata('di:inject', serviceIdentifier, target, propertyKey);
  };
}

// 3. IoC 容器类:负责注册和解析服务
class DIContainer {
  private static instance: DIContainer;
  private services: Map<string, any> = new Map();

  // 获取全局单例容器
  static getInstance(): DIContainer {
    if (!DIContainer.instance) {
      DIContainer.instance = new DIContainer();
    }
    return DIContainer.instance;
  }

  // 注册服务实例
  register<T>(identifier: string, service: T): void {
    this.services.set(identifier, service);
  }

  // 解析并获取服务实例
  resolve<T>(identifier: string): T {
    const service = this.services.get(identifier);
    if (!service) {
      throw new Error(`Service ${identifier} not found`);
    }
    return service;
  }

  // 自动注册:扫描带有 @Service 装饰器的类并实例化
  autoRegister(constructor: any): void {
    const isService = Reflect.getMetadata('di:service', constructor);
    if (isService) {
      const identifier = Reflect.getMetadata('di:identifier', constructor) || constructor.name;
      this.register(identifier, new constructor());
    }
  }
}

三、 业务层实战示例

结合之前的 MVVM 架构,DI 框架可以完美地应用于服务层和 ViewModel 层:

// 1. 定义服务层:使用 @Service 标记
@Service('UserService')
class UserService {
  private users: Map<string, any> = new Map();

  addUser(user: any): void {
    this.users.set(user.id, user);
  }

  getUser(id: string): any {
    return this.users.get(id);
  }
}

// 2. 定义 ViewModel:使用 @Inject 注入依赖
@Service('UserViewModel')
class UserViewModel {
  // 容器会自动将 UserService 注入到该属性中
  @Inject('UserService')
  private userService!: UserService; 

  loadUser(id: string) {
    return this.userService.getUser(id);
  }
}

// 3. 在 UI 组件中使用
@Component
struct UserProfile {
  @Inject('UserService')
  private userService!: UserService;

  @State userInfo: any = null;

  aboutToAppear(): void {
    // 从容器中获取服务并加载数据
    this.userInfo = this.userService.getUser('user123');
  }

  build() {
    Column() {
      Text(this.userInfo ? `用户名: ${this.userInfo.name}` : '加载中...')
    }
  }
}

四、 生命周期与最佳实践

在实际的鸿蒙工程中,DI 框架的使用需要遵循以下规范:

  1. 分层架构设计:将 UI 组件、ViewModel、数据服务严格分层,通过 DI 容器进行连接,确保各层职责单一。
  2. 生命周期管理策略
    • Singleton(单例):适用于全局配置、数据库连接、工具类。容器内缓存实例,整个应用生命周期内共享。
    • Transient(瞬时):适用于每次请求都需要新实例的场景。每次调用 resolve 时创建新实例。
    • Scoped(作用域):适用于请求上下文或特定页面。在作用域内共享实例,页面销毁时自动释放资源。
  3. 配置集中管理:可以将 API 基础 URL、超时时间等配置封装为 AppConfig 服务,通过 DI 注入到网络请求服务中,避免硬编码。
  4. 延迟注入与缓存:避免在应用启动时(如 Application.onCreate())一次性注入所有依赖。采用懒加载策略,并对频繁使用的依赖进行缓存,以提升应用启动性能。

五、 进阶扩展

跨平台与模块化 DI 方案

如果你的鸿蒙项目是基于 Flutter for OpenHarmony 开发,或者需要处理多 HAP(Feature)模块的复杂工程,可以考虑使用成熟的第三方 DI 库:

  • weaver:一个极致轻量的 DI 和服务发现框架。它提出了“注册(Register)- 注入(Inject)”模型,支持局部作用域容器(weaver.scoped),非常适合在鸿蒙多 HAP 模式下实现模块自治和强弱隔离。
  • injector / flutter_simple_dependency_injection:纯 Dart 库,100% 兼容 OpenHarmony。支持通过工厂模式(Factory)和单例模式(Singleton)管理依赖,非常适合用来屏蔽鸿蒙与 Android/iOS 的底层 API 差异,实现一套代码多端运行。
1、 拥抱自动化:使用 injectable 与 inject_generator

在大型工程中,手动维护注册和解析逻辑不仅繁琐,还极易出错。引入代码生成器可以将开发者从繁琐的依赖树组装中解放出来。

  • 核心机制:利用 @module@provide 等注解标记依赖,通过 build_runner 在编译期自动扫描并生成 *.inject.dart 文件,构建好单例或工厂的实例化逻辑。
  • 鸿蒙适配优势:由于生成的代码是标准的 Dart 实例化调用,不涉及运行时反射,因此不会带来额外的性能损耗,且能在编译期提前发现依赖链条断裂或环形依赖的问题。

1. 编写注解:定义模块与依赖

在业务代码中,我们只需通过 @module 和 @provide 等注解告诉生成器“谁是谁的依赖”。

// 1. 定义一个鸿蒙模块,提供底层数据库服务
@module
class OhosMainModule {
  @provide
  OhosDatabase provideDb() => OhosDatabase();
}

// 2. 定义业务服务,声明它依赖 OhosDatabase
@injectable
class OhosUserService {
  final OhosDatabase _db;
  
  // 构造函数中的参数,生成器会自动寻找并注入
  OhosUserService(this._db); 
  
  void fetchUser() => print('通过数据库获取用户');
}

2. 运行生成器:触发自动化组装

在终端中执行 build_runner 命令:

dart run build_runner build

此时,生成器会在后台自动分析 OhosUserService 与 OhosMainModule 之间的依赖树,并在同级目录下生成一份名为 *.inject.dart(或 *.g.dart)的文件。在这个生成的文件中,机器已经为你写好了类似如下的标准 Dart 实例化代码:

// 自动生成的代码(开发者无需手写)
OhosUserService createOhosUserService() {
  final db = OhosDatabase(); // 自动实例化底层依赖
  return OhosUserService(db); // 自动组装并返回完整对象
}

3. 业务层使用:零手动配置的解耦体验

在鸿蒙应用的入口或页面中,你只需要调用自动生成的容器,即可获取组装好的服务:

void main() async {
  // 1. 初始化鸿蒙注入容器(底层会自动调用生成器组装好的逻辑)
  final container = await OhosAppComponent.create(OhosMainModule());
  
  // 2. 直接获取服务,无需关心 OhosUserService 是如何被 new 出来的
  runApp(MyApp(userService: container.userService));
}
2、 多环境隔离与动态适配(Environments)

鸿蒙应用通常面临真机调试、模拟器运行、自动化测试(ohos_test)等多种环境。DI 框架应支持根据环境动态注入不同的实现:

  • 环境标签:利用 @Environment('ohos_real') 或 @Environment('dev') 为同一个接口定义多份实现。
  • 实战场景:在开发环境下注入 MockStorage(内存缓存),而在鸿蒙真机环境下自动注入 NativeOhosStorage(鸿蒙原生持久化存储)。业务层无需修改任何代码,即可实现跨环境的无缝切换。
3、 异步服务的先行预解析(Asynchronous Init)

鸿蒙系统中许多底层 API(如 Preferences.getInstance()、设备传感器初始化)都是异步的。如果在 DI 容器就绪前就尝试获取这些服务,会导致运行时异常。

  • 解决方案:使用 @preResolve 注解。它会强制 DI 容器在初始化阶段等待异步服务完成解析,确保后续业务逻辑在调用 getIt<T>() 时,服务已完全就绪。
@module
abstract class OhosPlatformModule {
  // 使用 @preResolve 标记异步初始化服务
  @preResolve
  Future<SharedPreferences> get prefs => SharedPreferences.getInstance();
}
4、 防御循环依赖与内存泄漏

在复杂的模块化架构中,A 依赖 B、B 又依赖 A 的“炸弹式堆栈溢出”是常见的崩溃诱因。

  • 延迟初始化(Lazy Fetch):在业务逻辑内部才调用 df.get() 或 weaver.get(),不要在构造函数的第一行就触发全量寻找,将依赖解析的时间点向后推移。
  • 内存防御:对于持有大体积数据(如鸿蒙媒体缓存、大规模 JSON 字典)的 Service,应谨慎使用全局单例。建议在模块卸载或页面销毁时,手动触发容器的 unregister 或配合 weaver.scoped 局部作用域,确保依赖资源能被及时释放。
5、 多 Isolate 间的“注入真空”处理

由于鸿蒙的隔离空间(Isolate)不共享内存,UI 线程注册的服务在后台计算线程中是拿不到的。

  • 独立注入空间:在每一个新创建的 Isolate 起始处,重新执行一次轻量级的注册逻辑。利用现代 DI 框架(如 df_di)的极速启动特性,这种重复注册的性能损耗可以忽略不计。
  • 参数序列化透传:如果必须跨线程共享某些服务状态,建议将对象关键参数序列化后通过消息队列(如 tw_queue)进行跨线程传递。
6、 分布式场景下的状态对齐

在鸿蒙“万物互联”的分布式场景下,多个设备副本可能同时运行着 DI 容器。如果某个服务需要在设备间保持状态一致(如全局配置、分布式数据对象),建议在 DI 的实现类内部调用鸿蒙原生的分布式数据对象(Distributed Data Object)接口,实现“全局单例”在不同设备间的状态自动对齐。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐