Flutter无线网络扫描插件wifi_scan的使用

发布于 1周前 作者 sinazl 来自 Flutter

Flutter无线网络扫描插件wifi_scan的使用

简介

wifi_scan 是一个允许Flutter应用程序扫描附近可见WiFi接入点的插件。该插件是 WiFiFlutter 套件的一部分,为Flutter提供各种WiFi服务。

WiFiFlutter

Flutter Network pub.dev pub points popularity likes


平台支持

Platform Status Min. Version API Notes
Android ✔️ 16 (J) Scan related APIs in WifiManager For SDK >= 26(O) scans are throttled.
iOS ✔️ 9.0 No public API, requires special entitlements from Apple Stub implementation with sane returns.

使用方法

入口点

插件的入口点是单例实例 WiFiScan.instance

开始扫描

你可以通过 WiFiScan.startScan API 触发完整的WiFi扫描:

void _startScan() async {
  // 检查平台支持和必要的要求
  final can = await WiFiScan.instance.canStartScan(askPermissions: true);
  switch(can) {
    case CanStartScan.yes:
      // 异步启动完整扫描
      final isScanning = await WiFiScan.instance.startScan();
      //...
      break;
    // ... 处理其他 `CanStartScan` 值的情况
  }
}

更多详情请参阅 WiFiScan.startScanWiFiScan.canStartScanCanStartScan 的文档。

获取扫描结果

你可以通过 WiFiScan.getScannedResults API 获取扫描结果:

void _getScannedResults() async {
  // 检查平台支持和必要的要求
  final can = await WiFiScan.instance.canGetScannedResults(askPermissions: true);
  switch(can) {
    case CanGetScannedResults.yes:
      // 获取扫描结果
      final accessPoints = await WiFiScan.instance.getScannedResults();
      // ...
      break;
    // ... 处理其他 `CanGetScannedResults` 值的情况
  }
}

注意: getScannedResults API 可以独立于 startScan API 使用。这会返回最新的可用扫描结果。

更多详情请参阅 WiFiScan.getScannedResultsWiFiAccessPointWiFiScan.canGetScannedResultsCanGetScannedResults 的文档。

获取扫描结果的通知

你可以通过 WiFiScan.onScannedResultsAvailable API 在有新的扫描结果时获取通知:

// 初始化 accessPoints 和 subscription
List<WiFiAccessPoint> accessPoints = [];
StreamSubscription<List<WiFiAccessPoint>>? subscription;

void _startListeningToScannedResults() async {
  // 检查平台支持和必要的要求
  final can = await WiFiScan.instance.canGetScannedResults(askPermissions: true);
  switch(can) {
    case CanGetScannedResults.yes:
      // 监听 onScannedResultsAvailable 流
      subscription = WiFiScan.instance.onScannedResultsAvailable.listen((results) {
        // 更新 accessPoints
        setState(() => accessPoints = results);
      });
      // ...
      break;
    // ... 处理其他 `CanGetScannedResults` 值的情况
  }
}

// 确保在完成后取消订阅
@override
dispose() {
  super.dispose();
  subscription?.cancel();
}

此外,WiFiScan.onScannedResultsAvailable API 还可以与 Flutter 的 StreamBuilder 小部件一起使用。

注意: onScannedResultsAvailable API 可以独立于 startScan API 使用。通知也可以是平台或其它应用执行完整扫描的结果。

更多详情请参阅 WiFiScan.onScannedResultsAvailableWiFiAccessPointWiFiScan.canGetScannedResultsCanGetScannedResults 的文档。


示例代码

以下是一个完整的示例代码,展示了如何使用 wifi_scan 插件进行WiFi扫描:

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:wifi_scan/wifi_scan.dart';

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

/// 示例应用程序用于 wifi_scan 插件。
class MyApp extends StatefulWidget {
  /// 默认构造函数。
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  List<WiFiAccessPoint> accessPoints = <WiFiAccessPoint>[];
  StreamSubscription<List<WiFiAccessPoint>>? subscription;
  bool shouldCheckCan = true;

  bool get isStreaming => subscription != null;

  Future<void> _startScan(BuildContext context) async {
    // 检查是否可以开始扫描
    if (shouldCheckCan) {
      // 检查是否可以开始扫描
      final can = await WiFiScan.instance.canStartScan();
      // 如果不可以,则显示错误信息
      if (can != CanStartScan.yes) {
        if (context.mounted) kShowSnackBar(context, "Cannot start scan: $can");
        return;
      }
    }

    // 调用 startScan API
    final result = await WiFiScan.instance.startScan();
    if (context.mounted) kShowSnackBar(context, "startScan: $result");
    // 重置访问点。
    setState(() => accessPoints = <WiFiAccessPoint>[]);
  }

