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

本文基于flutter3.27.5开发

在这里插入图片描述

一、connectivity_plus 库概述

网络连接状态检测是移动应用开发中的基础需求,应用需要根据网络状态来优化数据加载策略、提示用户网络异常或切换离线模式。在 Flutter for OpenHarmony 应用开发中,connectivity_plus 是一个功能完善的网络连接状态检测插件,提供了跨平台的网络状态获取能力。

connectivity_plus 库特点

connectivity_plus 库基于 Flutter 平台接口实现,提供了以下核心特性:

网络状态检测:支持检测当前设备的网络连接状态,区分 WiFi、蜂窝网络、以太网、蓝牙等多种连接类型。

实时状态监听:提供 Stream 接口,实时监听网络状态变化,无需轮询,性能更优。

多网络类型支持:支持检测 WiFi、蜂窝网络(mobile)、以太网(ethernet)、蓝牙(bluetooth)、VPN 等多种网络类型。

跨平台统一 API:提供统一的 Dart API,开发者无需关心底层平台差异,一套代码多端运行。

轻量级设计:插件体积小,性能开销低,适合各种规模的应用。

功能支持对比

功能 Android iOS OpenHarmony
检测网络状态
区分网络类型
网络状态变化监听
VPN 检测 ⚠️
蓝牙网络检测

注意:iOS/macOS 平台 VPN 连接返回 other,OpenHarmony 平台可以正确识别 VPN 连接。

使用场景:网络状态监控、离线模式切换、网络类型判断、网络异常提示、数据加载策略优化等。


二、安装与配置

2.1 添加依赖

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

dependencies:
  connectivity_plus:
    git:
      url: https://atomgit.com/openharmony-sig/flutter_plus_plugins.git
      path: packages/connectivity_plus/connectivity_plus

然后执行以下命令获取依赖:

flutter pub get

2.2 兼容性信息

项目 版本要求
Flutter SDK 3.7.12-ohos-1.0.6
OpenHarmony SDK 5.0.0 (API 12)
DevEco Studio 5.0.13.200
ROM 5.1.0.120 SP3

2.3 权限配置

connectivity_plus 在 OpenHarmony 平台上需要配置网络权限。在 ohos/entry/src/main/module.json5 文件中添加:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}

注意:只需要 ohos.permission.INTERNET 权限即可,不需要额外的网络状态权限。


三、核心 API 详解

3.1 Connectivity 类

Connectivity 是 connectivity_plus 库的核心类,采用单例模式设计,提供网络状态检测的所有方法。

class Connectivity {
  factory Connectivity();
  
  Future<ConnectivityResult> checkConnectivity();
  Stream<ConnectivityResult> get onConnectivityChanged;
}

使用示例

final connectivity = Connectivity();

3.2 checkConnectivity 方法

checkConnectivity 方法用于检查当前网络连接状态。

Future<ConnectivityResult> checkConnectivity()

返回值:返回 ConnectivityResult 枚举值,表示当前网络连接类型。

使用示例

final connectivity = Connectivity();
final result = await connectivity.checkConnectivity();

if (result == ConnectivityResult.mobile) {
  print('连接到蜂窝网络');
} else if (result == ConnectivityResult.wifi) {
  print('连接到 WiFi 网络');
} else if (result == ConnectivityResult.ethernet) {
  print('连接到以太网');
} else if (result == ConnectivityResult.bluetooth) {
  print('连接到蓝牙网络');
} else if (result == ConnectivityResult.vpn) {
  print('连接到 VPN');
} else if (result == ConnectivityResult.other) {
  print('连接到其他网络');
} else if (result == ConnectivityResult.none) {
  print('无网络连接');
}

3.3 onConnectivityChanged 属性

onConnectivityChanged 属性是一个 Stream,用于监听网络状态变化。

Stream<ConnectivityResult> get onConnectivityChanged

返回值:返回 ConnectivityResult 的 Stream,每次网络状态变化时触发。

使用示例

final connectivity = Connectivity();
StreamSubscription<ConnectivityResult> subscription;


void initState() {
  super.initState();
  subscription = connectivity.onConnectivityChanged.listen((ConnectivityResult result) {
    print('网络状态变化: $result');
    if (result == ConnectivityResult.none) {
      _showNetworkError();
    }
  });
}


void dispose() {
  subscription.cancel();
  super.dispose();
}

3.4 ConnectivityResult 枚举

