【Flutter】NewsHub跨平台开发:Flutter适配鸿蒙实战教程


【Flutter】NewsHub跨平台开发:Flutter适配鸿蒙实战教程

摘要

本文详细介绍如何使用 Flutter 框架开发"NewsHub"新闻聚合应用,实现一套 Dart 代码在 Android、iOS 和 HarmonyOS 三端运行。重点讲解 DevEco Studio 环境配置、华为 HMS 服务集成、响应式 UI 构建、鸿蒙平台专项优化以及 .hap 包打包上架流程。通过本教程,开发者将掌握 Flutter 跨平台开发的完整流程,特别是在鸿蒙生态中的适配技巧。


一、项目架构设计

1.1 整体技术架构

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                      NewsHub 新闻聚合应用                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐          │
│  │  Android    │  │    iOS      │  │  HarmonyOS  │          │
│  │  原生层     │  │  原生层     │  │  原生层     │          │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘          │
│         │                │                │                         │
│         └────────────────┴────────────────┘                         │
│                          │                                      │
│                  ┌───────▼────────┐                             │
│                  │  Flutter Engine │                             │
│                  │   (Dart 代码)   │                             │
│                  └───────┬────────┘                             │
│                          │                                      │
│  ┌───────────────────────▼─────────────────────────────────┐       │
│  │                    Flutter 业务层                        │       │
│  │  ┌───────────────────────────────────────────────┐      │       │
│  │  │ BLoC 状态管理    │ Repository    │ Services   │      │       │
│  │  └───────────────────────────────────────────────┘      │       │
│  │  ┌───────────────────────────────────────────────┐      │       │
│  │  │   UI 层 (responsive_builder + flutter_hms_sdk)   │      │       │
│  │  └───────────────────────────────────────────────┘      │       │
│  └───────────────────────────────────────────────────────────┘       │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────────┐      │
│  │              外部服务集成                                    │      │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────┐ │      │
│  │  │ News API  │  │ Push Kit  │  │ Account   │  │Anal. │ │      │
│  │  └──────────┘  └──────────┘  └──────────┘  └──────┘ │      │
│  └─────────────────────────────────────────────────────────────┘      │
└─────────────────────────────────────────────────────────────────────┘

1.2 项目目录结构

复制代码
NewsHub/
├── lib/
│   ├── main.dart                    # 应用入口
│   ├── app.dart                     # 根组件
│   │
│   ├── core/
│   │   ├── config/
│   │   │   ├── app_config.dart      # 应用配置
│   │   │   └── platform_config.dart # 平台配置
│   │   ├── constants/
│   │   │   └── api_constants.dart  # API 常量
│   │   ├── theme/
│   │   │   ├── app_theme.dart      # 主题配置
│   │   │   └── dark_theme.dart     # 深色主题
│   │   └── utils/
│   │       ├── device_utils.dart     # 设备工具
│   │       └── logger.dart         # 日志工具
│   │
│   ├── features/
│   │   ├── home/
│   │   │   ├── presentation/
│   │   │   │   ├── pages/
│   │   │   │   │   └── home_page.dart
│   │   │   │   └── widgets/
│   │   │   │       ├── news_card.dart
│   │   │   │       └── category_chips.dart
│   │   │   └── domain/
│   │   │       ├── repositories/
│   │   │       └── usecases/
│   │   │
│   │   ├── news_detail/
│   │   │   ├── presentation/
│   │   │   │   ├── pages/
│   │   │   │   │   └── news_detail_page.dart
│   │   │   │   └── widgets/
│   │   │   │       └── content_reader.dart
│   │   │   └── domain/
│   │   │
│   │   ├── settings/
│   │   │   ├── presentation/
│   │   │   │   └── pages/
│   │   │   │       └── settings_page.dart
│   │   │   └── domain/
│   │   │
│   │   └── notifications/
│   │       ├── presentation/
│   │       │   └── pages/
│   │       │       └── notification_page.dart
│   │       └── domain/
│   │           └── hms_push_service.dart
│   │
│   └── shared/
│       ├── widgets/
│       │   ├── responsive_wrapper.dart  # 响应式包装器
│       │   ├── adaptive_scaffold.dart  # 自适应脚手架
│       │   └── custom_app_bar.dart     # 自定义导航栏
│       └── domain/
│           ├── entities/
│           │   └── news_article.dart
│           └── repositories/
│               └── news_repository.dart
│
│   └── services/
│       ├── hms/
│       │   ├── hms_account_service.dart   # HMS 账号服务
│       │   ├── hms_analytics_service.dart # HMS 分析服务
│       │   └── hms_push_service.dart     # HMS 推送服务
│       └── api/
│           └── news_api_service.dart       # 新闻 API 服务
│
├── android/
│   └── app/
│       └── src/
│           └── main/
│               └── AndroidManifest.xml     # Android 配置
│
├── ios/
│   └── Runner/
│       └── Info.plist                   # iOS 配置
│
├── ohos/                              # HarmonyOS 专用目录
│   ├── entry/
│   │   └── src/
│   │       └── main/
│   │           ├── ets/
│   │           │   └── entryability/
│   │           │       └── EntryAbility.ets
│   │           ├── resources/
│   │           │   └── base/
│   │           │       └── element/
│   │           │           └── string.json
│   │           └── module.json5           # HarmonyOS 模块配置
│   │
│   ├── harmony_flutter_adapter/          # Flutter-HarmonyOS 适配层
│   │   └── lib/
│   │       ├── harmony_plugins.dart       # 插件适配
│   │       └── platform_channel.dart    # 平台通道
│   │
│   └── build-profile.json5            # HarmonyOS 构建配置
│
├── pubspec.yaml                        # Flutter 依赖配置
├── build-profile.json5                  # HarmonyOS 构建配置
└── hvigorfile.ts                      # HarmonyOS 构建脚本

二、DevEco Studio 环境配置

2.1 开发环境准备

工具/组件 版本要求 用途
DevEco Studio 4.0+ HarmonyOS 应用开发
Flutter SDK 3.13.0+ 跨平台框架
Dart SDK 3.0.0+ Flutter 编程语言
HarmonyOS SDK API 9+ 鸿蒙系统 API
JDK 17 Android 原生开发

2.2 创建 Flutter 项目

bash 复制代码
# 1. 创建 Flutter 项目
flutter create --platforms=android,ios newshub

cd newshub

# 2. 添加鸿蒙支持
# 使用 flutter-harmonyos-adapter 或手动配置

2.3 pubspec.yaml 依赖配置

yaml 复制代码
name: newshub
description: NewsHub - 跨平台新闻聚合应用
version: 1.0.0+1

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter

  # UI 框架
  cupertino_icons: ^1.0.6

  # 状态管理
  flutter_bloc: ^8.1.3
  equatable: ^2.0.5

  # 网络请求
  dio: ^5.3.2
  pretty_dio_logger: ^1.3.1

  # 本地存储
  shared_preferences: ^2.2.2
  hive: ^2.2.3
  hive_flutter: ^1.1.0

  # 响应式 UI
  responsive_builder: ^0.7.0

  # 华为 HMS 服务
  flutter_hms_sdk:
    git:
      url: https://github.com/HMS-Core/hms-flutter-plugin.git
      ref: harmonyos
      path: flutter/

  # 设备信息
  device_info_plus: ^9.1.0
  platform_info: ^4.0.2

  # 其他工具
  intl: ^0.18.0
  url_launcher: ^6.1.11
  share_plus: ^7.2.1

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.1
  build_runner: ^2.4.6
  freezed_annotation: ^2.4.1

flutter:
  uses-material-design: true
  assets:
    - assets/images/
    - assets/icons/

2.4 HarmonyOS 权限配置 (module.json5)

