Flutter 三方库 get_it 的鸿蒙化适配实践

欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

引言

在 Flutter 跨平台应用开发中,依赖注入(Dependency Injection,简称 DI)和控制反转(Inversion of Control,简称 IoC)是构建可维护、可测试代码的重要设计模式。随着 OpenHarmony 平台逐渐走入开发者的视野,如何将 Flutter 生态中成熟的依赖注入方案迁移到这一新兴平台,成为许多开发者关注的问题。

本文将详细介绍如何将 get_it 服务定位器库成功适配到 OpenHarmony 平台。get_it 是 Flutter 生态中轻量级且功能强大的服务定位器实现,其纯 Dart 语言实现的特性使其具备天然的跨平台兼容性。我们将从一个实际项目出发,完整展示 get_it 在 OH 环境下的集成过程、验证方法以及应用生命周期管理的最佳实践。

本文面向具有一定 Flutter 开发经验的读者,需要读者熟悉 Dart 语言基础、Flutter 应用的构建流程以及基本的面向对象设计原则。对于 OpenHarmony 平台的环境配置,假设读者已经具备 Flutter for OpenHarmony 开发环境,如需相关资料可参考社区中的环境配置指南。

一、问题的提出:为什么需要服务定位器

1.1 传统手动依赖创建的问题

在传统的 Flutter 开发模式中,服务类通常在需要的地方直接实例化。考虑一个典型的网络请求服务场景:

class TodoProvider extends ChangeNotifier {
  final TodoService _todoService = TodoService();

  Future<void> loadTodos() async {
    // 加载待办事项
  }
}

这种写法简单直接,但在实际项目中会面临几个棘手的问题。首先,当 TodoService 的构造函数发生变更时,比如需要增加日志服务或配置服务参数,所有使用 TodoService 的地方都需要逐一修改。其次,单元测试时无法方便地替换为 mock 对象,导致测试代码与业务代码耦合严重。再者,当应用规模扩大、服务数量增多时,服务之间的依赖关系会变得错综复杂,难以追踪和理清。

1.2 服务定位器模式的优势

服务定位器(Service Locator)是一种常见的设计模式,它提供一个集中的容器来管理服务的注册和获取。与构造函数注入相比,服务定位器提供了更灵活的服务管理能力:可以在运行时动态替换服务实现、支持服务实例的单例和工厂模式、以及便捷的资源释放机制。

get_it 是 Dart 语言中实现服务定位器模式的优秀库,其核心特性包括:完全使用 Dart 语言实现、无任何平台依赖、支持多种服务生命周期模式、以及简洁直观的 API 设计。这些特性使得 get_it 特别适合作为 OpenHarmony 平台的依赖注入解决方案。

二、get_it 库的技术特性分析

2.1 纯 Dart 实现的兼容性优势

get_it 之所以能够无缝适配 OpenHarmony 平台,根本原因在于其完全使用 Dart 语言实现,不依赖任何平台特定代码或原生库。在 Flutter 的跨平台架构中,纯 Dart 包可以在所有目标平台上以相同的方式运行,无需额外的平台通道(Platform Channel)或条件编译逻辑。

对于 OpenHarmony 平台而言,这意味着开发者无需担心底层平台能力的差异会导致库功能异常。get_it 提供的服务注册、获取、释放等功能在 Android、iOS、Windows、macOS 以及 OpenHarmony 上的行为完全一致。这种一致性对于构建需要同时支持多平台的 Flutter 应用尤为重要。

2.2 支持的服务生命周期模式

get_it 提供了三种主要的服务注册模式,每种模式适用于不同的业务场景:

单例模式(registerSingleton) 适用于无状态的工具类服务,如 HTTP 客户端、日志服务等。整个应用生命周期内只会创建一个服务实例,所有获取请求返回同一个对象。这种模式适合那些初始化成本较高但需要被广泛使用的服务。

懒加载单例模式(registerLazySingleton) 与普通单例类似,但实例在首次访问时才被创建。如果服务在某些应用场景下可能不会被使用,懒加载模式可以避免不必要的资源消耗。这种模式特别适合有状态服务,如本地存储服务。

工厂模式(registerFactory) 每次调用获取方法都会创建新的服务实例。适用于需要保持独立状态的服务,如针对不同用户的业务服务实例。这种模式确保每次获取的服务都是全新的,彼此之间互不影响。

2.3 资源管理与释放能力