ConnectivityResult 枚举定义了所有可能的网络连接类型。

enum ConnectivityResult {
  bluetooth,
  wifi,
  ethernet,
  mobile,
  none,
  vpn,
  other,
}

枚举值说明

枚举值 说明 OpenHarmony 支持
bluetooth 通过蓝牙连接的设备
wifi 通过 WiFi 连接的设备
ethernet 连接到以太网的设备
mobile 连接到蜂窝网络的设备
none 未连接到任何网络
vpn 连接到 VPN 的设备
other 连接到未知网络

四、OpenHarmony 平台实现原理

4.1 原生 API 映射

connectivity_plus 在 OpenHarmony 平台上使用 @ohos.net.connection 模块实现:

Flutter API OpenHarmony API
checkConnectivity() connection.getDefaultNet()
connection.getNetCapabilities()
onConnectivityChanged connection.createNetConnection()

4.2 网络类型映射

OpenHarmony 的 NetBearType 与 Flutter 的 ConnectivityResult 映射关系:

OpenHarmony 状态 Flutter 状态
BEARER_WIFI wifi
BEARER_CELLULAR mobile
BEARER_ETHERNET ethernet
netId == 0 none
其他 none

4.3 实现原理

OpenHarmony 平台通过以下步骤实现网络状态检测:

1. 获取默认网络句柄

const netHandle = await connection.getDefaultNet();
if (netHandle.netId == 0) {
  return Connectivity.CONNECTIVITY_NONE;
}

2. 获取网络能力信息

const capabilities = await connection.getNetCapabilities(netHandle);
this.networkType = this.hasTransport(capabilities.bearerTypes);

3. 判断网络类型

hasTransport(capabilities: Array<number>): String {
  if (capabilities.includes(connection.NetBearType.BEARER_WIFI)) {
    return Connectivity.CONNECTIVITY_WIFI;
  }
  if (capabilities.includes(connection.NetBearType.BEARER_ETHERNET)) {
    return Connectivity.CONNECTIVITY_ETHERNET;
  }
  if (capabilities.includes(connection.NetBearType.BEARER_CELLULAR)) {
    return Connectivity.CONNECTIVITY_MOBILE;
  }
  return Connectivity.CONNECTIVITY_NONE;
}

4. 网络状态监听

通过 connection.createNetConnection() 创建网络连接对象,监听网络状态变化事件。


五、实战案例

5.1 检查网络状态

Future<void> checkNetworkStatus() async {
  final connectivity = Connectivity();
  final result = await connectivity.checkConnectivity();
  
  switch (result) {
    case ConnectivityResult.mobile:
      print('使用蜂窝网络');
      break;
    case ConnectivityResult.wifi:
      print('使用 WiFi 网络');
      break;
    case ConnectivityResult.ethernet:
      print('使用以太网');
      break;
    case ConnectivityResult.none:
      print('无网络连接');
      break;
    default:
      print('其他网络类型');
  }
}

5.2 监听网络状态变化

StreamSubscription<ConnectivityResult>? _subscription;

void startNetworkListener() {
  final connectivity = Connectivity();
  _subscription = connectivity.onConnectivityChanged.listen((result) {
    if (result == ConnectivityResult.none) {
      print('网络已断开');
    } else {
      print('网络已连接: $result');
    }
  });
}

void stopNetworkListener() {
  _subscription?.cancel();
  _subscription = null;
}

5.3 判断是否有网络连接

Future<bool> hasNetworkConnection() async {
  final connectivity = Connectivity();
  final result = await connectivity.checkConnectivity();
  return result != ConnectivityResult.none;
}

5.4 根据网络类型优化加载策略

Future<void> loadData() async {
  final connectivity = Connectivity();
  final result = await connectivity.checkConnectivity();
  
  if (result == ConnectivityResult.wifi) {
    print('WiFi 环境,加载高清图片');
  } else if (result == ConnectivityResult.mobile) {
    print('蜂窝网络,加载标清图片');
  } else if (result == ConnectivityResult.none) {
    print('无网络,显示缓存数据');
  }
}

5.5 完整的网络状态监控组件

class NetworkMonitor extends StatefulWidget {
  const NetworkMonitor({super.key});

  
  State<NetworkMonitor> createState() => _NetworkMonitorState();
}

