flutter_blue_plus在使用flutter run时能检测到蓝牙设备,但在Google Play商店发布的版本(AAB,SDK 35)中却检测不到
当我在本地使用 flutter run 运行我的Flutter应用时,蓝牙扫描工作得非常好——所有附近的设备都能被检测到。然而,当我将应用发布到Google Play商店(封闭测试,发布AAB)时,根本检测不到任何蓝牙设备,即使我在设备设置中手动授权了所有权限。
我最初以为问题是由我设置的UUID过滤器引起的(只扫描具有特定UUID的设备)。我移除了该过滤器并推送了新版本。更新后的版本在本地通过 flutter run 测试时运行正常,但Play商店的版本仍然检测不到任何设备。
以下是我的代码。如果你需要我提供其他信息来调试这个问题,请告诉我!
谢谢
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- ── Bluetooth permissions (Android 12+ / SDK 31+) ── -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- Legacy Bluetooth for older devices -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!-- Location required for BT scanning on all Android versions -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Storage permissions for file import/export -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<!-- Bluetooth feature declaration -->
<uses-feature android:name="android.hardware.bluetooth_le"
android:required="false" />
<application
android:label="Brimbéloi"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:allowBackup="false"
android:fullBackupContent="false">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
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>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
pubspec.yaml
name: brimbeloiv3
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: "none" # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.1+1
environment:
sdk: ^3.11.1
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_launcher_icons: ^0.13.1
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^6.0.0
provider: ^6.1.1
flutter_blue_plus: 1.32.4
flutter_reorderable_list: ^1.0.1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
assets:
- assets/images/
flutter_icons:
android: "launcher_icon"
ios: false
image_path: "assets/images/icon.png"
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'bluetooth_provider.dart';
import 'pattern_provider.dart';
import 'esp_file_provider.dart';
import 'settings_provider.dart';
import 'main_screen.dart';
import 'app_theme.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => SettingsProvider()),
ChangeNotifierProvider(create: (_) => BluetoothProvider()),
ChangeNotifierProvider(create: (_) => PatternProvider()),
ChangeNotifierProvider(create: (_) => EspFileProvider()),
],
child: const JiggerApp(),
),
);
}
class JiggerApp extends StatelessWidget {
const JiggerApp({super.key});
@override
Widget build(BuildContext context) {
final settings = context.watch<SettingsProvider>();
return MaterialApp(
title: 'Jigger Control',
debugShowCheckedModeBanner: false,
theme: AppTheme.theme,
darkTheme: AppTheme.darkTheme,
themeMode: settings.darkMode ? ThemeMode.dark : ThemeMode.light,
home: const MainScreen(),
);
}
}
bluetooth_provider.dart
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'bt_device.dart';
class BluetoothProvider extends ChangeNotifier {
// ── Jigger UUIDs ─────────────────────────────────────────────
static const String serviceUuid = '4fafc201-1fb5-459e-8fcc-c5c9c331914b';
static const String characteristicUuid =
'beb5483e-36e1-4688-b7f5-ea07361b26a8';
// ── State ─────────────────────────────────────────────────────
List<BtDevice> _devices = [];
BtDevice? _selectedDevice;
bool _isScanning = false;
String? _errorMessage;
final Map<String, String> _customNames = {};
StreamSubscription? _scanSubscription;
StreamSubscription? _scanningSubscription;
List<BtDevice> get devices => List.unmodifiable(_devices);
BtDevice? get selectedDevice => _selectedDevice;
bool get isScanning => _isScanning;
String? get errorMessage => _errorMessage;
// ── Scanning ──────────────────────────────────────────────────
Future<void> startScan() async {
_errorMessage = null;
_devices = [];
notifyListeners();
// Check + request Bluetooth (triggers permission dialog on first launch)
final adapterState = await FlutterBluePlus.adapterState.first;
if (adapterState != BluetoothAdapterState.on) {
// On Android this will show the "turn on Bluetooth" system dialog
try {
await FlutterBluePlus.turnOn();
// Wait up to 3 seconds for BT to turn on
await FlutterBluePlus.adapterState
.where((s) => s == BluetoothAdapterState.on)
.first
.timeout(const Duration(seconds: 3));
} catch (_) {
_errorMessage = 'Bluetooth désactivé. Veuillez l\'activer.';
notifyListeners();
return;
}
}
// Always stop any existing scan first — prevents SCAN_FAILED_ALREADY_STARTED
try {
await FlutterBluePlus.stopScan();
} catch (_) {}
// Cancel old subscriptions before creating new ones
await _scanSubscription?.cancel();
await _scanningSubscription?.cancel();
_scanSubscription = null;
_scanningSubscription = null;
// Small delay to let the OS fully stop the previous scan
await Future.delayed(const Duration(milliseconds: 300));
_isScanning = true;
notifyListeners();
// Listen to scan results and filter by our service UUID
_scanSubscription = FlutterBluePlus.onScanResults.listen(
(results) {
for (final r in results) {
// UUID filter disabled — shows all BT devices
// Uncomment below to filter by jigger service UUID only:
// final advertisedUuids = r.advertisementData.serviceUuids
// .map((u) => u.toString().toLowerCase())
// .toList();
// if (!advertisedUuids.contains(serviceUuid.toLowerCase())) continue;
final id = r.device.remoteId.str;
if (_devices.any((d) => d.id == id)) continue;
final name = r.device.platformName.isNotEmpty
? r.device.platformName
: (r.advertisementData.advName.isNotEmpty
? r.advertisementData.advName
: 'Jigger inconnu');
final device = BtDevice(id: id, originalName: name);
if (_customNames.containsKey(id)) {
device.customName = _customNames[id]!;
}
_devices.add(device);
notifyListeners();
}
},
onError: (e) {
_errorMessage = 'Erreur de scan: $e';
_isScanning = false;
notifyListeners();
},
);
// Track when the scan finishes naturally
_scanningSubscription = FlutterBluePlus.isScanning.listen((scanning) {
if (!scanning && _isScanning) {
_isScanning = false;
notifyListeners();
}
});
try {
await FlutterBluePlus.startScan(
// withServices: [Guid(serviceUuid)], // Uncomment to filter by UUID
timeout: const Duration(seconds: 10),
);
} catch (e) {
_errorMessage = 'Impossible de démarrer le scan: $e';
_isScanning = false;
notifyListeners();
}
}
Future<void> stopScan() async {
try {
await FlutterBluePlus.stopScan();
} catch (_) {}
_isScanning = false;
notifyListeners();
}
// ── Selection ─────────────────────────────────────────────────
void selectDevice(BtDevice? device) {
_selectedDevice = device;
notifyListeners();
}
// ── Rename (in-memory) ────────────────────────────────────────
void renameDevice(String deviceId, String newName) {
final trimmed = newName.trim();
_customNames[deviceId] = trimmed;
final idx = _devices.indexWhere((d) => d.id == deviceId);
if (idx >= 0) {
_devices[idx] = _devices[idx].copyWith(customName: trimmed);
if (_selectedDevice?.id == deviceId) {
_selectedDevice = _devices[idx];
}
}
notifyListeners();
}
// ── Command Sending (stub) ────────────────────────────────────
Future<void> sendCommand(String command) async {
if (_selectedDevice == null) {
debugPrint('[BT] Aucun appareil sélectionné.');
return;
}
debugPrint('[BT] → ${_selectedDevice!.displayName}: $command');
// TODO: connect + write to characteristicUuid
}
@override
void dispose() {
_scanSubscription?.cancel();
_scanningSubscription?.cancel();
FlutterBluePlus.stopScan();
super.dispose();
}
}
解决方案
问题原因是依赖错误。
开发依赖中的依赖没有被包含在生产依赖中。
请检查pubspec.yaml。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。