  Future<bool> _canGetScannedResults(BuildContext context) async {
    if (shouldCheckCan) {
      // 检查是否可以获取扫描结果
      final can = await WiFiScan.instance.canGetScannedResults();
      // 如果不可以,则显示错误信息
      if (can != CanGetScannedResults.yes) {
        if (context.mounted) {
          kShowSnackBar(context, "Cannot get scanned results: $can");
        }
        accessPoints = <WiFiAccessPoint>[];
        return false;
      }
    }
    return true;
  }

  Future<void> _getScannedResults(BuildContext context) async {
    if (await _canGetScannedResults(context)) {
      // 获取扫描结果
      final results = await WiFiScan.instance.getScannedResults();
      setState(() => accessPoints = results);
    }
  }

  Future<void> _startListeningToScanResults(BuildContext context) async {
    if (await _canGetScannedResults(context)) {
      subscription = WiFiScan.instance.onScannedResultsAvailable
          .listen((result) => setState(() => accessPoints = result));
    }
  }

  void _stopListeningToScanResults() {
    subscription?.cancel();
    setState(() => subscription = null);
  }

  @override
  void dispose() {
    super.dispose();
    // 停止扫描结果的订阅
    _stopListeningToScanResults();
  }

  // 构建带标签的切换按钮
  Widget _buildToggle({
    String? label,
    bool value = false,
    ValueChanged<bool>? onChanged,
    Color? activeColor,
  }) =>
      Row(
        children: [
          if (label != null) Text(label),
          Switch(value: value, onChanged: onChanged, activeColor: activeColor),
        ],
      );

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
          actions: [
            _buildToggle(
                label: "Check can?",
                value: shouldCheckCan,
                onChanged: (v) => setState(() => shouldCheckCan = v),
                activeColor: Colors.purple)
          ],
        ),
        body: Builder(
          builder: (context) => Padding(
            padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 20),
            child: Column(
              mainAxisSize: MainAxisSize.max,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    ElevatedButton.icon(
                      icon: const Icon(Icons.perm_scan_wifi),
                      label: const Text('SCAN'),
                      onPressed: () async => _startScan(context),
                    ),
                    ElevatedButton.icon(
                      icon: const Icon(Icons.refresh),
                      label: const Text('GET'),
                      onPressed: () async => _getScannedResults(context),
                    ),
                    _buildToggle(
                      label: "STREAM",
                      value: isStreaming,
                      onChanged: (shouldStream) async => shouldStream
                          ? await _startListeningToScanResults(context)
                          : _stopListeningToScanResults(),
                    ),
                  ],
                ),
                const Divider(),
                Flexible(
                  child: Center(
                    child: accessPoints.isEmpty
                        ? const Text("NO SCANNED RESULTS")
                        : ListView.builder(
                            itemCount: accessPoints.length,
                            itemBuilder: (context, i) =>
                                _AccessPointTile(accessPoint: accessPoints[i])),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

/// 显示接入点的瓷砖。
///
/// 点击时可以查看详细信息。
class _AccessPointTile extends StatelessWidget {
  final WiFiAccessPoint accessPoint;

  const _AccessPointTile({Key? key, required this.accessPoint})
      : super(key: key);

  // 构建可以显示信息的行,基于标签:值对。
  Widget _buildInfo(String label, dynamic value) => Container(
        decoration: const BoxDecoration(
          border: Border(bottom: BorderSide(color: Colors.grey)),
        ),
        child: Row(
          children: [
            Text(
              "$label: ",
              style: const TextStyle(fontWeight: FontWeight.bold),
            ),
            Expanded(child: Text(value.toString()))
          ],
        ),
      );

  @override
  Widget build(BuildContext context) {
    final title = accessPoint.ssid.isNotEmpty ? accessPoint.ssid : "**EMPTY**";
    final signalIcon = accessPoint.level >= -80
        ? Icons.signal_wifi_4_bar
        : Icons.signal_wifi_0_bar;
    return ListTile(
      visualDensity: VisualDensity.compact,
      leading: Icon(signalIcon),
      title: Text(title),
      subtitle: Text(accessPoint.capabilities),
      onTap: () => showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: Text(title),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              _buildInfo("BSSID", accessPoint.bssid),
              _buildInfo("Capability", accessPoint.capabilities),
              _buildInfo("frequency", "${accessPoint.frequency}MHz"),
              _buildInfo("level", accessPoint.level),
              _buildInfo("standard", accessPoint.standard),
              _buildInfo(
                  "centerFrequency0", "${accessPoint.centerFrequency0}MHz"),
              _buildInfo(
                  "centerFrequency1", "${accessPoint.centerFrequency1}MHz"),
              _buildInfo("channelWidth", accessPoint.channelWidth),
              _buildInfo("isPasspoint", accessPoint.isPasspoint),
              _buildInfo(
                  "operatorFriendlyName", accessPoint.operatorFriendlyName),
              _buildInfo("venueName", accessPoint.venueName),
              _buildInfo("is80211mcResponder", accessPoint.is80211mcResponder),
            ],
          ),
        ),
      ),
    );
  }
}