get_it 提供了完善的服务释放机制,这对于移动端应用尤为重要。在 OpenHarmony 应用中,用户可能会频繁切换应用或使应用进入后台,合理管理服务资源可以有效降低应用内存占用。

通过 reset() 方法可以清空所有已注册的服务实例,释放相关资源。通过 unregister<T>() 可以针对性地释放特定服务。这些功能使得开发者可以在应用生命周期管理的各个阶段灵活控制资源使用。

三、项目集成实践

3.1 依赖配置

在项目的 pubspec.yaml 文件中添加 get_it 依赖:

dependencies:
  flutter:
    sdk: flutter

  # get_it - 服务定位器 / 依赖注入框架
  # 纯 Dart 实现,OpenHarmony 平台完全兼容
  get_it: ^8.0.3

执行 flutter pub get 命令完成依赖安装。get_it 库的包体积较小,下载和安装过程通常很快完成。安装完成后,可以在项目的 .dart_tool/package_config.json 中确认 get_it 的版本信息。

3.2 服务类定义

首先定义业务中使用的服务类。这些服务类保持原有结构,不需要添加任何特殊注解,这是 get_it 与其他 DI 框架的重要区别——它采用完全的非侵入式设计。

import 'package:dio/dio.dart';
import '../models/todo_item.dart';

class TodoService {
  static const String _baseUrl = 'https://jsonplaceholder.typicode.com';
  final Dio _dio;

  TodoService() : _dio = Dio(
    BaseOptions(
      connectTimeout: const Duration(seconds: 30),
      receiveTimeout: const Duration(seconds: 30),
      sendTimeout: const Duration(seconds: 30),
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
    ),
  );

  Future<List<TodoItem>> getTodos() async {
    try {
      final response = await _dio.get('$_baseUrl/todos');
      final List<dynamic> data = response.data;
      return data.map((json) => TodoItem.fromJson(json)).toList();
    } on DioException catch (e) {
      throw Exception(_handleDioError(e));
    }
  }

  Future<List<TodoItem>> getTodosByUser(int userId) async {
    try {
      final response = await _dio.get(
        '$_baseUrl/todos',
        queryParameters: {'userId': userId},
      );
      final List<dynamic> data = response.data;
      return data.map((json) => TodoItem.fromJson(json)).toList();
    } on DioException catch (e) {
      throw Exception(_handleDioError(e));
    }
  }

  String _handleDioError(DioException e) {
    switch (e.type) {
      case DioExceptionType.connectionTimeout:
        return '连接超时,请检查网络设置';
      case DioExceptionType.receiveTimeout:
        return '服务器响应超时';
      case DioExceptionType.badResponse:
        return '服务器错误: ${e.response?.statusCode}';
      case DioExceptionType.connectionError:
        return '网络连接失败';
      default:
        return '请求失败: ${e.message}';
    }
  }
}

上述代码展示了一个典型的网络请求服务实现。TodoService 内部创建并管理自己的 Dio 实例,这是服务类自主管理依赖的常见模式。

再看一个本地存储服务的实现:

import 'package:shared_preferences/shared_preferences.dart';

class StorageService {
  static const String _keyThemeMode = 'theme_mode';
  static const String _keyUserName = 'user_name';
  static const String _keyLastLogin = 'last_login';

  late SharedPreferences _prefs;
  bool _isInitialized = false;

  Future<void> init() async {
    if (!_isInitialized) {
      _prefs = await SharedPreferences.getInstance();
      _isInitialized = true;
    }
  }

  Future<void> ensureInitialized() async {
    if (!_isInitialized) {
      await init();
    }
  }

  Future<void> setThemeMode(String mode) async {
    await ensureInitialized();
    await _prefs.setString(_keyThemeMode, mode);
  }

  String getThemeMode() {
    if (!_isInitialized) return 'system';
    return _prefs.getString(_keyThemeMode) ?? 'system';
  }

  Future<void> setUserName(String name) async {
    await ensureInitialized();
    await _prefs.setString(_keyUserName, name);
  }

  String getUserName() {
    if (!_isInitialized) return '默认用户';
    return _prefs.getString(_keyUserName) ?? '默认用户';
  }

  Future<void> setLastLogin(DateTime time) async {
    await ensureInitialized();
    await _prefs.setString(_keyLastLogin, time.toIso8601String());
  }

  DateTime? getLastLogin() {
    if (!_isInitialized) return null;
    final str = _prefs.getString(_keyLastLogin);
    return str != null ? DateTime.tryParse(str) : null;
  }

