Flutter蓝牙信标扫描插件beacon_scanner的使用
Flutter蓝牙信标扫描插件beacon_scanner的使用
简介
beacon_scanner
是一个基于 flutter_beacon
的插件,由Eyro Labs开发。该插件可以与Android的前台服务结合使用,并在底层使用了Android的AltBeacon和iOS的CoreLocation。它是一个混合iBeacon扫描SDK,支持Android API 18+和iOS 13+。
功能特性
- 自动权限管理
- 扫描(Ranging)iBeacons
- 监控(Monitoring)iBeacons
安装
1. 添加依赖
在 pubspec.yaml
文件中添加以下依赖:
dependencies:
flutter_beacon: latest
2. Android平台特定设置
对于目标SDK版本29+(Android 10, 11),需要手动添加 ACCESS_FINE_LOCATION
权限:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
如果你还需要后台扫描功能,还需添加 ACCESS_BACKGROUND_LOCATION
权限:
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
3. iOS平台特定设置
仅支持iOS 13+。为了使用信标相关功能,应用程序需要请求位置权限。这是一个两步过程:
- 在配置文件中声明应用程序所需的权限。
- 在应用程序运行时请求用户权限(插件可以自动处理)。
iOS所需权限为“使用时”权限。权限必须在 ios/Runner/Info.plist
中声明:
<dict>
<!-- 使用时 -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>应用程序需要位置的原因</string>
<!-- 始终 -->
<!-- 对于iOS 11+ -->
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>应用程序需要位置的原因</string>
<!-- 对于iOS 9/10 -->
<key>NSLocationAlwaysUsageDescription</key>
<string>应用程序需要位置的原因</string>
<!-- 蓝牙隐私 -->
<!-- 对于iOS 13+ -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>应用程序需要蓝牙的原因</string>
</dict>
iOS故障排除
- 示例代码仅在物理设备上正常工作(模拟器上的蓝牙功能已禁用)。
- 如果示例代码在设备上无法正常工作(信标未出现),请确保已启用位置和蓝牙(设置 -> Flutter Beacon)。
使用方法
初始化库
final BeaconScanner beaconScanner = BeaconScanner.instance;
try {
// false - 如果你想手动检查所需的权限
await beaconScanner.initialize(true);
} on PlatformException catch(e) {
// 库初始化失败,检查错误代码和消息
}
扫描(Ranging)信标
final regions = <Region>[];
if (Platform.isIOS) {
// iOS平台,至少设置identifier和proximityUUID用于区域扫描
regions.add(Region(
identifier: 'Apple Airlocate',
beaconId: IBeaconId(proximityUUID: 'E2C56DB5-DFFB-48D2-B060-D0F5A71096E0'),
));
} else {
// Android平台,它可以扫描所有Proximity UUID的信标
regions.add(Region(identifier: 'com.beacon'));
}
// 开始扫描信标
_streamRanging = beaconScanner.ranging(regions).listen((ScanResult result) {
// result包含一个区域和找到的信标列表
// 如果范围内没有匹配的信标,列表可能是空的
});
// 停止扫描信标
_streamRanging.cancel();
监控(Monitoring)信标
final regions = <Region>[];
if (Platform.isIOS) {
// iOS平台,至少设置identifier和proximityUUID用于区域监控
regions.add(Region(
identifier: 'Apple Airlocate',
beaconId: IBeaconId(proximityUUID: 'E2C56DB5-DFFB-48D2-B060-D0F5A71096E0'),
));
} else {
// Android平台,它可以监控所有Proximity UUID的信标
regions.add(Region(identifier: 'com.beacon'));
}
// 开始监控信标
// 即使应用程序关闭,iOS上也可以继续工作
_streamMonitoring = beaconScanner.monitoring(regions).listen((MonitoringResult result) {
// result包含一个区域、事件类型和事件状态
});
// 停止监控信标
_streamMonitoring.cancel();
完整示例Demo
以下是一个完整的Flutter项目示例,展示了如何使用 beacon_scanner
插件来扫描和监控信标。
pubspec.yaml
name: beacon_scanner_example
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_beacon: latest
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_beacon/flutter_beacon.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Beacon Scanner Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BeaconScannerPage(),
);
}
}
class BeaconScannerPage extends StatefulWidget {
[@override](/user/override)
_BeaconScannerPageState createState() => _BeaconScannerPageState();
}
class _BeaconScannerPageState extends State<BeaconScannerPage> {
final BeaconScanner beaconScanner = BeaconScanner.instance;
StreamSubscription<ScanResult>? _streamRanging;
StreamSubscription<MonitoringResult>? _streamMonitoring;
List<Beacon> _beacons = [];
String _monitoringStatus = 'Not monitoring';
[@override](/user/override)
void initState() {
super.initState();
_initializeBeaconScanner();
}
Future<void> _initializeBeaconScanner() async {
try {
await beaconScanner.initialize(true);
setState(() {
_monitoringStatus = 'Initialized';
});
} on PlatformException catch (e) {
setState(() {
_monitoringStatus = 'Failed to initialize: ${e.message}';
});
}
}
void _startRanging() {
final regions = <Region>[];
if (Platform.isIOS) {
regions.add(Region(
identifier: 'Apple Airlocate',
beaconId: IBeaconId(proximityUUID: 'E2C56DB5-DFFB-48D2-B060-D0F5A71096E0'),
));
} else {
regions.add(Region(identifier: 'com.beacon'));
}
_streamRanging?.cancel();
_streamRanging = beaconScanner.ranging(regions).listen((result) {
setState(() {
_beacons = result.beacons;
});
});
}
void _stopRanging() {
_streamRanging?.cancel();
setState(() {
_beacons = [];
});
}
void _startMonitoring() {
final regions = <Region>[];
if (Platform.isIOS) {
regions.add(Region(
identifier: 'Apple Airlocate',
beaconId: IBeaconId(proximityUUID: 'E2C56DB5-DFFB-48D2-B060-D0F5A71096E0'),
));
} else {
regions.add(Region(identifier: 'com.beacon'));
}
_streamMonitoring?.cancel();
_streamMonitoring = beaconScanner.monitoring(regions).listen((result) {
setState(() {
_monitoringStatus = 'Monitoring: ${result.state.name}';
});
});
}
void _stopMonitoring() {
_streamMonitoring?.cancel();
setState(() {
_monitoringStatus = 'Not monitoring';
});
}
[@override](/user/override)
void dispose() {
_streamRanging?.cancel();
_streamMonitoring?.cancel();
super.dispose();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Beacon Scanner Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Monitoring Status: $_monitoringStatus',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_streamMonitoring == null) {
_startMonitoring();
} else {
_stopMonitoring();
}
},
child: Text(_streamMonitoring == null ? 'Start Monitoring' : 'Stop Monitoring'),
),
SizedBox(height: 20),
Text(
'Ranging Beacons:',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
if (_streamRanging == null) {
_startRanging();
} else {
_stopRanging();
}
},
child: Text(_streamRanging == null ? 'Start Ranging' : 'Stop Ranging'),
),
SizedBox(height: 20),
Expanded(
child: ListView.builder(
itemCount: _beacons.length,
itemBuilder: (context, index) {
final beacon = _beacons[index];
return ListTile(
title: Text('Beacon ${index + 1}'),
subtitle: Text(
'UUID: ${beacon.proximityUUID}, Major: ${beacon.major}, Minor: ${beacon.minor}, RSSI: ${beacon.rssi}',
),
);
},
),
),
],
),
),
);
}
}
更多关于Flutter蓝牙信标扫描插件beacon_scanner的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter蓝牙信标扫描插件beacon_scanner的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中使用beacon_scanner
插件进行蓝牙信标扫描的示例代码。这个插件允许你扫描附近的蓝牙信标并获取它们的信息。
首先,确保你已经在pubspec.yaml
文件中添加了beacon_scanner
依赖:
dependencies:
flutter:
sdk: flutter
beacon_scanner: ^x.y.z # 请替换为最新版本号
然后,运行flutter pub get
来安装依赖。
接下来,在你的Flutter项目中,你可以按照以下步骤进行蓝牙信标扫描:
- 初始化插件并请求权限:
import 'package:flutter/material.dart';
import 'package:beacon_scanner/beacon_scanner.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
BeaconScanner _beaconScanner = BeaconScanner();
List<ScanResult> _scanResults = [];
@override
void initState() {
super.initState();
_requestPermissions();
}
Future<void> _requestPermissions() async {
// 请求位置权限,因为蓝牙扫描通常需要位置权限
var status = await Permission.locationWhenInUse.status;
if (!status.isGranted) {
var result = await Permission.locationWhenInUse.request();
if (result.isGranted) {
// 开始扫描
_startScanning();
} else if (result.isPermanentlyDenied) {
// 处理权限被拒绝的情况
print("Location permission is permanently denied");
}
} else {
// 权限已授予,开始扫描
_startScanning();
}
}
void _startScanning() {
_beaconScanner.startScanning().listen((scanResult) {
setState(() {
_scanResults.add(scanResult);
});
}).onError((error) {
print("Scanning failed: $error");
}).onDone(() {
print("Scanning stopped");
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Beacon Scanner'),
),
body: ListView.builder(
itemCount: _scanResults.length,
itemBuilder: (context, index) {
var result = _scanResults[index];
return ListTile(
title: Text('UUID: ${result.beacon.uuid}'),
subtitle: Text('Distance: ${result.beacon.distance?.toString()}'),
);
},
),
),
);
}
@override
void dispose() {
_beaconScanner.stopScanning();
super.dispose();
}
}
- 处理权限请求:
上面的代码使用了
permission_handler
插件来处理权限请求。你需要在pubspec.yaml
文件中添加这个依赖:
dependencies:
permission_handler: ^x.y.z # 请替换为最新版本号
-
权限处理逻辑: 在代码中,我们首先检查位置权限的状态,如果权限未被授予,则请求权限。如果权限被授予,则开始扫描蓝牙信标。
-
扫描结果展示: 扫描到的结果会被存储在一个列表中,并通过
ListView.builder
展示在UI上。 -
停止扫描: 在
dispose
方法中停止扫描,以确保在组件被销毁时不会继续监听扫描结果。
请注意,实际使用中你可能需要处理更多的边缘情况,比如权限请求被用户拒绝时的用户引导,以及扫描结果的更新处理等。此外,确保在真机上进行测试,因为模拟器通常不支持蓝牙功能。