/// 显示Snackbar。
void kShowSnackBar(BuildContext context, String message) {
  if (kDebugMode) print(message);
  ScaffoldMessenger.of(context)
    ..hideCurrentSnackBar()
    ..showSnackBar(SnackBar(content: Text(message)));
}

资源

问题和反馈

请在我们的 issue tracker 中提交 WiFiFlutter 特定的问题、错误或功能请求。

要贡献更改,请查看插件的 checklist for 1.0,我们的 contribution guide 并打开一个 pull request

贡献者 ✨

感谢这些 💖 人 的贡献。

该项目遵循 all-contributors 规范。欢迎任何形式的贡献!


更多关于Flutter无线网络扫描插件wifi_scan的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter无线网络扫描插件wifi_scan的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用wifi_scan插件来进行无线网络扫描的示例代码。这个插件允许你获取附近可用的Wi-Fi网络列表。

步骤 1: 添加依赖

首先,你需要在pubspec.yaml文件中添加wifi_scan插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  wifi_scan: ^0.4.0  # 请确保使用最新版本

然后,运行以下命令来安装依赖:

flutter pub get

步骤 2: 请求权限

在Android平台上,你需要在AndroidManifest.xml文件中添加必要的权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yourapp">

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

    <!-- 其他配置 -->

</manifest>

对于iOS平台,你需要在Info.plist文件中添加NSLocationWhenInUseUsageDescriptionNSLocationAlwaysUsageDescription权限描述。

步骤 3: 编写Flutter代码

接下来,在你的Flutter项目中编写代码以使用wifi_scan插件。

import 'package:flutter/material.dart';
import 'package:wifi_scan/wifi_scan.dart';
import 'package:permission_handler/permission_handler.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Wi-Fi Scanner'),
        ),
        body: WiFiScanner(),
      ),
    );
  }
}

class WiFiScanner extends StatefulWidget {
  @override
  _WiFiScannerState createState() => _WiFiScannerState();
}

class _WiFiScannerState extends State<WiFiScanner> {
  List<WifiScanResult> wifiList = [];

  @override
  void initState() {
    super.initState();
    _requestPermissions();
  }

  Future<void> _requestPermissions() async {
    var status = await Permission.locationWhenInUse.status;
    if (!status.isGranted) {
      Map<Permission, PermissionStatus> statuses = await Permission.requestMultiple([
        Permission.locationWhenInUse,
        Permission.accessWifiState,
      ]);

      if (statuses[Permission.locationWhenInUse]?.isGranted == true &&
          statuses[Permission.accessWifiState]?.isGranted == true) {
        _scanWifi();
      } else {
        // 处理权限被拒绝的情况
      }
    } else {
      _scanWifi();
    }
  }

  Future<void> _scanWifi() async {
    try {
      bool isLocationEnabled = await WifiScan.isLocationEnabled();
      if (!isLocationEnabled) {
        // 提示用户启用位置服务
        return;
      }

      List<WifiScanResult> results = await WifiScan.scanResults();
      setState(() {
        wifiList = results;
      });
    } catch (e) {
      print("Error scanning for Wi-Fi: $e");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Available Wi-Fi Networks',
            style: TextStyle(fontSize: 24),
          ),
          SizedBox(height: 16),
          Expanded(
            child: ListView.builder(
              itemCount: wifiList.length,
              itemBuilder: (context, index) {
                WifiScanResult result = wifiList[index];
                return Padding(
                  padding: const EdgeInsets.symmetric(vertical: 8.0),
                  child: Text(
                    'SSID: ${result.ssid} | BSSID: ${result.bssid} | Level: ${result.level}',
                    style: TextStyle(fontSize: 18),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

解释

  1. 依赖管理:首先,我们添加了wifi_scanpermission_handler依赖,以便在Flutter应用中处理Wi-Fi扫描和权限请求。

  2. 权限请求:在_requestPermissions方法中,我们请求位置权限和Wi-Fi访问权限。这些权限是扫描Wi-Fi网络所必需的。

  3. Wi-Fi扫描:在_scanWifi方法中,我们检查位置服务是否已启用,然后调用WifiScan.scanResults()方法来获取附近的Wi-Fi网络列表。

  4. UI展示:在UI部分,我们使用ListView.builder来展示扫描到的Wi-Fi网络列表。

这样,你就可以在Flutter应用中实现无线网络扫描功能了。确保在实际部署中处理各种异常和权限被拒绝的情况,以提供更好的用户体验。

回到顶部