json5 复制代码
{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "default",
      "tablet"
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:icon",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:icon",
        "startWindowBackgroundColor": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ]
          }
        ]
      }
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:internet_permission_reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.GET_NETWORK_INFO",
        "reason": "$string:network_info_permission_reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.NOTIFICATION_CONTROLLER",
        "reason": "$string:notification_permission_reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
        "reason": "$string:background_permission_reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "always"
        }
      }
    ]
  }
}

三、HMS 服务集成

3.1 Push Kit 消息推送服务

dart 复制代码
// lib/services/hms/hms_push_service.dart
import 'package:flutter_hms_sdk/flutter_hms_sdk.dart';
import 'package:shared_preferences/shared_preferences.dart';

/// HMS 推送消息数据模型
class HmsPushMessage {
  final String title;
  final String content;
  final Map<String, dynamic>? data;
  final String? messageId;

  HmsPushMessage({
    required this.title,
    required this.content,
    this.data,
    this.messageId,
  });

  factory HmsPushMessage.fromJson(Map<String, dynamic> json) {
    return HmsPushMessage(
      title: json['title'] ?? '',
      content: json['content'] ?? '',
      data: json['data'],
      messageId: json['messageId'],
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'title': title,
      'content': content,
      if (data != null) 'data': data,
      if (messageId != null) 'messageId': messageId,
    };
  }
}

/// HMS Push Kit 推送服务
class HmsPushService {
  static HmsPushService? _instance;
  final FlutterHmsSdk _hmsSdk = FlutterHmsSdk();
  final SharedPreferences _prefs = SharedPreferences.getInstance();

  // 推送回调控制器
  final _pushController = StreamController<HmsPushMessage>.broadcast();

  Stream<HmsPushMessage> get pushStream => _pushController.stream;

  factory HmsPushService() {
    _instance ??= HmsPushService._internal();
    return _instance!;
  }

  HmsPushService._internal();

  /// 初始化 Push Kit
  Future<bool> init() async {
    try {
      // 初始化 HMS SDK
      await _hmsSdk.init(
        appId: PlatformConfig.harmonyosAppId,
      );

      // 请求推送权限
      final hasPermission = await _requestNotificationPermission();
      if (!hasPermission) {
        return false;
      }

      // 注册推送回调
      _setupPushCallbacks();

      // 获取 Token
      final token = await _getPushToken();
      if (token != null) {
        await _saveToken(token);
      }

      return true;
    } catch (e) {
      AppLogger.error('HMS Push', '初始化失败: $e');
      return false;
    }
  }

  /// 请求通知权限
  Future<bool> _requestNotificationPermission() async {
    try {
      final result = await _hmsSdk.requestNotificationPermission();
      return result ?? false;
    } catch (e) {
      AppLogger.error('HMS Push', '权限请求失败: $e');
      return false;
    }
  }

  /// 获取推送 Token
  Future<String?> _getPushToken() async {
    try {
      final token = await _hmsSdk.getToken();
      AppLogger.info('HMS Push', 'Token 获取成功: ${token.substring(0, 10)}...');
      return token;
    } catch (e) {
      AppLogger.error('HMS Push', 'Token 获取失败: $e');
      return null;
    }
  }

  /// 保存 Token 到本地
  Future<void> _saveToken(String token) async {
    try {
      final prefs = await _prefs;
      await prefs.setString('push_token', token);
      await prefs.setString('push_token_update_time',
          DateTime.now().toIso8601String());
    } catch (e) {
      AppLogger.error('HMS Push', 'Token 保存失败: $e');
    }
  }

  /// 设置推送回调
  void _setupPushCallbacks() {
    // 接收消息回调
    _hmsSdk.onMessageReceived((message) {
      final pushMessage = HmsPushMessage.fromJson(message);
      _pushController.add(pushMessage);

      // 处理不同类型的推送
      _handlePushMessage(pushMessage);
    });

    // Token 刷新回调
    _hmsSdk.onTokenRefresh((token) {
      AppLogger.info('HMS Push', 'Token 刷新: ${token.substring(0, 10)}...');
      _saveToken(token);
    });

    // 错误回调
    _hmsSdk.onPushError((error) {
      AppLogger.error('HMS Push', '推送错误: $error');
    });
  }

  /// 处理推送消息
  void _handlePushMessage(HmsPushMessage message) {
    switch (message.data?['type']) {
      case 'news_breaking':
        _handleBreakingNews(message);
        break;
      case 'daily_digest':
        _handleDailyDigest(message);
        break;
      default:
        _handleDefaultPush(message);
    }
  }

  /// 处理突发新闻推送
  void _handleBreakingNews(HmsPushMessage message) {
    AppLogger.info('HMS Push', '突发新闻: ${message.title}');

    // 显示本地通知
    _showLocalNotification(
      title: message.title,
      body: message.content,
      priority: NotificationPriority.high,
    );

    // 更新应用内角标
    _updateBadgeCount();
  }

  /// 处理每日摘要推送
  void _handleDailyDigest(HmsPushMessage message) {
    AppLogger.info('HMS Push', '每日摘要');

    // 缓存摘要数据
    _cacheDailyDigest(message.data);
  }

  /// 处理默认推送
  void _handleDefaultPush(HmsPushMessage message) {
    AppLogger.info('HMS Push', '普通推送: ${message.title}');
  }

  /// 显示本地通知
  Future<void> _showLocalNotification({
    required String title,
    required String body,
    NotificationPriority priority = NotificationPriority.normal,
  }) async {
    try {
      await _hmsSdk.showLocalNotification(
        title: title,
        body: body,
        priority: priority,
      );
    } catch (e) {
      AppLogger.error('HMS Push', '本地通知显示失败: $e');
    }
  }

  /// 更新角标数量
  Future<void> _updateBadgeCount() async {
    try {
      final prefs = await _prefs;
      final currentCount = prefs.getInt('badge_count', 0);
      await prefs.setInt('badge_count', currentCount + 1);

      // 同步到鸿蒙桌面
      await _hmsSdk.setBadgeCount(currentCount + 1);
    } catch (e) {
      AppLogger.error('HMS Push', '角标更新失败: $e');
    }
  }

  /// 清除角标
  Future<void> clearBadgeCount() async {
    try {
      final prefs = await _prefs;
      await prefs.setInt('badge_count', 0);

      await _hmsSdk.setBadgeCount(0);
    } catch (e) {
      AppLogger.error('HMS Push', '角标清除失败: $e');
    }
  }

  /// 取消订阅
  Future<void> unsubscribe() async {
    try {
      await _hmsSdk.deleteToken();

      final prefs = await _prefs;
      await prefs.remove('push_token');
    } catch (e) {
      AppLogger.error('HMS Push', '取消订阅失败: $e');
    }
  }

  /// 释放资源
  void dispose() {
    _pushController.close();
  }
}

3.2 Account Kit 账号服务

dart 复制代码
// lib/services/hms/hms_account_service.dart
import 'package:flutter_hms_sdk/flutter_hms_sdk.dart';

/// 用户信息模型
class HmsUserInfo {
  final String displayName;
  final String? email;
  final String? avatarUrl;
  final String accountId;

  HmsUserInfo({
    required this.displayName,
    this.email,
    this.avatarUrl,
    required this.accountId,
  });

  factory HmsUserInfo.fromJson(Map<String, dynamic> json) {
    return HmsUserInfo(
      displayName: json['displayName'] ?? '',
      email: json['email'],
      avatarUrl: json['avatarUrl'],
      accountId: json['accountId'] ?? '',
    );
  }
}

/// HMS Account Kit 服务
class HmsAccountService {
  static HmsAccountService? _instance;
  final FlutterHmsSdk _hmsSdk = FlutterHmsSdk();