  Future<void> clearAll() async {
    await ensureInitialized();
    await _prefs.clear();
  }
}

StorageService 使用 SharedPreferences 进行本地数据持久化。这类服务需要在应用启动时初始化,适合使用懒加载单例模式注册。

3.3 DI 容器配置

创建专门的 injection.dart 文件来集中管理所有服务的注册和配置:

import 'package:get_it/get_it.dart';
import 'package:dio/dio.dart';
import 'services/storage_service.dart';
import 'services/todo_service.dart';
import 'services/post_service.dart';
import 'services/message_service.dart';

// ============================================================
// OpenHarmony 服务定位器配置
// ============================================================
// 使用 get_it 实现依赖注入容器
//
// OH 平台适配要点:
// 1. get_it 纯 Dart 实现,无需平台通道,OH 完全兼容
// 2. 手动注册方式,避免代码生成器依赖
// 3. 支持单例和懒加载单例模式
// 4. 应用退出时可通过 reset() 释放所有注册实例
// ============================================================

/// 全局 GetIt 实例,作为服务定位器
final getIt = GetIt.instance;

/// 初始化依赖注入容器
/// 应在 main() 函数中,runApp() 之前调用
Future<void> configureDependencies() async {
  // 注册 Dio HTTP 客户端(单例)
  // Dio 是无状态服务,整个应用生命周期共享一个实例
  getIt.registerSingleton<Dio>(_createDio());

  // 注册 StorageService(懒加载单例)
  // 有状态服务,首次访问时才创建实例
  getIt.registerLazySingleton<StorageService>(() => StorageService());

  // 注册 MessageService(懒加载单例)
  // 无状态服务,但需要保持状态一致性
  getIt.registerLazySingleton<MessageService>(() => MessageService());

  // 注册 PostService(工厂模式)
  // 每次获取都创建新实例,适合业务数据请求
  getIt.registerFactory<PostService>(() => PostService());

  // 注册 TodoService(工厂模式)
  getIt.registerFactory<TodoService>(() => TodoService());
}

/// 创建 Dio 实例
Dio _createDio() {
  final dio = Dio(
    BaseOptions(
      connectTimeout: const Duration(seconds: 30),
      receiveTimeout: const Duration(seconds: 30),
      sendTimeout: const Duration(seconds: 30),
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
    ),
  );

  // 添加日志拦截器(开发环境)
  dio.interceptors.add(LogInterceptor(
    requestBody: true,
    responseBody: true,
    error: true,
    logPrint: (obj) => print('[DIO] $obj'),
  ));

  return dio;
}

// ============================================================
// OH 应用生命周期管理
// ============================================================

/// 释放所有注册的实例
/// 应在应用退出或页面销毁时调用
void resetDependencies() {
  // 关闭 Dio 连接
  if (getIt.isRegistered<Dio>()) {
    getIt<Dio>().close();
  }

  // 重置容器
  getIt.reset();
}

/// 检查服务是否已注册
bool isServiceRegistered<T extends Object>() {
  return getIt.isRegistered<T>();
}

/// 获取已注册服务的实例
T getService<T extends Object>() {
  return getIt<T>();
}

这段代码展示了 DI 容器配置的完整实现。configureDependencies() 函数负责所有服务的注册,应在应用启动时调用一次。resetDependencies() 函数用于清理所有注册的服务实例,适合在应用退出或需要重置状态时调用。

3.4 应用入口集成

在 main.dart 中集成 DI 容器的初始化:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/providers.dart';
import 'routing/router.dart';
import 'utils/theme_utils.dart';
import 'injection.dart';

/// 全局 router 实例
final _appRouter = createAppRouter();

/// Riverpod Provider 用于初始化设置
final initSettingsProvider = FutureProvider<void>((ref) async {
  await ref.read(settingsProvider.notifier).loadSettings();
});

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 在 runApp 之前初始化依赖注入容器
  await configureDependencies();

  runApp(
    const ProviderScope(
      child: OpenHarmonyApp(),
    ),
  );
}

