You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xamarin.Forms iOS端锁屏后台BLE扫描问题求助

Xamarin.Forms iOS 后台BLE扫描问题解决指南

问题场景

我在Xamarin.Forms iOS应用中尝试实现锁屏状态下的后台BLE设备扫描,但遇到了几个关键问题。

环境信息

  • 使用Xamarin & MAUI的BLE插件执行扫描操作;
  • 通过Xamarin.Forms DependencyService从共享代码调用原生平台功能(启动/停止扫描);
  • 已在Info.plist中启用后台模式;
  • 测试设备:iPhone 8(iOS 15.6.1)、iPad Air 3(iOS 15.1)。

遇到的问题

  1. 屏幕锁屏后扫描直接停止;
  2. 后台运行约30秒后进程停止,应用被系统杀死;
  3. 基于服务GUID的扫描过滤完全失效。

当前配置与代码

Info.plist 配置

<key>UIBackgroundModes</key>
<array>
    <string>location</string>
    <string>external-accessory</string>
    <string>bluetooth-central</string>
    <string>bluetooth-peripheral</string>
    <string>fetch</string>
    <string>processing</string>
</array>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>YOUR CUSTOM MESSAGE</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>YOUR CUSTOM MESSAGE</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>YOUR CUSTOM MESSAGE</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>YOUR CUSTOM MESSAGE</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your message</string>

iOS原生扫描代码(BLE_Scanner_iOS)

using System;
using Xamarin.Forms;
using Scan_BLE_test.iOS;
using Plugin.BLE;
using Plugin.BLE.Abstractions.Contracts;
using System.Threading.Tasks;
using UIKit;
using Plugin.BLE.Abstractions;

[assembly: Dependency(typeof(BLE_Scanner_iOS))]

namespace Scan_BLE_test.iOS
{
    public class BLE_Scanner_iOS : IStartStopScan
    {
        private static readonly object syncObj = new object(); // 线程安全锁

        /* 日志记录用数据 */
        private static int current_rssi = 0;
        private static string selected_device_name = "";

        IAdapter Adapter;
        IBluetoothLE Ble;
        bool runningFlag;

        public void Start_Ble_Scan()
        {
            runningFlag = true;

            Ble = CrossBluetoothLE.Current;
            Adapter = CrossBluetoothLE.Current.Adapter;

            Task.Factory.StartNew(async () => {
                nint taskId = 0;
                // 后台时间耗尽时触发
                taskId = UIApplication.SharedApplication.BeginBackgroundTask(() => {
                    Console.WriteLine("后台时间耗尽");
                    UIApplication.SharedApplication.EndBackgroundTask(taskId);
                });
                while (runningFlag)
                {
                    lock (syncObj)
                    {
                        Console.WriteLine("运行循环中...");
                        Console.WriteLine(UIApplication.SharedApplication.BackgroundTimeRemaining);

                        Scan_BLE(10000);

                        Console.WriteLine($"设备 {selected_device_name} RSSI: {current_rssi}");
                    }
                    await Task.Delay(1000);
                }
                UIApplication.SharedApplication.EndBackgroundTask(taskId);
            });
        }


        async void Scan_BLE(int scan_time_i)
        {
            if (Ble.State == BluetoothState.On)
            {
                if (!Adapter.IsScanning)
                {
                    Adapter.ScanTimeout = scan_time_i;
                    Adapter.ScanMode = ScanMode.Balanced;
                    // 注册设备发现事件
                    Adapter.DeviceDiscovered += (s, a) =>
                    {
                        if (!string.IsNullOrEmpty(a.Device.Name))
                        {
                            lock (syncObj)
                            {
                                current_rssi = a.Device.Rssi;
                                selected_device_name = a.Device.Name;
                            }
                        }
                    };
                    var scanFilterOptions = new ScanFilterOptions();

                    // 这里的GUID过滤不生效
                    //scanFilterOptions.ServiceUuids = new[] {
                    //    Guid.Parse("F640A514-908B-CCAF-E5EB-89AE701CF346")
                    //};

                    await Adapter.StartScanningForDevicesAsync(scanFilterOptions);
                    System.Diagnostics.Debug.WriteLine("[INFO] 扫描完成");
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine("[WARNING] 设备正在扫描");
                }
            }
            else
            {
                System.Diagnostics.Debug.WriteLine("[WARNING] BLE未开启");
            }
        }


        public void Stop_Ble_Scan()
        {
            runningFlag = false;
        }
    }
}

输出日志

锁屏前能正常获取Beacon数据,之后日志停止,应用被系统关闭:

[0:] [WARNING] device is scaning
2022-11-16 12:49:49.063 Xamarin.PreBuilt.iOS[46180:1980782] Running loop...
2022-11-16 12:49:49.064 Xamarin.PreBuilt.iOS[46180:1980782] 26,5684510833526
2022-11-16 12:49:49.066 Xamarin.PreBuilt.iOS[46180:1980782] Device Beacon1 rssi -85
2022-11-16 12:49:50.091 Xamarin.PreBuilt.iOS[46180:1980781] Running loop...
[0:] [WARNING] device is scaning
2022-11-16 12:49:50.092 Xamarin.PreBuilt.iOS[46180:1980781] 25,540871916688
2022-11-16 12:49:50.094 Xamarin.PreBuilt.iOS[46180:1980781] Device Beacon1 rssi -85
[0:] [WARNING] device is scaning
2022-11-16 12:49:51.134 Xamarin.PreBuilt.iOS[46180:1980781] Running loop...
2022-11-16 12:49:51.135 Xamarin.PreBuilt.iOS[46180:1980781] 24,4981506250042
2022-11-16 12:49:51.136 Xamarin.PreBuilt.iOS[46180:1980781] Device Beacon1 rssi -85

问题解决步骤

1. 后台扫描被终止的问题

iOS对后台BLE扫描有严格限制,你的代码逻辑和配置存在以下问题:

  • 冗余的后台模式:external-accessory、fetch、processing这些权限和BLE后台扫描无关,反而会让系统认为你在滥用后台权限,直接干掉应用。只保留bluetooth-central(如果需要结合位置增强稳定性可以保留location);
  • 错误的扫描循环:你用10秒超时扫描+1秒延迟循环的方式,每次扫描结束后重启,这种方式在后台会被系统判定为无效后台活动,很快被终止。应该改用持续扫描,只在扫描意外停止时重启;
  • 后台任务管理不当:不要在循环里反复创建后台任务,应该在扫描启动时创建一个后台任务,直到扫描停止再结束它。

修改后的扫描启动逻辑:

public void Start_Ble_Scan()
{
    runningFlag = true;
    Ble = CrossBluetoothLE.Current;
    Adapter = CrossBluetoothLE.Current.Adapter;

    // 避免重复注册事件
    Adapter.DeviceDiscovered -= HandleDeviceDiscovered;
    Adapter.DeviceDiscovered += HandleDeviceDiscovered;

    // 创建单个后台任务
    var taskId = UIApplication.SharedApplication.BeginBackgroundTask(() => {
        Console.WriteLine("后台时间耗尽,停止扫描");
        Stop_Ble_Scan();
        UIApplication.SharedApplication.EndBackgroundTask(taskId);
    });

    Task.Run(async () => {
        while (runningFlag)
        {
            if (Ble.State == BluetoothState.On && !Adapter.IsScanning)
            {
                // 后台用低功耗扫描模式更稳定
                Adapter.ScanMode = ScanMode.LowPower;
                // 无超时持续扫描
                await Adapter.StartScanningForDevicesAsync(new ScanFilterOptions());
            }
            // 5秒检查一次扫描状态
            await Task.Delay(5000);
        }
        UIApplication.SharedApplication.EndBackgroundTask(taskId);
    });
}

private void HandleDeviceDiscovered(object sender, Plugin.BLE.Abstractions.EventArgs.DeviceEventArgs e)
{
    if (!string.IsNullOrEmpty(e.Device.Name))
    {
        lock (syncObj)
        {
            current_rssi = e.Device.Rssi;
            selected_device_name = e.Device.Name;
            Console.WriteLine($"设备 {selected_device_name} RSSI: {current_rssi}");
        }
    }
}

2. GUID过滤失效的问题

iOS的BLE扫描过滤只对广播包中包含服务UUID的设备生效。如果你的BLE设备没有在广播数据里主动发送这个UUID,过滤规则就会失效。解决办法:

  • 确认BLE设备的固件配置,确保它在广播包中包含目标服务UUID;
  • 如果设备不支持广播UUID,只能在HandleDeviceDiscovered事件中手动过滤:
private void HandleDeviceDiscovered(object sender, Plugin.BLE.Abstractions.EventArgs.DeviceEventArgs e)
{
    var targetServiceId = Guid.Parse("F640A514-908B-CCAF-E5EB-89AE701CF346");
    // 检查设备是否包含目标服务
    if (e.Device.Services?.Any(s => s.Id == targetServiceId) == true)
    {
        lock (syncObj)
        {
            current_rssi = e.Device.Rssi;
            selected_device_name = e.Device.Name;
            Console.WriteLine($"匹配目标设备 {selected_device_name} RSSI: {current_rssi}");
        }
    }
}

3. 锁屏后扫描停止的问题

  • 确保用户授予了始终允许的蓝牙权限(对应NSBluetoothAlwaysUsageDescription),仅“使用期间允许”的权限在锁屏后会被限制;
  • 如果需要更稳定的后台扫描,可以结合location后台模式(同时申请位置权限),iOS对位置相关的后台任务优先级更高;
  • 后台尽量减少CPU消耗,避免不必要的日志输出和计算,降低被系统杀死的概率。

内容的提问来源于stack exchange,提问作者Jakub

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 20:50:29