  factory HmsAccountService() {
    _instance ??= HmsAccountService._internal();
    return _instance!;
  }

  HmsAccountService._internal();

  /// 初始化 Account Kit
  Future<bool> init() async {
    try {
      await _hmsSdk.init(
        appId: PlatformConfig.harmonyosAppId,
      );

      AppLogger.info('HMS Account', '初始化成功');
      return true;
    } catch (e) {
      AppLogger.error('HMS Account', '初始化失败: $e');
      return false;
    }
  }

  /// 静默登录
  Future<HmsUserInfo?> silentSignIn() async {
    try {
      final result = await _hmsSdk.silentSignIn();
      if (result == null) {
        return null;
      }

      final userInfo = HmsUserInfo.fromJson(result);
      await _saveUserInfo(userInfo);

      return userInfo;
    } catch (e) {
      AppLogger.error('HMS Account', '静默登录失败: $e');
      return null;
    }
  }

  /// 显式登录
  Future<HmsUserInfo?> signIn() async {
    try {
      final result = await _hmsSdk.signIn();
      if (result == null) {
        return null;
      }

      final userInfo = HmsUserInfo.fromJson(result);
      await _saveUserInfo(userInfo);

      // 登录成功后初始化分析服务
      await HmsAnalyticsService().setUserId(userInfo.accountId);

      return userInfo;
    } catch (e) {
      if (e.toString().contains('Canceled')) {
        // 用户取消登录
        return null;
      }
      AppLogger.error('HMS Account', '登录失败: $e');
      return null;
    }
  }

  /// 退出登录
  Future<bool> signOut() async {
    try {
      await _hmsSdk.signOut();

      // 清除本地用户信息
      await _clearUserInfo();

      // 重置分析用户 ID
      await HmsAnalyticsService().resetUserId();

      AppLogger.info('HMS Account', '退出登录成功');
      return true;
    } catch (e) {
      AppLogger.error('HMS Account', '退出登录失败: $e');
      return false;
    }
  }

  /// 取消授权
  Future<bool> revokeAuthorization() async {
    try {
      await _hmsSdk.revokeAuthorization();
      return true;
    } catch (e) {
      AppLogger.error('HMS Account', '取消授权失败: $e');
      return false;
    }
  }

  /// 保存用户信息
  Future<void> _saveUserInfo(HmsUserInfo userInfo) async {
    try {
      final prefs = await SharedPreferences.getInstance();
      final json = jsonEncode(userInfo.toJson());
      await prefs.setString('user_info', json);
    } catch (e) {
      AppLogger.error('HMS Account', '保存用户信息失败: $e');
    }
  }

  /// 清除用户信息
  Future<void> _clearUserInfo() async {
    try {
      final prefs = await SharedPreferences.getInstance();
      await prefs.remove('user_info');
    } catch (e) {
      AppLogger.error('HMS Account', '清除用户信息失败: $e');
    }
  }

  /// 获取本地用户信息
  Future<HmsUserInfo?> getLocalUserInfo() async {
    try {
      final prefs = await SharedPreferences.getInstance();
      final json = prefs.getString('user_info');

      if (json != null && json.isNotEmpty) {
        return HmsUserInfo.fromJson(jsonDecode(json));
      }
      return null;
    } catch (e) {
      AppLogger.error('HMS Account', '获取本地用户信息失败: $e');
      return null;
    }
  }
}

3.3 Analytics Kit 分析服务

dart 复制代码
// lib/services/hms/hms_analytics_service.dart
import 'package:flutter_hms_sdk/flutter_hms_sdk.dart';

/// 分析事件类型
enum AnalyticsEventType {
  pageView('page_view'),
  newsClick('news_click'),
  newsShare('news_share'),
  newsFavorite('news_favorite'),
  search('search'),
  customEvent('custom_event');

  final String value;
  const AnalyticsEventType(this.value);
}

/// HMS Analytics Kit 服务
class HmsAnalyticsService {
  static HmsAnalyticsService? _instance;
  final FlutterHmsSdk _hmsSdk = FlutterHmsSdk();
  String? _userId;

  factory HmsAnalyticsService() {
    _instance ??= HmsAnalyticsService._internal();
    return _instance!;
  }

  HmsAnalyticsService._internal();

  /// 初始化 Analytics Kit
  Future<bool> init() async {
    try {
      await _hmsSdk.initAnalytics(
        appId: PlatformConfig.harmonyosAppId,
      );

      AppLogger.info('HMS Analytics', '初始化成功');
      return true;
    } catch (e) {
      AppLogger.error('HMS Analytics', '初始化失败: $e');
      return false;
    }
  }

  /// 设置用户 ID
  Future<void> setUserId(String userId) async {
    try {
      await _hmsSdk.setUserId(userId);
      _userId = userId;
      AppLogger.info('HMS Analytics', '设置用户 ID: $userId');
    } catch (e) {
      AppLogger.error('HMS Analytics', '设置用户 ID 失败: $e');
    }
  }

  /// 重置用户 ID
  Future<void> resetUserId() async {
    try {
      await _hmsSdk.setUserId('');
      _userId = null;
      AppLogger.info('HMS Analytics', '重置用户 ID');
    } catch (e) {
      AppLogger.error('HMS Analytics', '重置用户 ID 失败: $e');
    }
  }

  /// 发送页面浏览事件
  Future<void> logPageView({
    required String pageName,
    String? pageClass,
  }) async {
    try {
      await _hmsSdk.logEvent(
        eventType: AnalyticsEventType.pageView.value,
        eventName: pageName,
        params: {
          if (pageClass != null) 'page_class': pageClass,
          'timestamp': DateTime.now().millisecondsSinceEpoch,
          if (_userId != null) 'user_id': _userId,
        },
      );
    } catch (e) {
      AppLogger.error('HMS Analytics', '页面浏览事件发送失败: $e');
    }
  }

  /// 发送新闻点击事件
  Future<void> logNewsClick({
    required String newsId,
    required String category,
    String? source,
  }) async {
    try {
      await _hmsSdk.logEvent(
        eventType: AnalyticsEventType.newsClick.value,
        eventName: 'news_click',
        params: {
          'news_id': newsId,
          'category': category,
          if (source != null) 'source': source,
          'timestamp': DateTime.now().millisecondsSinceEpoch,
        },
      );
    } catch (e) {
      AppLogger.error('HMS Analytics', '新闻点击事件发送失败: $e');
    }
  }

  /// 发送新闻分享事件
  Future<void> logNewsShare({
    required String newsId,
    required String platform,
  }) async {
    try {
      await _hmsSdk.logEvent(
        eventType: AnalyticsEventType.newsShare.value,
        eventName: 'news_share',
        params: {
          'news_id': newsId,
          'platform': platform,
          'timestamp': DateTime.now().millisecondsSinceEpoch,
        },
      );
    } catch (e) {
      AppLogger.error('HMS Analytics', '新闻分享事件发送失败: $e');
    }
  }

  /// 发送自定义事件
  Future<void> logCustomEvent({
    required String eventName,
    Map<String, dynamic>? params,
  }) async {
    try {
      await _hmsSdk.logEvent(
        eventType: AnalyticsEventType.customEvent.value,
        eventName: eventName,
        params: {
          ...?params,
          'timestamp': DateTime.now().millisecondsSinceEpoch,
        },
      );
    } catch (e) {
      AppLogger.error('HMS Analytics', '自定义事件发送失败: $e');
    }
  }

  /// 设置用户属性
  Future<void> setUserProfile({
    String? favoriteCategory,
    int? readingTime,
  }) async {
    try {
      final params = <String, dynamic>{};

      if (favoriteCategory != null) {
        params['favorite_category'] = favoriteCategory;
      }
      if (readingTime != null) {
        params['reading_time'] = readingTime;
      }

      await _hmsSdk.setUserProfile(params);
      AppLogger.info('HMS Analytics', '设置用户属性: $params');
    } catch (e) {
      AppLogger.error('HMS Analytics', '设置用户属性失败: $e');
    }
  }