class _NetworkMonitorState extends State<NetworkMonitor> {
  final Connectivity _connectivity = Connectivity();
  ConnectivityResult _connectionStatus = ConnectivityResult.none;
  StreamSubscription<ConnectivityResult>? _subscription;
  List<String> _statusHistory = [];

  
  void initState() {
    super.initState();
    _initConnectivity();
    _listenConnectivity();
  }

  Future<void> _initConnectivity() async {
    try {
      final result = await _connectivity.checkConnectivity();
      setState(() {
        _connectionStatus = result;
      });
    } catch (e) {
      print('初始化网络状态失败: $e');
    }
  }

  void _listenConnectivity() {
    _subscription = _connectivity.onConnectivityChanged.listen((result) {
      setState(() {
        _connectionStatus = result;
        _statusHistory.insert(0, '${DateTime.now().toString().substring(11, 19)} - ${_getStatusText(result)}');
        if (_statusHistory.length > 10) {
          _statusHistory.removeLast();
        }
      });
    });
  }

  String _getStatusText(ConnectivityResult result) {
    switch (result) {
      case ConnectivityResult.mobile:
        return '蜂窝网络';
      case ConnectivityResult.wifi:
        return 'WiFi 网络';
      case ConnectivityResult.ethernet:
        return '以太网';
      case ConnectivityResult.bluetooth:
        return '蓝牙网络';
      case ConnectivityResult.vpn:
        return 'VPN 网络';
      case ConnectivityResult.other:
        return '其他网络';
      case ConnectivityResult.none:
        return '无网络连接';
    }
  }

  
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('当前网络: ${_getStatusText(_connectionStatus)}'),
        Text('状态历史: ${_statusHistory.length} 条记录'),
      ],
    );
  }
}

六、最佳实践

6.1 及时取消监听

监听网络状态变化时,务必在组件销毁时取消订阅:


void dispose() {
  _subscription?.cancel();
  super.dispose();
}

6.2 错误处理

对网络状态获取进行错误处理:

Future<void> safeCheckConnectivity() async {
  try {
    final connectivity = Connectivity();
    final result = await connectivity.checkConnectivity();
    print('网络状态: $result');
  } on PlatformException catch (e) {
    print('获取网络状态失败: ${e.message}');
  }
}

6.3 性能优化建议

使用 Stream 监听而不是轮询:

// 不推荐:频繁轮询
Timer.periodic(Duration(seconds: 1), (_) async {
  final result = await connectivity.checkConnectivity();
});

// 推荐:使用 Stream 监听
connectivity.onConnectivityChanged.listen((result) {
  // 自动响应状态变化
});

6.4 应用恢复时检查

Android 8.0+ 后台无法接收网络状态变化,建议在应用恢复时主动检查:

class _MyWidgetState extends State<MyWidget> with WidgetsBindingObserver {
  final Connectivity _connectivity = Connectivity();

  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      _checkConnectivity();
    }
  }

  Future<void> _checkConnectivity() async {
    final result = await _connectivity.checkConnectivity();
    print('网络状态: $result');
  }
}

七、常见问题

Q1:为什么网络状态显示 WiFi 但无法访问互联网?

connectivity_plus 只能检测设备的网络连接状态,不能保证能够访问互联网。例如,设备可能连接到 WiFi,但该 WiFi 没有互联网访问权限。

解决方案:建议在实际网络请求时进行超时和错误处理:

try {
  final response = await http.get(Uri.parse('https://example.com'))
      .timeout(const Duration(seconds: 10));
} catch (e) {
  print('网络请求失败: $e');
}

Q3:如何区分 WiFi 和移动热点?

connectivity_plus 无法区分 WiFi 和移动热点。如果需要区分,可以尝试连接特定服务器并检查响应,或者使用其他网络信息插件。

Q4:VPN 连接在 iOS/macOS 上返回 other?

是的,iOS 和 macOS 没有单独的 VPN 网络接口类型,会返回 ConnectivityResult.other。在 OpenHarmony 上,VPN 连接会正确返回 ConnectivityResult.vpn

Q5:Web 平台支持情况如何?

Web 平台依赖浏览器的 NetworkInformation API,功能受限,只能检测在线/离线状态,部分浏览器可能不支持。


八、总结

connectivity_plus 库为 Flutter for OpenHarmony 开发提供了完整的网络连接状态检测能力。通过简洁的 API,开发者可以轻松检测网络状态并监听网络变化,为应用提供更好的网络体验。该库在鸿蒙平台上已经完成了完整的适配,支持所有核心功能,开发者可以放心使用。


九、完整代码示例

