首页 > 解决方案 > 在后台执行/运行 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 代码。

谢谢!

标签: javaandroidflutterdart

解决方案


如果您计划在后台运行任务,您可能需要考虑使用Isolate。这是一个编写良好的创建后台任务的指南,可帮助您入门。您还可以查看这篇文章,了解 Isolates 的简单介绍。


推荐阅读