  /// 开始会话
  Future<void> beginSession({
    String? sessionId,
  }) async {
    try {
      await _hmsSdk.beginSession(
        sessionId: sessionId ?? _generateSessionId(),
      );
    } catch (e) {
      AppLogger.error('HMS Analytics', '开始会话失败: $e');
    }
  }

  /// 结束会话
  Future<void> endSession() async {
    try {
      await _hmsSdk.endSession();
    } catch (e) {
      AppLogger.error('HMS Analytics', '结束会话失败: $e');
    }
  }

  /// 生成会话 ID
  String _generateSessionId() {
    return '${DateTime.now().millisecondsSinceEpoch}_${_userId ?? 'guest'}';
  }
}

四、响应式 UI 构建

4.1 响应式包装器实现

dart 复制代码
// lib/shared/widgets/responsive_wrapper.dart
import 'package:flutter/material.dart';
import 'package:responsive_builder/responsive_builder.dart';

/// 设备类型枚举
enum DeviceType {
  mobile,
  tablet,
  desktop,
  foldable,
}

/// 屏幕信息
class ScreenInfo {
  final DeviceType deviceType;
  final double screenWidth;
  final double screenHeight;
  final bool isFoldable;
  final bool isDarkMode;

  ScreenInfo({
    required this.deviceType,
    required this.screenWidth,
    required this.screenHeight,
    required this.isFoldable,
    required this.isDarkMode,
  });
}

/// 响应式包装器组件
class ResponsiveWrapper extends StatelessWidget {
  final Widget Function(BuildContext, DeviceType) builder;
  final Widget? mobile;
  final Widget? tablet;
  final Widget? desktop;
  final Widget? foldable;

  const ResponsiveWrapper({
    Key? key,
    required this.builder,
    this.mobile,
    this.tablet,
    this.desktop,
    this.foldable,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ResponsiveBuilder(
      builder: (context, sizingInformation) {
        // 判断设备类型
        final deviceType = _getDeviceType(sizingInformation);

        // 获取屏幕信息
        final screenInfo = _getScreenInfo(context, sizingInformation, deviceType);

        return _buildForDevice(context, deviceType, screenInfo);
      },
    );
  }

  DeviceType _getDeviceType(SizingInformation sizingInformation) {
    final isFoldable = sizingInformation.deviceScreenType == DeviceScreenType.foldable;

    if (isFoldable) {
      return DeviceType.foldable;
    }

    if (sizingInformation.deviceScreenType == DeviceScreenType.desktop) {
      return DeviceType.desktop;
    }

    if (sizingInformation.deviceScreenType == DeviceScreenType.tablet) {
      return DeviceType.tablet;
    }

    return DeviceType.mobile;
  }

  ScreenInfo _getScreenInfo(
    BuildContext context,
    SizingInformation sizingInformation,
    DeviceType deviceType,
  ) {
    final isFoldable = deviceType == DeviceType.foldable;
    final isDarkMode = Theme.of(context).brightness == Brightness.dark;

    return ScreenInfo(
      deviceType: deviceType,
      screenWidth: sizingInformation.localWidgetSize.width,
      screenHeight: sizingInformation.localWidgetSize.height,
      isFoldable: isFoldable,
      isDarkMode: isDarkMode,
    );
  }

  Widget _buildForDevice(
    BuildContext context,
    DeviceType deviceType,
    ScreenInfo screenInfo,
  ) {
    // 优先使用指定的设备组件
    if (deviceType == DeviceType.mobile && mobile != null) {
      return mobile!;
    }
    if (deviceType == DeviceType.tablet && tablet != null) {
      return tablet!;
    }
    if (deviceType == DeviceType.desktop && desktop != null) {
      return desktop!;
    }
    if (deviceType == DeviceType.foldable && foldable != null) {
      return foldable!;
    }

    // 使用 builder 函数
    return builder(context, deviceType);
  }
}

/// 响应式值辅助类
class ResponsiveValue<T> {
  final T mobile;
  final T? tablet;
  final T? desktop;
  final T? foldable;

  const ResponsiveValue({
    required this.mobile,
    this.tablet,
    this.desktop,
    this.foldable,
  });

  T getValue(DeviceType deviceType) {
    if (deviceType == DeviceType.foldable && foldable != null) {
      return foldable!;
    }
    if (deviceType == DeviceType.desktop && desktop != null) {
      return desktop!;
    }
    if (deviceType == DeviceType.tablet && tablet != null) {
      return tablet!;
    }
    return mobile;
  }
}

4.2 自适应脚手架实现

dart 复制代码
// lib/shared/widgets/adaptive_scaffold.dart
import 'package:flutter/material.dart';

/// 自适应脚手架配置
class AdaptiveScaffoldConfig {
  final bool showBottomNavigation;
  final bool showNavigationRail;
  final int navigationRailWidth;
  final double? maxContentWidth;
  final bool enableDrawer;

  const AdaptiveScaffoldConfig({
    this.showBottomNavigation = true,
    this.showNavigationRail = false,
    this.navigationRailWidth = 56,
    this.maxContentWidth = 1200,
    this.enableDrawer = false,
  });

  /// 根据设备类型创建配置
  factory AdaptiveScaffoldConfig.forDevice(DeviceType deviceType) {
    switch (deviceType) {
      case DeviceType.mobile:
        return const AdaptiveScaffoldConfig(
          showBottomNavigation: true,
          showNavigationRail: false,
          enableDrawer: false,
        );
      case DeviceType.tablet:
        return const AdaptiveScaffoldConfig(
          showBottomNavigation: false,
          showNavigationRail: true,
          navigationRailWidth: 72,
          enableDrawer: true,
        );
      case DeviceType.desktop:
        return const AdaptiveScaffoldConfig(
          showBottomNavigation: false,
          showNavigationRail: true,
          navigationRailRailWidth: 80,
          enableDrawer: false,
          maxContentWidth: 1200,
        );
      case DeviceType.foldable:
        return const AdaptiveScaffoldConfig(
          showBottomNavigation: false,
          showNavigationRail: true,
          navigationRailWidth: 72,
          enableDrawer: true,
          maxContentWidth: 1400,
        );
    }
  }
}

/// 自适应脚手架组件
class AdaptiveScaffold extends StatelessWidget {
  final AdaptiveScaffoldConfig config;
  final PreferredSizeWidget? appBar;
  final Widget? body;
  final List<Widget>? floatingActions;
  final Widget? drawer;
  final List<NavigationDestination>? navigationDestinations;
  final int selectedIndex;
  final ValueChanged<int>? onDestinationSelected;

