024.MAUI 安卓—扫描附近的WiFi网络

WiFi扫描通常需要原生代码。

在Android上,我们需要使用原生WifiManager来扫描WiFi。注意:需要相应的权限(如ACCESS_FINE_LOCATION)和运行时权限请求。

由于实现完整功能代码量较大,这里只给出框架代码。实际开发中,还需要考虑Android版本差异、权限请求等。

在Maui项目中,我们可以通过依赖注入获取该服务,然后调用扫描方法。

但是,请注意,从Android 10(API级别29)开始,对WiFi扫描的限制更加严格,应用需要具有精确位置权限,并且设备需要启用位置服务。

一. 创建共享wifi扫描接口

首先创建一个共享接口类:

cs 复制代码
namespace MauiWifi
{
    public interface IWifiScanner
    {
        Task<List<WifiNetwork>> ScanNetworksAsync();//扫描wifi
        Task<bool> RequestPermissionsAsync();//请求权限
    }
}

类中定义了一个扫描方法和一个请求权限方法,方法需要用异步 task之类

二.新建一个wifi 网络模型类WifiNetwork.cs

每一个附近的wifi网络都会被封装成一个模型类对象

cs 复制代码
 public class WifiNetwork
 {
     public string Ssid { get; set; }//wifi网络名
     public string Bssid { get; set; }
     public int SignalStrength { get; set; } // dBm
     public int Frequency { get; set; } // MHz
     public bool IsSecured { get; set; } //是否设置了密码
 }

三.Android 实现

添加权限(Android/AndroidManifest.xml):

cs 复制代码
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

这是必须是权限,对于安卓10.0以上的版本还需要动态申请权限以及定位权限

四。实现接口类

// Platforms/Android/WifiScanner.cs 需建立在Android路径下

cs 复制代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;  // 需要添加这个用于Task
using System.Linq;             // 需要添加这个用于LINQ操作
using Android.Content;
using Android.Net.Wifi;
using Android.OS;
using Application = Android.App.Application;

namespace MauiWifi.Platforms.Android
{
    // WiFi扫描器实现类,实现IWifiScanner接口
    public class WifiScanner : IWifiScanner
    {
        
        private readonly WifiManager _wifiManager;  //原生wifi管理类WifiManager对象

        // 构造函数,初始化WifiManager
        public WifiScanner()
        {
            // 从Android应用上下文中获取WifiService系统服务
            _wifiManager = (WifiManager)Application.Context
                .GetSystemService(Context.WifiService);
        }

        // 请求必要的权限(特别是位置权限,因为Android 6.0+需要位置权限来扫描WiFi)
        public async Task<bool> RequestPermissionsAsync()
        {
            // 检查Android版本是否为6.0(API 23)或更高
            if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
            {
                // 请求运行时位置权限
                var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
                // 返回权限是否被授予
                return status == PermissionStatus.Granted;
            }
            // Android 6.0以下版本不需要运行时权限
            return true;
        }

        // 扫描附近的WiFi网络
        public async Task<List<WifiNetwork>> ScanNetworksAsync()
        {
            // 创建存储WiFi网络结果的列表
            var networks = new List<WifiNetwork>();

            // 请求权限,如果权限被拒绝则返回空列表
            if (!await RequestPermissionsAsync())
                return networks;

            // 检查WiFi是否启用,如果未启用则启用它
            if (!_wifiManager.IsWifiEnabled)
                _wifiManager.SetWifiEnabled(true);

            // 开始扫描附近的WiFi网络
            _wifiManager.StartScan();

            // 等待扫描完成(2秒延迟,给扫描操作足够的时间)
            await Task.Delay(2000);

            // 获取扫描结果
            var scanResults = _wifiManager.ScanResults;

            // 遍历所有扫描到的WiFi网络
            foreach (var result in scanResults)
            {
                // 将每个WiFi网络信息添加到列表中
                networks.Add(new WifiNetwork
                {
                    Ssid = result.Ssid,                     // 网络名称
                    Bssid = result.Bssid,                   // MAC地址
                    SignalStrength = result.Level,          // 信号强度(dBm)
                    Frequency = result.Frequency,           // 频率(MHz)
                    // 检查网络是否加密(通过检查能力字段是否包含安全协议)
                    IsSecured = !string.IsNullOrEmpty(result.Capabilities)
                        && result.Capabilities.Contains("WEP")
                        || result.Capabilities.Contains("PSK")
                        || result.Capabilities.Contains("EAP")
                });
            }

            // 对结果进行分组、去重和排序:
            // 1. 按SSID分组(处理多个AP有相同SSID的情况)
            // 2. 从每组中选择信号最强的网络
            // 3. 转换为列表返回
            return networks
                .GroupBy(n => n.Ssid)
                .Select(g => g.OrderByDescending(n => n.SignalStrength).First())
                .ToList();
        }
    }
}

五.在 MauiProgram.cs 中注册服务

cs 复制代码
//依赖注入wifi接口IWifiScanner,实现这个接口依赖Platforms.Android.WifiScanner
#if ANDROID
        builder.Services.AddSingleton<IWifiScanner, Platforms.Android.WifiScanner>();
#endif

完整代码如下

cs 复制代码
using Microsoft.Extensions.Logging;

namespace MauiWifi
{
    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            
            builder
                .UseMauiApp<App>()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                    fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
                });
#if ANDROID
        builder.Services.AddSingleton<IWifiScanner, Platforms.Android.WifiScanner>();
#endif

#if DEBUG
            builder.Logging.AddDebug();
#endif

            return builder.Build();
        }
    }
}

六.在页面中使用

cs 复制代码
using System.Collections.ObjectModel;

namespace MauiWifi
{
    public partial class MainPage : ContentPage
    {
        private readonly IWifiScanner _wifiScanner;//wifi扫描接口对象

        //自动刷新的list<wifi模型>字段 _networks
        private ObservableCollection<WifiNetwork> _networks = new();

        public MainPage(IWifiScanner wifiScanner)
        {
            InitializeComponent();
            _wifiScanner = wifiScanner;//依赖注入
            wifiListView.ItemsSource = _networks;//绑定到界面的 Items
        }

        //扫描按键
        private async void OnScanClicked(object sender, EventArgs e)
        {
            try
            {
                var networks = await _wifiScanner.ScanNetworksAsync();

                _networks.Clear();//清空
                foreach (var network in networks.OrderByDescending(n => n.SignalStrength))
                {
                    _networks.Add(network);//逐个添加扫描到的附近WiFi
                }
            }
            catch (Exception ex)
            {
                await DisplayAlert("错误", ex.Message, "确定");
            }
        }

    }
}

七.XLML 页面

XML 复制代码
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiWifi.MainPage">

    <VerticalStackLayout>
        <Button Text="扫描WiFi" 
                Clicked="OnScanClicked"
                Margin="20"/>

        <ListView x:Name="wifiListView">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <VerticalStackLayout Padding="10">
                            <Label Text="{Binding Ssid}" 
                                   FontSize="16"
                                   FontAttributes="Bold"/>
                            <Label Text="{Binding SignalStrength, StringFormat='信号强度: {0} dBm'}"
                                   FontSize="12"/>
                            <Label Text="{Binding Bssid}"
                                   FontSize="10"
                                   TextColor="Gray"/>
                        </VerticalStackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </VerticalStackLayout>

</ContentPage>

运行测试:原生api,需在真机上运行