java - 在后台执行/运行 Flutter 应用程序
问题描述
现在我正在做一个项目,我正在通过BLE将数据从微控制器(ESP32)发送到我的Flutter App。当数据的一个值大于定义的限制时,播放声音,然后在低于限制时停止。 现在的问题是:如果应用程序处于“后台模式”,Dart 代码将不会被执行。如果尝试使用 android-alarm-manager 插件或 WidgetBindingObserver 解决问题,但找不到合适的解决方案。
我的 main.dart 文件:
// Copyright 2017, Paul DeMarco.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_blue_app_2/werte.dart';
import 'package:flutter_blue_app_2/widgets.dart';
import 'package:flutter_blue/flutter_blue.dart';
void main() {
runApp(FlutterBlueApp());
}
class FlutterBlueApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
color: Colors.lightBlue,
home: StreamBuilder<BluetoothState>(
stream: FlutterBlue.instance.state,
initialData: BluetoothState.unknown,
builder: (c, snapshot) {
final state = snapshot.data;
if (state == BluetoothState.on) {
return FindDevicesScreen();
}
return BluetoothOffScreen(state: state);
}),
);
}
}
class BluetoothOffScreen extends StatelessWidget {
const BluetoothOffScreen({Key key, this.state}) : super(key: key);
final BluetoothState state;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.lightBlue,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.bluetooth_disabled,
size: 200.0,
color: Colors.white54,
),
Text(
"BLUETOOTH ADAPTER IS ${state.toString().substring(15)}.",
style: Theme.of(context)
.primaryTextTheme
.subhead
.copyWith(color: Colors.white),
),
],
),
),
);
}
}
class FindDevicesScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("FIND DEVICES ..."),
),
body: RefreshIndicator(
onRefresh: () =>
FlutterBlue.instance.startScan(timeout: Duration(seconds: 4)),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
StreamBuilder<List<BluetoothDevice>>(
stream: Stream.periodic(Duration(seconds: 2))
.asyncMap((_) => FlutterBlue.instance.connectedDevices),
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data
.map((d) => ListTile(
title: Text(d.name),
subtitle: Text(d.id.toString()),
trailing: StreamBuilder<BluetoothDeviceState>(
stream: d.state,
initialData: BluetoothDeviceState.disconnected,
builder: (c, snapshot) {
if (snapshot.data ==
BluetoothDeviceState.connected) {
return RaisedButton(
child: Text("OPEN"),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
DeviceScreen(device: d))),
);
}
return Text(snapshot.data.toString());
},
),
))
.toList(),
),
),
StreamBuilder<List<ScanResult>>(
stream: FlutterBlue.instance.scanResults,
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data
.map(
(r) => ScanResultTile(
result: r,
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
r.device.connect();
return SensorPage(device: r.device);
})),
),
)
.toList(),
),
),
],
),
),
),
floatingActionButton: StreamBuilder<bool>(
stream: FlutterBlue.instance.isScanning,
initialData: false,
builder: (c, snapshot) {
if (snapshot.data) {
return FloatingActionButton(
child: Icon(Icons.stop),
onPressed: () => FlutterBlue.instance.stopScan(),
backgroundColor: Colors.red,
);
} else {
return FloatingActionButton(
child: Icon(Icons.search),
onPressed: () => FlutterBlue.instance
.startScan(timeout: Duration(seconds: 4)));
}
},
),
);
}
}
class DeviceScreen extends StatelessWidget {
const DeviceScreen({Key key, this.device}) : super(key: key);
final BluetoothDevice device;
List<Widget> _buildServiceTiles(List<BluetoothService> services) {
return services
.map(
(s) => ServiceTile(
service: s,
characteristicTiles: s.characteristics
.map(
(c) => CharacteristicTile(
characteristic: c,
onReadPressed: () => c.read(),
onWritePressed: () => c.write([13, 24]),
onNotificationPressed: () =>
c.setNotifyValue(!c.isNotifying),
descriptorTiles: c.descriptors
.map(
(d) => DescriptorTile(
descriptor: d,
onReadPressed: () => d.read(),
onWritePressed: () => d.write([11, 12]),
),
)
.toList(),
),
)
.toList(),
),
)
.toList();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(device.name),
actions: <Widget>[
StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (c, snapshot) {
VoidCallback onPressed;
String text;
switch (snapshot.data) {
case BluetoothDeviceState.connected:
onPressed = () => device.disconnect();
text = "DISCONNECT";
break;
case BluetoothDeviceState.disconnected:
onPressed = () => device.connect();
text = "CONNECT";
break;
default:
onPressed = null;
text = snapshot.data.toString().substring(21).toUpperCase();
break;
}
return FlatButton(
onPressed: onPressed,
child: Text(
text,
style: Theme.of(context)
.primaryTextTheme
.button
.copyWith(color: Colors.white),
),
);
},
)
],
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (c, snapshot) => ListTile(
leading: (snapshot.data == BluetoothDeviceState.connected)
? Icon(Icons.bluetooth_connected)
: Icon(Icons.bluetooth_disabled),
title: Text(
"Device is ${snapshot.data.toString().split('.')[1]}."),
subtitle: Text("${device.id}"),
trailing: StreamBuilder<bool>(
stream: device.isDiscoveringServices,
initialData: false,
builder: (c, snapshot) => IndexedStack(
index: snapshot.data ? 1 : 0,
children: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () => device.discoverServices(),
),
IconButton(
icon: SizedBox(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.grey),
),
width: 18.0,
height: 18.0,
),
onPressed: null,
)
],
),
),
),
),
StreamBuilder<int>(
stream: device.mtu,
initialData: 0,
builder: (c, snapshot) => ListTile(
title: Text("MTU Size"),
subtitle: Text("${snapshot.data} bytes"),
trailing: IconButton(
icon: Icon(Icons.edit),
onPressed: () => device.requestMtu(223),
),
),
),
StreamBuilder<List<BluetoothService>>(
stream: device.services,
initialData: [],
builder: (c, snapshot) {
return Column(
children: _buildServiceTiles(snapshot.data),
);
},
),
],
),
),
);
}
}
我的 werte.dart 文件,其中的值是从微控制器接收的:
import 'dart:async';
import 'dart:convert' show utf8;
import 'package:audioplayers/audio_cache.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:audioplayers/audioplayers.dart';
//import 'package:android_alarm_manager/android_alarm_manager.dart';
class SensorPage extends StatefulWidget {
const SensorPage({Key key, this.device}) : super(key: key);
final BluetoothDevice device;
@override
_SensorPageState createState() => _SensorPageState();
}
class _SensorPageState extends State<SensorPage> {
final String SERVICE_UUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b";
final String CHARACTERISTIC_UUID = "beb5483e-36e1-4688-b7f5-ea07361b26a8";
bool isReady;
var extrem = 0;
var val1 = "";
var pot1 = 0;
var val2 = "";
var pot2 = 0;
var x = 0;
Stream<List<int>> stream;
AudioPlayer advancedPlayer;
AudioCache audioCache;
//var state;
/*void printHello() {
initState();
/*final DateTime now = DateTime.now();
print("[$now] Hello, world! function='$printHello'");*/
}*/
@override
void initState() {
super.initState();
isReady = false;
initPlayer();
connectToDevice();
}
bool _isVisible = true;
void showToast() {
setState(() {
_isVisible = !_isVisible;
});
}
/*void alarmmanager() async {
await AndroidAlarmManager.initialize();
await AndroidAlarmManager.oneShot(
const Duration(seconds: 1), 0, printHello);
}*/
void initPlayer() {
advancedPlayer = new AudioPlayer();
audioCache = new AudioCache(fixedPlayer: advancedPlayer);
}
String localFilePath;
connectToDevice() async {
if (widget.device == null) {
_Pop();
return;
}
new Timer(
const Duration(seconds: 15),
() {
if (!isReady) {
disconnectFromDevice();
_Pop();
}
},
);
await widget.device.connect();
discoverServices();
}
disconnectFromDevice() {
if (widget.device == null) {
_Pop();
return;
}
widget.device.disconnect();
}
discoverServices() async {
if (widget.device == null) {
_Pop();
return;
}
List<BluetoothService> services = await widget.device.discoverServices();
services.forEach((service) {
if (service.uuid.toString() == SERVICE_UUID) {
service.characteristics.forEach((characteristic) {
if (characteristic.uuid.toString() == CHARACTERISTIC_UUID) {
characteristic.setNotifyValue(!characteristic.isNotifying);
stream = characteristic.value;
setState(() {
isReady = true;
});
}
});
}
});
if (!isReady) {
_Pop();
}
}
Future<bool> _onWillPop() {
return showDialog(
context: context,
builder: (context) =>
new AlertDialog(
title: Text(
"ARE YOU SURE?",
style: TextStyle(fontSize: 18, fontFamily: "Montserrat"),
),
content: Text(
"DO YOU WANT TO DISCONNECT DEVICE AND GO BACK?",
style: TextStyle(fontSize: 18, fontFamily: "Montserrat"),
),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: new Text(
"NO",
style: TextStyle(fontSize: 14, fontFamily: "Montserrat"),
),
),
new FlatButton(
onPressed: () {
disconnectFromDevice();
Navigator.of(context).pop(true);
},
child: new Text(
"YES",
style: TextStyle(fontSize: 14, fontFamily: "Montserrat"),
),
),
],
) ??
false);
}
_Pop() {
Navigator.of(context).pop(true);
}
String _dataParser(List<int> dataFromDevice) {
return utf8.decode(dataFromDevice);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.red[800],
title: Text(
"T(RAIN)-SHIRT",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24),
),
),
body: Container(
child: !isReady
? Center(
child: CircularProgressIndicator(),
)
: Container(
child: StreamBuilder<List<int>>(
stream: stream,
builder: (BuildContext context,
AsyncSnapshot<List<int>> snapshot) {
if (snapshot.hasError)
return Text("Error: ${snapshot.error}");
if (snapshot.connectionState == ConnectionState.active) {
if (x > 0) {
var currentValue = _dataParser(snapshot.data);
val1 = currentValue.split(',')[0];
pot1 = int.parse(val1);
val2 = currentValue.split(',')[1];
pot2 = int.parse(val2);
}
if (pot1 >= 1500 && pot1 < 2500) {
audioCache.play("audio.mp4");
advancedPlayer.setVolume(0.3);
extrem++;
} else if (pot1 >= 2500 && pot1 < 3500) {
audioCache.play("audio.mp4");
advancedPlayer.setVolume(0.6);
extrem++;
} else if (pot1 >= 3500 && pot1 <= 4095) {
audioCache.play("audio.mp4");
advancedPlayer.setVolume(1.0);
extrem++;
} else {
advancedPlayer.stop();
}
//alarmmanager();
return Center(
child: Visibility(
visible: _isVisible,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
"$extrem",
style: TextStyle(
color: Colors.black,
fontSize: 30,
fontWeight: FontWeight.bold,
fontFamily: "Montserrat"),
),
RaisedButton(
child: Text(
"START",
style: TextStyle(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.bold,
fontFamily: "Montserrat"),
),
onPressed: () {
showToast();
x++;
extrem = 0;
},
color: Colors.red[800],
),
],
),
replacement: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
"$pot1",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
fontFamily: "Montserrat"),
),
Text(
"$pot2",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
fontFamily: "Montserrat"),
),
RaisedButton(
child: Text(
"STOP",
style: TextStyle(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.bold,
fontFamily: "Montserrat"),
),
onPressed: () {
showToast();
x = 0;
pot1 = 0;
pot2 = 0;
},
color: Colors.red[800],
),
],
),
),
);
} else {
return Text("CHECK THE STREAM");
}
},
),
),
),
backgroundColor: Colors.grey[300],
),
);
}
}
如果我处于“后台模式”,如果条件为真,我仍想从 ESP32 接收数据并播放声音。也许有人有一个简单的解决方案,关于如何在后台执行 Dart 代码。
谢谢!
解决方案
推荐阅读
- c# - 无法创建数据透视表
- vba - 运行时错误“1004”:无法访问文件“C:\WINDOWS\System32”
- java - 将 minSDK 设置为 Jelly Bean 时我的应用程序崩溃
- javascript - 使用javascript排序更改基于另一个数组的顺序
- python - 在 Postgresql 中保存时区时出错
- ios - IOS/Objective-C:将自定义对象的 NSArray 转换为 JSON
- c++ - 为什么这会一直打印圆圈的宽度?
- android - Android - 如何使用firebase在帖子中显示喜欢的数量
- amazon-web-services - "You are not authorized to access this operation"
- haskell - 图表和 gtk2hs