  const AdaptiveScaffold({
    Key? key,
    required this.config,
    this.appBar,
    required this.body,
    this.floatingActions,
    this.drawer,
    this.navigationDestinations,
    this.selectedIndex = 0,
    this.onDestinationSelected,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ResponsiveBuilder(
      builder: (context, sizingInformation) {
        final deviceType = _getDeviceType(sizingInformation);

        switch (deviceType) {
          case DeviceType.mobile:
            return _buildMobileScaffold(context);
          case DeviceType.tablet:
          case DeviceType.foldable:
            return _buildTabletScaffold(context);
          case DeviceType.desktop:
            return _buildDesktopScaffold(context);
        }
      },
    );
  }

  Widget _buildMobileScaffold(BuildContext context) {
    return Scaffold(
      appBar: appBar,
      body: body,
      floatingActionButton: floatingActions?.isNotEmpty == true
          ? floatingActions!.first
          : null,
      bottomNavigationBar: navigationDestinations != null
          ? NavigationBar(
              selectedIndex: selectedIndex,
              onDestinationSelected: onDestinationSelected,
              destinations: navigationDestinations!,
            )
          : null,
    );
  }

  Widget _buildTabletScaffold(BuildContext context) {
    return Scaffold(
      appBar: appBar,
      body: Row(
        children: [
          // 侧边导航栏
          if (config.showNavigationRail && navigationDestinations != null)
            NavigationRail(
              width: config.navigationRailWidth.toDouble(),
              selectedIndex: selectedIndex,
              onDestinationSelected: onDestinationSelected,
              destinations: navigationDestinations!,
            ),

          // 抽屉菜单
          if (config.enableDrawer && drawer != null)
            Drawer(
              width: 300,
              child: drawer,
            ),

          // 主内容区
          Expanded(
            child: _buildConstrainedBody(),
          ),
        ],
      ),
      floatingActionButton: floatingActions?.isNotEmpty == true
          ? floatingActions!.first
          : null,
    );
  }

  Widget _buildDesktopScaffold(BuildContext context) {
    return Scaffold(
      appBar: appBar,
      body: Row(
        children: [
          // 宽导航栏
          if (config.showNavigationRail && navigationDestinations != null)
            NavigationRail(
              width: config.navigationRailWidth.toDouble(),
              selectedIndex: selectedIndex,
              onDestinationSelected: onDestinationSelected,
              destinations: navigationDestinations!,
            ),

          // 主内容区(限制最大宽度)
          Expanded(
            child: Center(
              child: ConstrainedBox(
                constraints: BoxConstraints(
                  maxWidth: config.maxContentWidth ?? double.infinity,
                ),
                child: _buildConstrainedBody(),
              ),
            ),
          ),
        ],
      ),
      floatingActionButton: floatingActions?.isNotEmpty == true
          ? floatingActions!.first
          : null,
    );
  }

  Widget _buildConstrainedBody() {
    return body ?? const SizedBox();
  }

  DeviceType _getDeviceType(SizingInformation sizingInformation) {
    switch (sizingInformation.deviceScreenType) {
      case DeviceScreenType.desktop:
        return DeviceType.desktop;
      case DeviceScreenType.tablet:
        return DeviceType.tablet;
      case DeviceScreenType.foldable:
        return DeviceType.foldable;
      default:
        return DeviceType.mobile;
    }
  }
}

4.3 新闻卡片响应式实现

dart 复制代码
// lib/features/home/presentation/widgets/news_card.dart
import 'package:flutter/material.dart';
import 'package:responsive_builder/responsive_builder.dart';

/// 新闻文章模型
class NewsArticle {
  final String id;
  final String title;
  final String summary;
  final String imageUrl;
  final String category;
  final DateTime publishTime;
  final String source;

  NewsArticle({
    required this.id,
    required this.title,
    required this.summary,
    required this.imageUrl,
    required this.category,
    required this.publishTime,
    required this.source,
  });
}

/// 新闻卡片组件
class NewsCard extends StatelessWidget {
  final NewsArticle article;
  final VoidCallback? onTap;
  final VoidCallback? onFavorite;

  const NewsCard({
    Key? key,
    required this.article,
    this.onTap,
    this.onFavorite,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ResponsiveBuilder(
      builder: (context, sizingInformation) {
        // 根据屏幕大小调整卡片样式
        final isMobile = sizingInformation.deviceScreenType == DeviceScreenType.mobile;
        final isTablet = sizingInformation.deviceScreenType == DeviceScreenType.tablet;

        return Card(
          margin: EdgeInsets.symmetric(
            horizontal: isMobile ? 12 : 16,
            vertical: isMobile ? 6 : 8,
          ),
          clipBehavior: Clip.antiAlias,
          child: InkWell(
            onTap: onTap,
            child: _buildCardContent(context, isMobile, isTablet),
          ),
        );
      },
    );
  }

  Widget _buildCardContent(
    BuildContext context,
    bool isMobile,
    bool isTablet,
  ) {
    // 手机端:垂直布局
    if (isMobile) {
      return Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _buildImage(),
          _buildTitleAndSummary(isVertical: true),
          _buildFooter(),
        ],
      );
    }

    // 平板端:水平布局
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        _buildImage(width: 200),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _buildTitleAndSummary(isVertical: false),
              const Spacer(),
              _buildFooter(),
            ],
          ),
        ),
      ],
    );
  }

  Widget _buildImage({double? width}) {
    return CachedNetworkImage(
      imageUrl: article.imageUrl,
      width: width ?? double.infinity,
      height: 200,
      fit: BoxFit.cover,
      placeholder: (context, url) => Container(
        width: width ?? double.infinity,
        height: 200,
        color: Colors.grey[200],
        child: const Center(
          child: CircularProgressIndicator(),
        ),
      ),
      errorWidget: (context, url, error) => Container(
        width: width ?? double.infinity,
        height: 200,
        color: Colors.grey[300],
        child: const Icon(Icons.broken_image),
      ),
    );
  }

  Widget _buildTitleAndSummary({required bool isVertical}) {
    return Padding(
      padding: const EdgeInsets.all(12),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _buildCategoryChip(),
          const SizedBox(height: 8),
          Text(
            article.title,
            style: Theme.of(context).textTheme.titleMedium?.copyWith(
              fontWeight: FontWeight.bold,
            ),
            maxLines: isVertical ? 2 : 3,
            overflow: TextOverflow.ellipsis,
          ),
          const SizedBox(height: 8),
          Text(
            article.summary,
            style: Theme.of(context).textTheme.bodyMedium,
            maxLines: isVertical ? 2 : 4,
            overflow: TextOverflow.ellipsis,
            color: Colors.grey[600],
          ),
        ],
      ),
    );
  }

  Widget _buildCategoryChip() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
      decoration: BoxDecoration(
        color: _getCategoryColor(article.category),
        borderRadius: BorderRadius.circular(12),
      ),
      child: Text(
        article.category,
        style: const TextStyle(
          fontSize: 12,
          fontWeight: FontWeight.w500,
          color: Colors.white,
        ),
      ),
    );
  }

  Widget _buildFooter() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      child: Row(
        children: [
          Icon(Icons.source, size: 14, color: Colors.grey[600]),
          const SizedBox(width: 4),
          Text(
            article.source,
            style: Theme.of(context).textTheme.bodySmall?.copyWith(
              color: Colors.grey[600],
            ),
          ),
          const Spacer(),
          Icon(Icons.access_time, size: 14, color: Colors.grey[600]),
          const SizedBox(width: 4),
          Text(
            _formatPublishTime(article.publishTime),
            style: Theme.of(context).textTheme.bodySmall?.copyWith(
              color: Colors.grey[600],
            ),
          ),
        ],
      ),
    );
  }

  Color _getCategoryColor(String category) {
    final categoryColors = {
      '科技': Colors.blue,
      '财经': Colors.green,
      '体育': Colors.orange,
      '娱乐': Colors.purple,
      '生活': Colors.teal,
    };

    return categoryColors[category] ?? Colors.grey;
  }

  String _formatPublishTime(DateTime time) {
    final now = DateTime.now();
    final difference = now.difference(time);

    if (difference.inMinutes < 60) {
      return '${difference.inMinutes}分钟前';
    } else if (difference.inHours < 24) {
      return '${difference.inHours}小时前';
    } else {
      return '${time.month}月${time.day}日';
    }
  }
}

五、鸿蒙平台专项优化

5.1 启动速度优化

dart 复制代码
// lib/core/config/startup_optimizer.dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

/// 启动优化配置
class StartupOptimizer {
  static const String _keyFirstLaunch = 'first_launch';
  static const String _keyLastStartup = 'last_startup';
  static const String _keyCachedData = 'cached_startup_data';

