我正在使用 flutter_reactive_ble 和 flutter_blue_plus 对于这两个库,当代码在 android 11 上构建时,蓝牙模块工作得很好。对于 Andorid 11+,它不会显示某些扫描的设备。
清单
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- permissions handling -->
<!-- Bluetooth -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- Notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- Camera -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- Location -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application
android:label="melo"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
扫描代码(在 Android 11 上工作正常,并且缺少 Android 11 及以上的某些设备):
import 'package:animated_custom_dropdown/custom_dropdown.dart';
import 'package:flutter/material.dart';
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:melo/ble/ble_scanner.dart';
import 'package:melo/providers/new_instrument_provider.dart';
import 'package:melo/screens/instrument_enrollment/primary_info.dart';
import 'package:melo/utils/colors.dart';
import 'package:melo/widgets/add_instrument/bluetooth_scan_item.dart';
import 'package:melo/widgets/add_instrument/steps.dart';
import 'package:melo/widgets/others/theme_button.dart';
class SelectInstrumentScreen extends ConsumerStatefulWidget {
const SelectInstrumentScreen({super.key, required this.collectionName});
final String collectionName;
@override
ConsumerState<SelectInstrumentScreen> createState() =>
_SelectInstrumentScreenState();
}
class _SelectInstrumentScreenState
extends ConsumerState<SelectInstrumentScreen> {
final BleScanner bleScanner = BleScanner(
ble: FlutterReactiveBle(),
logMessage: (message) {},
);
@override
void initState() {
super.initState();
bleScanner.startScan([]);
}
@override
Widget build(BuildContext context) {
ref.watch(newInstrumentProvider);
bool areValuesSelected = ref.read(newInstrumentProvider)['type'] != null &&
ref.read(newInstrumentProvider)['macAddress'] != null;
return Scaffold(
backgroundColor: backgroundColor,
appBar: AppBar(
backgroundColor: Colors.white,
leadingWidth: 0,
foregroundColor: Colors.transparent,
toolbarHeight: 70,
title: Container(
padding: const EdgeInsets.all(5),
child: const StepsIndicator(step: 1),
),
),
body: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 5),
const Text(
'Select Sensor Device',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
Text(
'Select the sensor device that you want to connect from the available devices.',
style: TextStyle(
color: secondaryColor,
fontSize: 13,
),
),
// select instrument from a list of options
const SizedBox(height: 10),
const Text(
'Choose Instrument',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
const SizedBox(height: 5),
CustomDropdown<String>(
closedHeaderPadding: const EdgeInsets.only(
top: 7, bottom: 8, left: 15, right: 10),
decoration: CustomDropdownDecoration(
closedBorderRadius: BorderRadius.circular(100),
closedBorder: Border.all(
color: const Color.fromRGBO(220, 220, 220, 1),
),
),
overlayHeight: MediaQuery.of(context).size.height * 0.5,
hintText: 'Choose Instrument',
items: _listInstruments,
initialItem: ref.read(newInstrumentProvider)['type'],
onChanged: (value) {
ref
.read(newInstrumentProvider.notifier)
.update('type', value);
},
),
// bluetooth scanned devices
const SizedBox(height: 10),
const Text(
'Available Bluetooth Devices',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
const SizedBox(height: 5),
// reactive ble devices
],
),
),
Expanded(
child: StreamBuilder<BleScannerState>(
stream: bleScanner.state,
builder: (context, snapshot) {
if (snapshot.hasData) {
final discoveredDevices = snapshot.data!.discoveredDevices;
// sorting: nearest device first
discoveredDevices.sort((a, b) => b.rssi.compareTo(a.rssi));
return ListView.builder(
itemCount: discoveredDevices.length,
itemBuilder: (context, index) {
final device = discoveredDevices[index];
return Material(
child: InkWell(
onTap: () {
ref
.read(newInstrumentProvider.notifier)
.update('macAddress', device.id);
ref.read(newInstrumentProvider.notifier).update(
'name',
device.name.isNotEmpty
? device.name
: 'Unknown Name');
},
child: BluetoothScanItem(device: device),
),
);
},
);
} else {
return const Text('nothing so far');
}
},
),
),
Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
Expanded(
flex: 2,
child: ThemeButton(
onTap: () {
Navigator.pop(context);
},
name: 'Back',
isSimple: true,
),
),
const SizedBox(width: 10),
Expanded(
flex: 3,
child: ThemeButton(
onTap: () {
if (areValuesSelected) {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return PrimaryInfoScreen(
collectionName: widget.collectionName);
}));
}
},
name: 'Next',
isDisabled: !areValuesSelected,
),
),
],
),
),
],
),
);
}
}
const List<String> _listInstruments = [
'Acoustic',
'Bass',
'Bowed',
'Brass',
'Drum',
'Electric',
'Mandolin',
'Piano',
'Reed',
'Other',
];
自Android 12发布以来,引入了新的蓝牙权限。根据 documentation,BLUETOOTH_SCAN、BLUETOOTH_CONNECT 和 BLUETOOTH_ADVERTISE 被视为运行时权限。
这意味着,如果您的应用程序面向运行 Android 12 或更高版本的设备,您必须自行处理这些权限并向用户明确请求这些权限。
例如,当您打算扫描BLE设备时,您必须请求BLUETOOTH_SCAN权限。请注意,此权限将作为“附近设备”权限呈现给用户。
未能请求并获得这些权限可能会导致您的 BleScanner 无法检测到任何附近的设备。
请注意,对于低于 Android 12 的设备,您仍然需要请求运行时位置权限。