如何实现可与BLE客户端通信的Xamarin.Forms BLE服务端应用
Xamarin.Forms 实现BLE服务端方案
Xamarin.Forms 本身没有内置BLE GATT服务端(外设角色)的跨平台抽象API,需要通过依赖服务(DependencyService) 分别对接Android、iOS原生平台的BLE外设能力,封装统一接口给共享项目调用即可,不需要引入第三方服务。
核心实现逻辑
- 自定义固定UUID的GATT主服务,下挂1个同时支持「写入」「通知」属性的特征:客户端通过写入特征发送字符串消息,服务端收到消息处理后,通过特征的通知通道向已连接客户端返回字符串响应。
- 近场通信不需要额外开发检测逻辑:只要服务端开启可连接广播,客户端在蓝牙信号覆盖范围内扫描到对应服务、完成GATT连接和服务发现后,即可主动向服务端发消息,完全匹配需求。
- 提前处理平台权限:Android 6.0+需动态申请定位权限,Android 12+需额外申请
BLUETOOTH_ADVERTISE/BLUETOOTH_CONNECT/BLUETOOTH_SCAN权限;iOS需在Info.plist中添加NSBluetoothAlwaysUsageDescription、NSBluetoothPeripheralUsageDescription权限描述,且前台状态下才能正常广播。
具体实现步骤
1. 共享层定义跨平台接口
在Xamarin.Forms共享项目中定义统一的BLE服务端接口,供各原生平台实现:
public interface IBleServer { // 初始化GATT服务、开启广播 Task StartServerAsync(); // 停止广播、释放服务资源 Task StopServerAsync(); // 接收到客户端消息的触发事件 event EventHandler<string> MessageReceived; // 向所有已连接客户端发送响应字符串 Task SendResponseAsync(string response); }
2. Android平台原生实现
通过Android原生BluetoothGattServer、BluetoothLeAdvertiserAPI实现逻辑,注册为依赖服务:
[assembly: Dependency(typeof(BleServerImpl))] namespace YourApp.Droid { public class BleServerImpl : IBleServer { private BluetoothGattServer _gattServer; private BluetoothGattCharacteristic _dataCharacteristic; private readonly List<BluetoothDevice> _connectedDevices = new List<BluetoothDevice>(); // 自定义UUID,客户端需保持完全一致 private readonly Java.Util.UUID _serviceUuid = Java.Util.UUID.FromString("0000ffe0-0000-1000-8000-00805f9b34fb"); private readonly Java.Util.UUID _charUuid = Java.Util.UUID.FromString("0000ffe1-0000-1000-8000-00805f9b34fb"); public event EventHandler<string> MessageReceived; public async Task StartServerAsync() { var bluetoothManager = (BluetoothManager)Android.App.Application.Context.GetSystemService(Context.BluetoothService); _gattServer = bluetoothManager.OpenGattServer(Android.App.Application.Context, new ServerCallback(this)); // 构建GATT服务和特征 var service = new BluetoothGattService(_serviceUuid, GattServiceType.Primary); _dataCharacteristic = new BluetoothGattCharacteristic( _charUuid, GattProperty.Write | GattProperty.Notify, GattPermission.Write ); service.AddCharacteristic(_dataCharacteristic); _gattServer.AddService(service); // 开启BLE广播 var advertiser = BluetoothAdapter.DefaultAdapter.BluetoothLeAdvertiser; var adSettings = new AdvertiseSettings.Builder() .SetAdvertiseMode(AdvertiseMode.LowLatency) .SetConnectable(true) .SetTxPowerLevel(AdvertiseTxPower.High) .Build(); var adData = new AdvertiseData.Builder() .SetIncludeDeviceName(true) .AddServiceUuid(new ParcelUuid(_serviceUuid)) .Build(); advertiser.StartAdvertising(adSettings, adData, new SimpleAdCallback()); } public Task SendResponseAsync(string response) { byte[] respData = System.Text.Encoding.UTF8.GetBytes(response); _dataCharacteristic.SetValue(respData); // 向所有已连接客户端推送通知 foreach (var device in _connectedDevices) { _gattServer.NotifyCharacteristicChanged(device, _dataCharacteristic, false); } return Task.CompletedTask; } public Task StopServerAsync() { BluetoothAdapter.DefaultAdapter.BluetoothLeAdvertiser?.StopAdvertising(new SimpleAdCallback()); _gattServer?.Close(); _gattServer = null; _connectedDevices.Clear(); return Task.CompletedTask; } // GATT服务端回调处理 private class ServerCallback : BluetoothGattServerCallback { private readonly BleServerImpl _parent; public ServerCallback(BleServerImpl parent) => _parent = parent; public override void OnConnectionStateChange(BluetoothDevice device, ProfileState status, ProfileState newState) { base.OnConnectionStateChange(device, status, newState); if (newState == ProfileState.Connected) _parent._connectedDevices.Add(device); if (newState == ProfileState.Disconnected) _parent._connectedDevices.Remove(device); } public override void OnCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, bool preparedWrite, bool responseNeeded, int offset, byte[] value) { base.OnCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value); // 解析客户端发送的字符串 string receivedMsg = System.Text.Encoding.UTF8.GetString(value); _parent.MessageReceived?.Invoke(_parent, receivedMsg); // 返回写入成功响应 if (responseNeeded) _parent._gattServer.SendResponse(device, requestId, GattStatus.Success, offset, value); } } private class SimpleAdCallback : AdvertiseCallback { } } }
3. iOS平台原生实现
通过iOS原生CBPeripheralManagerAPI实现外设逻辑,注册为依赖服务:
[assembly: Dependency(typeof(BleServerImpl))] namespace YourApp.iOS { public class BleServerImpl : IBleServer { private CBPeripheralManager _peripheralManager; private CBMutableCharacteristic _dataCharacteristic; private readonly List<CBCentral> _connectedCentrals = new List<CBCentral>(); private readonly CBUUID _serviceUuid = CBUUID.FromString("0000ffe0-0000-1000-8000-00805f9b34fb"); private readonly CBUUID _charUuid = CBUUID.FromString("0000ffe1-0000-1000-8000-00805f9b34fb"); public event EventHandler<string> MessageReceived; public Task StartServerAsync() { _peripheralManager = new CBPeripheralManager(new PeripheralDelegate(this), DispatchQueue.MainQueue); return Task.CompletedTask; } public Task SendResponseAsync(string response) { byte[] respData = System.Text.Encoding.UTF8.GetBytes(response); _peripheralManager.UpdateValue(NSData.FromArray(respData), _dataCharacteristic, _connectedCentrals.ToArray()); return Task.CompletedTask; } public Task StopServerAsync() { _peripheralManager.StopAdvertising(); _peripheralManager.RemoveAllServices(); _connectedCentrals.Clear(); return Task.CompletedTask; } private class PeripheralDelegate : CBPeripheralManagerDelegate { private readonly BleServerImpl _parent; public PeripheralDelegate(BleServerImpl parent) => _parent = parent; public override void StateUpdated(CBPeripheralManager peripheral) { if (peripheral.State != CBPeripheralManagerState.PoweredOn) return; // 蓝牙可用时初始化服务和特征 var service = new CBMutableService(_parent._serviceUuid, true); _parent._dataCharacteristic = new CBMutableCharacteristic( _parent._charUuid, CBCharacteristicProperties.Write | CBCharacteristicProperties.Notify, CBAttributePermissions.Writeable | CBAttributePermissions.Readable, null ); service.Characteristics = new[] { _parent._dataCharacteristic }; peripheral.AddService(service); // 开启广播 peripheral.StartAdvertising(new NSDictionary( CBAdvertisement.DataServiceUUIDsKey, NSArray.FromObjects(_parent._serviceUuid), CBAdvertisement.DataLocalNameKey, "BLE_Server" )); } public override void DidSubscribeToCharacteristic(CBPeripheralManager peripheral, CBCentral central, CBCharacteristic characteristic) { _parent._connectedCentrals.Add(central); } public override void DidUnsubscribeFromCharacteristic(CBPeripheralManager peripheral, CBCentral central, CBCharacteristic characteristic) { _parent._connectedCentrals.Remove(central); } public override void WriteRequestsReceived(CBPeripheralManager peripheral, CBATTRequest[] requests) { foreach (var req in requests) { if (req.Characteristic.UUID.Equals(_parent._charUuid)) { string receivedMsg = System.Text.Encoding.UTF8.GetString(req.Value.ToArray()); _parent.MessageReceived?.Invoke(_parent, receivedMsg); peripheral.RespondToRequest(req, CBATTError.Success); } } } } } }
4. 共享层调用示例
在Xamarin.Forms页面/业务逻辑中直接通过依赖服务调用即可:
var bleServer = DependencyService.Get<IBleServer>(); // 监听收到的客户端消息 bleServer.MessageReceived += async (s, msg) => { // 这里写自定义业务逻辑 Console.WriteLine($"收到客户端消息:{msg}"); // 返回响应 await bleServer.SendResponseAsync($"服务端已接收消息,响应时间:{DateTime.Now:T}"); }; // 启动服务 await bleServer.StartServerAsync();
常见踩坑说明
- 服务端、客户端的服务UUID、特征UUID必须完全一致,否则客户端无法找到对应通信特征。
- 字符串编解码两端统一用UTF8,避免中文乱码。
- BLE单包广播载荷最大31字节,不要在广播包中塞入过多自定义数据,否则会导致广播失败。
- 客户端连接服务端后,必须先开启对应特征的通知订阅,否则无法收到服务端返回的响应消息。
- iOS后台状态下BLE广播会被系统降频、限制,建议服务端应用保持前台使用。
内容的提问来源于stack exchange,提问作者Huzaifa Awan
相关产品推荐
相关产品推荐