  /// 初始化优化器
  static Future<void> init() async {
    final prefs = await SharedPreferences.getInstance();

    // 检查是否首次启动
    final isFirstLaunch = !prefs.containsKey(_keyFirstLaunch);

    if (isFirstLaunch) {
      await _optimizeFirstLaunch(prefs);
    } else {
      await _optimizeSubsequentLaunch(prefs);
    }
  }

  /// 首次启动优化
  static Future<void> _optimizeFirstLaunch(SharedPreferences prefs) async {
    AppLogger.info('StartupOptimizer', '首次启动优化');

    // 标记首次启动完成
    await prefs.setBool(_keyFirstLaunch, true);

    // 预加载关键数据
    await _preloadCriticalData();

    // 预编译 Dart 代码(AOT)
    await _warmupCriticalPaths();
  }

  /// 后续启动优化
  static Future<void> _optimizeSubsequentLaunch(SharedPreferences prefs) async {
    final lastStartup = prefs.getString(_keyLastStartup);

    if (lastStartup != null) {
      final lastTime = DateTime.parse(lastStartup);
      final daysSinceLastStartup = DateTime.now().difference(lastTime).inDays;

      // 超过 7 天未使用,清理缓存
      if (daysSinceLastStartup > 7) {
        await _clearStaleCache();
      }
    }

    await prefs.setString(_keyLastStartup, DateTime.now().toIso8601String());

    // 异步恢复数据
    _asyncRestoreData();
  }

  /// 预加载关键数据
  static Future<void> _preloadCriticalData() async {
    AppLogger.info('StartupOptimizer', '预加载关键数据');

    // 并行加载关键数据
    await Future.wait([
      _loadConfigurations(),
      _loadUserPreferences(),
      _prepareOfflineData(),
    ]);
  }

  /// 加载配置
  static Future<void> _loadConfigurations() async {
    // 加载应用配置
    await AppConfig.load();
  }

  /// 加载用户偏好
  static Future<void> _loadUserPreferences() async {
    // 加载用户偏好设置
    final prefs = await SharedPreferences.getInstance();

    final themeMode = prefs.getString('theme_mode') ?? 'system';
    final language = prefs.getString('language') ?? 'system';
    final fontSize = prefs.getDouble('font_size') ?? 1.0;

    // 应用到全局配置
    AppConfig.updateTheme(themeMode);
    AppConfig.updateLanguage(language);
    AppConfig.updateFontSize(fontSize);
  }

  /// 准备离线数据
  static Future<void> _prepareOfflineData() async {
    // 预缓存首页新闻数据
    final newsRepository = NewsRepository();
    await newsRepository.cacheTopHeadlines();

    // 预加载分类数据
    final categories = await newsRepository.getCategories();
    await prefs.setString('cached_categories', jsonEncode(categories));
  }

  /// 预热关键路径
  static Future<void> _warmupCriticalPaths() async {
    AppLogger.info('StartupOptimizer', '预热关键路径');

    // 预加载常用路由
    _preloadRoutes();
  }

  /// 预加载路由
  static void _preloadRoutes() {
    // 预加载首页路由
    runApp(
      MaterialApp(
        home: const SplashPage(),
        routes: {
          '/home': (context) => const HomePage(),
          '/detail': (context) => const NewsDetailPage(),
          '/settings': (context) => const SettingsPage(),
        },
      ),
    );
  }

  /// 异步恢复数据
  static void _asyncRestoreData() {
    AppLogger.info('StartupOptimizer', '异步恢复数据');

    // 不阻塞启动,后台恢复
    Future.microtask(() async {
      await Future.wait([
        _restoreUserSession(),
        _restorePushToken(),
        _syncOfflineChanges(),
      ]);
    });
  }

  /// 恢复用户会话
  static Future<void> _restoreUserSession() async {
    final accountService = HmsAccountService();
    final userInfo = await accountService.silentSignIn();

    if (userInfo != null) {
      AppLogger.info('StartupOptimizer', '用户会话已恢复');
    }
  }

  /// 恢复推送 Token
  static Future<void> _restorePushToken() async {
    final prefs = await SharedPreferences.getInstance();
    final token = prefs.getString('push_token');

    if (token != null && token.isNotEmpty) {
      final pushService = HmsPushService();
      await pushService.init();
      AppLogger.info('StartupOptimizer', '推送 Token 已恢复');
    }
  }

  /// 同步离线变更
  static Future<void> _syncOfflineChanges() async {
    // 同步离线期间的用户操作
    final syncService = SyncService();
    await syncService.syncPendingChanges();
  }

  /// 清除过期缓存
  static Future<void> _clearStaleCache() async {
    AppLogger.info('StartupOptimizer', '清除过期缓存');

    final prefs = await SharedPreferences.getInstance();

    // 清除旧的缓存数据
    await prefs.remove('cached_home_data');
    await prefs.remove('cached_categories');

    // 清除数据库旧数据
    final database = await AppDatabase.getInstance();
    await database.clearOldRecords(days: 30);
  }
}

5.2 后台保活策略

dart 复制代码
// lib/services/background/background_keep_alive_service.dart
import 'dart:io';
import 'package:flutter/services.dart';

/// 后台任务类型
enum BackgroundTaskType {
  dataSync,
  contentRefresh,
  pushHeartbeat,
  analyticsUpload,
}

/// 后台保活服务
class BackgroundKeepAliveService {
  static BackgroundKeepAliveService? _instance;
  static const MethodChannel _channel =
      MethodChannel('com.example.newshub/background');

  factory BackgroundKeepAliveService() {
    _instance ??= BackgroundKeepAliveService._internal();
    return _instance!;
  }

  BackgroundKeepAliveService._internal() {
    _setupMethodCallHandler();
  }

  /// 初始化后台保活
  Future<void> init() async {
    try {
      // 检查是否为鸿蒙平台
      if (Platform.isAndroid) {
        final isHarmonyOS = await _checkHarmonyOS();

        if (isHarmonyOS) {
          await _initHarmonyBackgroundTasks();
        } else {
          await _initAndroidBackgroundTasks();
        }
      }

      AppLogger.info('BackgroundService', '后台保活初始化成功');
    } catch (e) {
      AppLogger.error('BackgroundService', '初始化失败: $e');
    }
  }

  /// 检测鸿蒙平台
  Future<bool> _checkHarmonyOS() async {
    try {
      final result = await _channel.invokeMethod('checkPlatform');
      return result == 'harmonyos';
    } catch (e) {
      return false;
    }
  }

  /// 初始化鸿蒙后台任务
  Future<void> _initHarmonyBackgroundTasks() async {
    // 启动心跳任务
    await _startHeartbeatTask();

    // 启动数据同步任务
    await _startDataSyncTask();

    // 请求后台运行权限
    await _requestBackgroundPermission();
  }

  /// 启动心跳任务
  Future<void> _startHeartbeatTask() async {
    try {
      await _channel.invokeMethod('startHeartbeatTask', {
        'interval': 15 * 60 * 1000, // 15 分钟
      });
      AppLogger.info('BackgroundService', '心跳任务已启动');
    } catch (e) {
      AppLogger.error('BackgroundService', '启动心跳任务失败: $e');
    }
  }

  /// 启动数据同步任务
  Future<void> _startDataSyncTask() async {
    try {
      await _channel.invokeMethod('startDataSyncTask', {
        'interval': 60 * 60 * 1000, // 1 小时
        'networkRequired': true,
      });
      AppLogger.info('BackgroundService', '数据同步任务已启动');
    } catch (e) {
      AppLogger.error('BackgroundService', '启动数据同步任务失败: $e');
    }
  }