以下是一个完整的可运行示例,展示了 connectivity_plus 库的核心功能:
在这里插入图片描述

main.dart

import 'dart:async';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Connectivity Plus Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final Connectivity _connectivity = Connectivity();
  ConnectivityResult _connectionStatus = ConnectivityResult.none;
  StreamSubscription<ConnectivityResult>? _subscription;
  List<String> _statusHistory = [];

  
  void initState() {
    super.initState();
    _initConnectivity();
    _listenConnectivity();
  }

  Future<void> _initConnectivity() async {
    try {
      final result = await _connectivity.checkConnectivity();
      setState(() {
        _connectionStatus = result;
      });
    } on PlatformException catch (e) {
      _showMessage('获取网络状态失败: ${e.message}');
    }
  }

  void _listenConnectivity() {
    _subscription = _connectivity.onConnectivityChanged.listen((result) {
      setState(() {
        _connectionStatus = result;
        _statusHistory.insert(
          0,
          '${DateTime.now().toString().substring(11, 19)} - ${_getStatusText(result)}',
        );
        if (_statusHistory.length > 10) {
          _statusHistory.removeLast();
        }
      });
    });
  }

  void _showMessage(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Connectivity Plus 演示'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            const Text(
              '网络连接状态',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 24),
            
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  children: [
                    Icon(
                      _getStatusIcon(),
                      size: 80,
                      color: _getStatusColor(),
                    ),
                    const SizedBox(height: 16),
                    Text(
                      _getStatusText(_connectionStatus),
                      style: TextStyle(
                        fontSize: 24,
                        fontWeight: FontWeight.bold,
                        color: _getStatusColor(),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),
            
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      '网络详情',
                      style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                    ),
                    const Divider(),
                    _buildInfoRow('连接类型', _getStatusText(_connectionStatus)),
                    _buildInfoRow('网络可用', _connectionStatus != ConnectivityResult.none ? '是' : '否'),
                    _buildInfoRow('WiFi 连接', _connectionStatus == ConnectivityResult.wifi ? '是' : '否'),
                    _buildInfoRow('蜂窝网络', _connectionStatus == ConnectivityResult.mobile ? '是' : '否'),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),
            
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        const Text(
                          '状态变化历史',
                          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                        ),
                        TextButton(
                          onPressed: () {
                            setState(() {
                              _statusHistory.clear();
                            });
                          },
                          child: const Text('清空'),
                        ),
                      ],
                    ),
                    const Divider(),
                    if (_statusHistory.isEmpty)
                      const Text('暂无状态变化记录')
                    else
                      Column(
                        children: _statusHistory.map((record) {
                          return Padding(
                            padding: const EdgeInsets.symmetric(vertical: 4),
                            child: Text(record),
                          );
                        }).toList(),
                      ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),
            
            ElevatedButton.icon(
              onPressed: _initConnectivity,
              icon: const Icon(Icons.refresh),
              label: const Text('刷新网络状态'),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildInfoRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(label, style: const TextStyle(color: Colors.grey)),
          Text(value, style: const TextStyle(fontWeight: FontWeight.bold)),
        ],
      ),
    );
  }

  String _getStatusText(ConnectivityResult result) {
    switch (result) {
      case ConnectivityResult.mobile:
        return '蜂窝网络';
      case ConnectivityResult.wifi:
        return 'WiFi 网络';
      case ConnectivityResult.ethernet:
        return '以太网';
      case ConnectivityResult.bluetooth:
        return '蓝牙网络';
      case ConnectivityResult.vpn:
        return 'VPN 网络';
      case ConnectivityResult.other:
        return '其他网络';
      case ConnectivityResult.none:
        return '无网络连接';
    }
  }

  IconData _getStatusIcon() {
    switch (_connectionStatus) {
      case ConnectivityResult.mobile:
        return Icons.signal_cellular_4_bar;
      case ConnectivityResult.wifi:
        return Icons.wifi;
      case ConnectivityResult.ethernet:
        return Icons.cable;
      case ConnectivityResult.bluetooth:
        return Icons.bluetooth;
      case ConnectivityResult.vpn:
        return Icons.vpn_key;
      case ConnectivityResult.other:
        return Icons.network_check;
      case ConnectivityResult.none:
        return Icons.signal_wifi_off;
    }
  }

  Color _getStatusColor() {
    return _connectionStatus == ConnectivityResult.none ? Colors.red : Colors.green;
  }
}

十、参考资源

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