class OpenHarmonyApp extends ConsumerWidget {
  const OpenHarmonyApp({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final settings = ref.watch(settingsProvider);
    final themeMode = settings.flutterThemeMode;

    return MaterialApp.router(
      title: 'OpenHarmony App',
      debugShowCheckedModeBanner: false,
      theme: AppTheme.lightTheme,
      darkTheme: AppTheme.darkTheme,
      themeMode: themeMode,
      routerConfig: _appRouter,
    );
  }
}

注意 configureDependencies() 必须在 runApp() 之前调用,这是为了确保所有服务在应用启动时已经完成注册。如果某些服务需要异步初始化,可以在 configureDependencies() 中等待初始化完成后再继续。

3.5 Provider 层集成

将 Provider 中的直接实例化改为通过服务定位器获取:

import 'package:flutter/foundation.dart';
import '../models/todo_item.dart';
import '../injection.dart';
import '../services/todo_service.dart';

class TodoProvider with ChangeNotifier {
  // 通过 get_it 获取服务实例,而非直接 new
  final TodoService _todoService = getIt<TodoService>();

  List<TodoItem> _todos = [];
  List<TodoItem> _filteredTodos = [];
  bool _isLoading = false;
  String? _errorMessage;
  String _currentFilter = 'all';

  List<TodoItem> get todos => _filteredTodos;
  List<TodoItem> get allTodos => _todos;
  bool get isLoading => _isLoading;
  String? get errorMessage => _errorMessage;
  String get currentFilter => _currentFilter;

  int get completedCount => _todos.where((t) => t.completed).length;
  int get pendingCount => _todos.where((t) => !t.completed).length;
  int get totalCount => _todos.length;

  Future<void> loadTodos({int? userId}) async {
    setLoading(true);
    _errorMessage = null;

    try {
      List<TodoItem> todos;
      if (userId != null && userId > 0) {
        todos = await _todoService.getTodosByUser(userId);
      } else {
        todos = await _todoService.getTodos();
      }
      _todos = todos;
      _applyFilter();
    } catch (e) {
      _errorMessage = e.toString();
      debugPrint('加载待办事项失败: $_errorMessage');
    } finally {
      setLoading(false);
    }
  }

  void setFilter(String filter) {
    _currentFilter = filter;
    _applyFilter();
    notifyListeners();
  }

  void _applyFilter() {
    switch (_currentFilter) {
      case 'completed':
        _filteredTodos = _todos.where((t) => t.completed).toList();
        break;
      case 'pending':
        _filteredTodos = _todos.where((t) => !t.completed).toList();
        break;
      default:
        _filteredTodos = List.from(_todos);
    }
  }

  void setLoading(bool value) {
    _isLoading = value;
    notifyListeners();
  }

  Map<String, int> get statistics {
    return {
      'total': totalCount,
      'completed': completedCount,
      'pending': pendingCount,
    };
  }
}

上述代码展示了 Provider 与服务定位器集成的完整模式。关键的改变只有一行:

final TodoService _todoService = getIt<TodoService>();

这行代码将 TodoService 的获取方式从直接实例化改为从服务定位器获取。这种改变带来的好处是:当需要为 TodoService 添加依赖或修改构造函数时,只需要修改 injection.dart 中的注册代码,所有使用该服务的 Provider 都无需改动。

四、OpenHarmony 平台验证

4.1 代码静态分析

在正式部署到 OH 设备之前,首先进行代码静态分析。运行 Flutter 分析器检查代码质量:

flutter analyze lib/injection.dart lib/providers/todo_provider.dart lib/providers/post_provider.dart

如果没有严重的错误或警告,说明代码结构符合 Dart 语言规范。对于 get_it 相关代码,应确保泛型参数满足 <T extends Object> 的约束条件。

4.2 OH 设备编译验证

配置好 OpenHarmony 设备连接后,执行以下命令进行编译:

flutter build ohos --debug

编译过程中,Flutter 工具链会将 Dart 代码编译为适用于 OH 平台的中间产物。get_it 作为纯 Dart 库,不需要任何额外的平台适配工作。编译成功后,会在 build/ohos/ 目录下生成可部署的应用包。

4.3 应用启动验证

将编译生成的应用包部署到 OH 设备上运行。应用启动后,应观察到以下行为:

服务注册阶段:在应用启动日志中,应能看到 DI 容器完成初始化的相关输出(如果配置了日志拦截器)。

服务获取阶段:当用户首次访问需要网络请求或本地存储的功能时,对应的服务会被正确实例化和使用。

状态一致性:单例模式注册的服务在整个应用生命周期内保持同一实例,工厂模式的服务每次获取返回新的实例。

4.4 生命周期管理验证

为验证服务的正确释放,可以实现应用生命周期监听:

class AppLifecycleManager extends WidgetsBindingObserver {
  
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        debugPrint('应用进入前台');
        break;
      case AppLifecycleState.inactive:
        debugPrint('应用进入非活跃状态');
        break;
      case AppLifecycleState.paused:
        debugPrint('应用进入后台');
        // 可选:释放非关键资源
        break;
      case AppLifecycleState.detached:
      case AppLifecycleState.hidden:
        debugPrint('应用即将退出');
        resetDependencies();
        break;
    }
  }
}