  /// 请求后台运行权限
  Future<void> _requestBackgroundPermission() async {
    try {
      await _channel.invokeMethod('requestBackgroundPermission');
    } catch (e) {
      AppLogger.error('BackgroundService', '请求后台权限失败: $e');
    }
  }

  /// 初始化 Android 后台任务
  Future<void> _initAndroidBackgroundTasks() async {
    // Android 使用 WorkManager 或 Foreground Service
    await _channel.invokeMethod('initAndroidBackground');
  }

  /// 设置方法调用处理器
  void _setupMethodCallHandler() {
    _channel.setMethodCallHandler((call) async {
      switch (call.method) {
        case 'onBackgroundTask':
          final taskType = call.arguments['taskType'];
          await _handleBackgroundTask(taskType);
          break;
        case 'onTaskRemoved':
          await _handleTaskRemoved();
          break;
        default:
          throw UnimplementedError('未实现的方法: ${call.method}');
      }
    });
  }

  /// 处理后台任务
  Future<void> _handleBackgroundTask(String taskTypeStr) async {
    final taskType = BackgroundTaskType.values.firstWhere(
      (e) => e.toString() == taskTypeStr,
      orElse: () => BackgroundTaskType.dataSync,
    );

    switch (taskType) {
      case BackgroundTaskType.dataSync:
        await _syncData();
        break;
      case BackgroundTaskType.contentRefresh:
        await _refreshContent();
        break;
      case BackgroundTaskType.pushHeartbeat:
        await _sendHeartbeat();
        break;
      case BackgroundTaskType.analyticsUpload:
        await _uploadAnalytics();
        break;
    }
  }

  /// 同步数据
  Future<void> _syncData() async {
    AppLogger.info('BackgroundService', '执行数据同步');

    try {
      final syncService = SyncService();
      await syncService.syncNews();
      await syncService.syncUserPreferences();
    } catch (e) {
      AppLogger.error('BackgroundService', '数据同步失败: $e');
    }
  }

  /// 刷新内容
  Future<void> _refreshContent() async {
    AppLogger.info('BackgroundService', '刷新内容');

    try {
      final newsRepository = NewsRepository();
      await newsRepository.refreshTopHeadlines();

      // 发送更新通知
      final pushService = HmsPushService();
      await pushService.showLocalNotification(
        title: '内容已更新',
        body: '查看最新新闻',
        priority: NotificationPriority.low,
      );
    } catch (e) {
      AppLogger.error('BackgroundService', '内容刷新失败: $e');
    }
  }

  /// 发送心跳
  Future<void> _sendHeartbeat() async {
    AppLogger.info('BackgroundService', '发送心跳');

    try {
      final analyticsService = HmsAnalyticsService();
      await analyticsService.logCustomEvent(
        eventName: 'heartbeat',
        params: {'timestamp': DateTime.now().millisecondsSinceEpoch},
      );
    } catch (e) {
      AppLogger.error('BackgroundService', '心跳失败: $e');
    }
  }

  /// 上传分析数据
  Future<void> _uploadAnalytics() async {
    AppLogger.info('BackgroundService', '上传分析数据');

    try {
      final analyticsService = HmsAnalyticsService();
      await analyticsService.endSession();
      await analyticsService.beginSession();
    } catch (e) {
      AppLogger.error('BackgroundService', '分析上传失败: $e');
    }
  }

  /// 任务被移除
  Future<void> _handleTaskRemoved() async {
    AppLogger.warning('BackgroundService', '后台任务被移除');

    // 尝试重新调度
    await Future.delayed(const Duration(seconds: 5));
    await init();
  }
}

5.3 深色主题适配

dart 复制代码
// lib/core/theme/dark_theme.dart
import 'package:flutter/material.dart';

/// 主题配置类
class ThemeConfig {
  static const String _keyThemeMode = 'theme_mode';

  /// 获取主题模式
  static Future<ThemeMode> getThemeMode() async {
    final prefs = await SharedPreferences.getInstance();
    final savedMode = prefs.getString(_keyThemeMode);

    switch (savedMode) {
      case 'light':
        return ThemeMode.light;
      case 'dark':
        return ThemeMode.dark;
      default:
        return ThemeMode.system;
    }
  }

  /// 设置主题模式
  static Future<void> setThemeMode(ThemeMode mode) async {
    final prefs = await SharedPreferences.getInstance();
    final modeString = mode == ThemeMode.light
        ? 'light'
        : mode == ThemeMode.dark
            ? 'dark'
            : 'system';

    await prefs.setString(_keyThemeMode, modeString);
  }

  /// 构建亮色主题
  static ThemeData buildLightTheme() {
    return ThemeData(
      useMaterial3: true,
      brightness: Brightness.light,
      colorScheme: _lightColorScheme,
      scaffoldBackgroundColor: _lightColorScheme.surface,
      appBarTheme: AppBarTheme(
        backgroundColor: _lightColorScheme.primary,
        foregroundColor: _lightColorScheme.onPrimary,
        elevation: 0,
        centerTitle: true,
      ),
      cardTheme: CardTheme(
        color: _lightColorScheme.surface,
        elevation: 1,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
        ),
      ),
      navigationBarTheme: NavigationBarTheme(
        backgroundColor: _lightColorScheme.surface,
        indicatorColor: _lightColorScheme.primary,
      ),
    );
  }

  /// 构建深色主题
  static ThemeData buildDarkTheme() {
    return ThemeData(
      useMaterial3: 3,
      brightness: Brightness.dark,
      colorScheme: _darkColorScheme,
      scaffoldBackgroundColor: _darkColorScheme.surface,
      appBarTheme: AppBarTheme(
        backgroundColor: _darkColorScheme.surface,
        foregroundColor: _darkColorScheme.onSurface,
        elevation: 0,
        centerTitle: true,
      ),
      cardTheme: CardTheme(
        color: _darkColorScheme.surfaceVariant,
        elevation: 1,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
        ),
      ),
      navigationBarTheme: NavigationBarTheme(
        backgroundColor: _darkColorScheme.surface,
        indicatorColor: _darkColorScheme.primary,
      ),
    );
  }

  /// 亮色配色方案
  static const ColorScheme _lightColorScheme = ColorScheme.light(
    primary: Color(0xFF1976D2),
    onPrimary: Colors.white,
    primaryContainer: Color(0xFFBBDEFB),
    onPrimaryContainer: Color(0xFF0D47A1),
    secondary: Color(0xFF26A69A),
    onSecondary: Colors.white,
    secondaryContainer: Color(0xFFB2DFDB),
    onSecondaryContainer: Color(0xFF004D40),
    tertiary: Color(0xFFFF9800),
    onTertiary: Colors.white,
    tertiaryContainer: Color(0xFFFFCC80),
    onTertiaryContainer: Color(0xFF5E3A00),
    error: Color(0xFFD32F2F),
    onError: Colors.white,
    background: Color(0xFFFAFAFA),
    onBackground: Color(0xFF121212),
    surface: Color(0xFFFFFFFF),
    onSurface: Color(0xFF121212),
    surfaceVariant: Color(0xFFF5F5F5),
    onSurfaceVariant: Color(0xFF121212),
    outline: Color(0xFFE0E0E0),
    outlineVariant: Color(0xFFC2C2C2),
    shadow: Color(0xFF000000),
    scrim: Color(0xFF000000),
    inverseSurface: Color(0xFF121212),
    onInverseSurface: Color(0xFFE0E0E0),
    inversePrimary: Color(0xFF90CAF9),
  );

  /// 深色配色方案
  static const ColorScheme _darkColorScheme = ColorScheme.dark(
    primary: Color(0xFF90CAF9),
    onPrimary: Color(0xFF000000),
    primaryContainer: Color(0xFF0D47A1),
    onPrimaryContainer: Color(0xFFBBDEFB),
    secondary: Color(0xFF80CBC4),
    onSecondary: Color(0xFF000000),
    secondaryContainer: Color(0xFF004D40),
    onSecondaryContainer: Color(0xFFB2DFDB),
    tertiary: Color(0xFFFFB74D),
    onTertiary: Color(0xFF000000),
    tertiaryContainer: Color(0xFF5E3A00),
    onTertiaryContainer: Color(0xFFFFCC80),
    error: Color(0xFFFF5252),
    onError: Color(0xFF000000),
    background: Color(0xFF121212),
    onBackground: Color(0xFFE0E0E0),
    surface: Color(0xFF1E1E1E),
    onSurface: Color(0xFFE0E0E0),
    surfaceVariant: Color(0xFF2C2C2C),
    onSurfaceVariant: Color(0xFFE0E0E0),
    outline: Color(0xFF3C3C3C),
    outlineVariant: Color(0xFF4C4C4C),
    shadow: Color(0xFF000000),
    scrim: Color(0xFF000000),
    inverseSurface: Color(0xFFE0E0E0),
    onInverseSurface: Color(0xFF121212),
    inversePrimary: Color(0xFF1976D2),
  );
}

六、.hap 包打包与上架

6.1 HarmonyOS 构建配置

json5 复制代码
// build-profile.json5
{
  "apiType": "stageMode",
  "buildOption": {
    "externalNativeOptions": {
      "path": "./src/main/cpp/CMakeLists.txt",
      "arguments": "",
      "abiFilters": [
        "armeabi-v7a",
        "arm64-v8a"
      ],
      "cppFlags": ""
    }
  },
  "targets": [
    {
      "name": "default",
      "runtimeOS": "HarmonyOS"
    }
  ]
}
json5 复制代码
// ohos/entry/build-profile.json5
{
  "apiType": "stageMode",
  "buildOption": {},
  "targets": [
    {
      "name": "default",
      "runtimeOS": "HarmonyOS"
    }
  ],
  "hapPackages": [
    {
      "name": "entry",
      "moduleName": "entry",
      "outPath": "../build/harmonyos/outputs/hap"
    }
  ]
}

6.2 Flutter 鸿蒙打包脚本

bash 复制代码
#!/bin/bash
# build_harmonyos.sh

echo "开始构建 NewsHub HarmonyOS 版本..."

# 1. 清理旧构建
flutter clean

# 2. 获取依赖
flutter pub get

# 3. 构建 Flutter 鸿蒙产物
flutter build harmonyos --release

# 4. 进入鸿蒙项目目录
cd ohos

# 5. 使用 DevEco 构建工具
# 构建前先执行 hvigorw assembleHap

# 6. 签名配置
# 需要在本地配置签名证书

# 7. 最终产物路径
# build/harmonyos/outputs/hap/release/entry-default-signed.hap

echo "构建完成!产物位于: build/harmonyos/outputs/hap/"

6.3 AppGallery 上架清单

检查项 要求 说明
应用图标 512x512 PNG 符合华为设计规范
应用截图 至少 3 张 手机、平板各尺寸
应用描述 中文/英文 80-4000 字符
隐私政策 必填项 HTTPS 链接
内容分级 正确填写 年龄分级
权限说明 详尽说明 用户可理解
测试账号 如需登录 提供测试账号
关键词 准确描述 提升搜索排名

七、总结

7.1 技术要点总结

本教程覆盖了 Flutter 跨平台开发的完整流程,特别是在鸿蒙平台上的适配:

  1. 一套代码,三端运行

    • Android、iOS、HarmonyOS 统一业务逻辑
    • 平台特定代码隔离
  2. HMS 服务完整集成

    • Push Kit: 消息推送
    • Account Kit: 账号登录
    • Analytics Kit: 数据分析
  3. 响应式 UI 设计

    • 手机/平板/折叠屏自适应
    • 深色主题支持
  4. 鸿蒙平台专项优化

    • 启动速度优化
    • 后台保活策略
    • 性能调优
  5. 完整打包上架流程

    • .hap 包构建
    • AppGallery 上架

7.2 技术栈总览

复制代码
┌─────────────────────────────────────────────────────────────┐
│              NewsHub 技术栈全景图                      │
├─────────────────────────────────────────────────────────────┤
│                                                            │
│  ┌──────────────────────────────────────────────┐         │
│  │          Flutter 跨平台框架                    │         │
│  │  ┌─────────────────────────────────────┐      │         │
│  │  │  Dart 语言 + Flutter Engine         │      │         │
│  │  │  - BLoC 状态管理                 │      │         │
│  │  │  - responsive_builder              │      │         │
│  │  └─────────────────────────────────────┘      │         │
│  └──────────────────────────────────────────────┘         │
│                                                            │
│  ┌──────────────────────────────────────────────┐         │
│  │          HMS Core 服务                       │         │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐│         │
│  │  │ Push Kit  │  │Account Kit│  │Analytics ││         │
│  │  │ 消息推送  │  │账号登录  │  │数据分析  ││         │
│  │  └──────────┘  └──────────┘  └──────────┘│         │
│  │  ┌─────────────────────────────────────────┐    │         │
│  │  │       flutter_hms_sdk 社区插件       │    │         │
│  │  └─────────────────────────────────────────┘    │         │
│  └──────────────────────────────────────────────┘         │
│                                                            │
│  ┌──────────────────────────────────────────────┐         │
│  │          HarmonyOS 原生层                   │         │
│  │  - DevEco Studio 项目配置                   │         │
│  │  - ArkTS 适配层                          │         │
│  │  - .hap 包打包                             │         │
│  └──────────────────────────────────────────────┘         │
│                                                            │
└─────────────────────────────────────────────────────────────┘

7.3 后续扩展方向

  • 集成 HMS Location Kit 实现本地新闻推荐
  • 使用 HMS Scan Kit 实现新闻二维码分享
  • 集成 HMS ML Kit 实现新闻内容分类
  • 添加折叠屏展开/折叠状态监听
  • 实现 TV 大屏适配
  • 添加手表端应用

关键词: Flutter、HarmonyOS、HMS Core、Push Kit、跨平台、responsive_builder、DevEco Studio、.hap包

项目完整代码: 请参考项目目录结构实现各模块

作者注: 本教程基于 Flutter 3.13+ 和 HarmonyOS API 9+,建议在真机上测试推送功能。

相关推荐
无巧不成书02182 小时前
【开源鸿蒙+Flutter实战】Step One复盘(DAY1-7)|环境闭环+网络请求+列表交互 全避坑(真机验证版)
flutter·开源·harmonyos
阿林来了3 小时前
Flutter三方库适配OpenHarmony【flutter_speech】— 项目概述与适配背景
flutter·harmonyos·鸿蒙
HwJack203 小时前
HarmonyOS APP ArkTS开发之ArkUI系统组件
华为·harmonyos
哈__4 小时前
基础入门 Flutter for OpenHarmony:share_extend 系统分享详解
flutter
思考着亮5 小时前
1.Flutter 环境配置 & Shell 基础知识笔记
flutter
哈__6 小时前
基础入门 Flutter for OpenHarmony:fluttertoast 消息提示详解
flutter
特立独行的猫a6 小时前
Kuikly多端框架(KMP)实战:现代Android/KMP状态管理指南:基于StateFlow与UDF架构的实践
android·架构·harmonyos·状态管理·kmp·stateflow·kuikly
哈__9 小时前
基础入门 Flutter for OpenHarmony:SystemChrome 屏幕方向控制详解
flutter
ITUnicorn9 小时前
【HarmonyOS 6】数据可视化:实现热力图时间块展示
华为·harmonyos·arkts·鸿蒙·harmonyos6