通过在应用退出时调用 resetDependencies(),可以确保所有服务实例被正确清理,验证资源释放机制的有效性。

五、进阶实践

5.1 测试环境配置

get_it 的设计使得在测试环境中替换服务变得非常容易:

import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';

void main() {
  setUpAll(() {
    // 注册 Mock 服务
    GetIt.instance.registerFactory<TodoService>(
      () => MockTodoService(),
    );
  });

  tearDownAll(() {
    // 清理测试注册
    GetIt.instance.reset();
  });

  test('待办事项加载测试', () async {
    final provider = TodoProvider();
    await provider.loadTodos();

    expect(provider.todos.isNotEmpty, true);
  });
}

class MockTodoService extends TodoService {
  
  Future<List<TodoItem>> getTodos() async {
    // 返回模拟数据
    return [
      TodoItem(userId: 1, id: 1, title: '测试待办', completed: false),
      TodoItem(userId: 1, id: 2, title: '已完成项', completed: true),
    ];
  }
}

这种测试配置方式的优势在于:业务代码无需任何修改,测试代码完全掌控服务的实例化逻辑。当需要测试不同的业务场景时,只需注册不同的 mock 实现即可。

5.2 服务间的依赖管理

当服务之间存在依赖关系时,可以在服务定位器配置中显式处理:

Future<void> configureDependencies() async {
  // 无依赖的服务先注册
  getIt.registerLazySingleton<StorageService>(() => StorageService());

  // 有依赖的服务使用 getIt 获取已注册的服务
  getIt.registerFactory<UserService>(
    () => UserService(
      getIt<StorageService>(),
      getIt<Dio>(),
    ),
  );
}

显式依赖注入的方式使得依赖关系一目了然,每个服务的依赖在创建时就被明确指定。这种方式比构造函数注入更加直观,也更容易进行单元测试。

5.3 条件注册策略

根据不同的运行环境注册不同的服务实现:

Future<void> configureDependencies({required bool isProduction}) async {
  if (isProduction) {
    getIt.registerSingleton<ApiService>(
      ProductionApiService(),
    );
  } else {
    getIt.registerSingleton<ApiService>(
      MockApiService(),
    );
  }
}

这种模式在实际项目中有重要应用价值:开发环境使用 mock 服务避免外部依赖,发布环境使用真实服务确保功能正常。
这是运行截图:在这里插入图片描述

六、总结与展望

本文详细介绍了 get_it 服务定位器在 OpenHarmony 平台上的适配实践。通过将 get_it 集成到 Flutter 项目中,我们实现了以下目标:

解耦服务创建与使用:Provider 不再直接创建服务实例,而是通过服务定位器获取。这使得服务的管理更加集中,也便于后续的维护和扩展。

灵活的生命周期管理:get_it 提供的单例、懒加载单例和工厂三种模式,覆盖了绝大多数业务场景。开发者可以根据服务的特性选择最合适的注册方式。

便捷的测试支持:通过注册不同的服务实现,可以轻松切换生产环境和测试环境,无需修改业务代码。

完善的资源释放:应用退出时可以调用 resetDependencies() 释放所有资源,这对于内存资源相对有限的移动设备尤为重要。

展望未来,随着 Flutter 对 OpenHarmony 平台支持的持续完善,会有越来越多的 Dart 库能够原生运行在 OH 设备上。get_it 的成功适配为我们提供了一个良好的范例:纯 Dart 实现、无平台依赖的库具有最好的跨平台兼容性。开发者在选择第三方库时,可以优先考虑这类库,以获得更稳定、更易维护的跨平台体验。

代码托管

本文涉及的完整示例代码已托管至 AtomGit 平台,仓库地址为:

https://atomgit.com/openharmony-flutter/getit-adaptation

仓库中包含了完整的示例项目,可以直接导入开发环境运行验证。代码按照本文的章节结构组织,每个关键步骤都有对应的代码文件便于对照学习。

欢迎对本文内容提出建议和反馈,共同推动 Flutter for OpenHarmony 生态的完善发展。

Logo

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

更多推荐