Flutter后台定位插件background_locator的使用

发布于 1周前 作者 ionicwang 来自 Flutter

Flutter后台定位插件background_locator的使用

插件简介

background_locator 是一个Flutter插件,允许在应用程序被杀死的情况下仍能接收位置更新。该插件可以满足开发者对于应用在后台或完全关闭后继续获取用户地理位置的需求,例如用于记录运动轨迹、地理围栏等功能。

demo

更多详情请参考 Wiki页面,其中包括但不限于:

License

本项目采用MIT许可证,详细内容见LICENSE文件

Contributor

感谢所有为此插件做出贡献的人,包括但不限于:

示例代码

下面是一个完整的示例demo,演示了如何使用background_locator插件来实现后台定位功能。

import 'dart:async';
import 'dart:isolate';
import 'dart:ui';

import 'package:background_locator/background_locator.dart';
import 'package:background_locator/location_dto.dart';
import 'package:background_locator/settings/android_settings.dart';
import 'package:background_locator/settings/ios_settings.dart';
import 'package:background_locator/settings/locator_settings.dart';
import 'package:flutter/material.dart';
import 'package:location_permissions/location_permissions.dart';

import 'file_manager.dart';
import 'location_callback_handler.dart';
import 'location_service_repository.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ReceivePort port = ReceivePort();

  String logStr = '';
  bool isRunning;
  LocationDto lastLocation;

  @override
  void initState() {
    super.initState();

    if (IsolateNameServer.lookupPortByName(LocationServiceRepository.isolateName) != null) {
      IsolateNameServer.removePortNameMapping(LocationServiceRepository.isolateName);
    }

    IsolateNameServer.registerPortWithName(port.sendPort, LocationServiceRepository.isolateName);

    port.listen(
      (dynamic data) async {
        await updateUI(data);
      },
    );
    initPlatformState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  Future<void> updateUI(LocationDto data) async {
    final log = await FileManager.readLogFile();

    await _updateNotificationText(data);

    setState(() {
      if (data != null) {
        lastLocation = data;
      }
      logStr = log;
    });
  }

  Future<void> _updateNotificationText(LocationDto data) async {
    if (data == null) {
      return;
    }

    await BackgroundLocator.updateNotificationText(
        title: "new location received",
        msg: "${DateTime.now()}",
        bigMsg: "${data.latitude}, ${data.longitude}");
  }

  Future<void> initPlatformState() async {
    print('Initializing...');
    await BackgroundLocator.initialize();
    logStr = await FileManager.readLogFile();
    print('Initialization done');
    final _isRunning = await BackgroundLocator.isServiceRunning();
    setState(() {
      isRunning = _isRunning;
    });
    print('Running ${isRunning.toString()}');
  }

  @override
  Widget build(BuildContext context) {
    final start = SizedBox(
      width: double.maxFinite,
      child: ElevatedButton(
        child: Text('Start'),
        onPressed: () {
          _onStart();
        },
      ),
    );
    final stop = SizedBox(
      width: double.maxFinite,
      child: ElevatedButton(
        child: Text('Stop'),
        onPressed: () {
          onStop();
        },
      ),
    );
    final clear = SizedBox(
      width: double.maxFinite,
      child: ElevatedButton(
        child: Text('Clear Log'),
        onPressed: () {
          FileManager.clearLogFile();
          setState(() {
            logStr = '';
          });
        },
      ),
    );
    String msgStatus = "-";
    if (isRunning != null) {
      if (isRunning) {
        msgStatus = 'Is running';
      } else {
        msgStatus = 'Is not running';
      }
    }
    final status = Text("Status: $msgStatus");

    final log = Text(
      logStr,
    );

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter background Locator'),
        ),
        body: Container(
          width: double.maxFinite,
          padding: const EdgeInsets.all(22),
          child: SingleChildScrollView(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[start, stop, clear, status, log],
            ),
          ),
        ),
      ),
    );
  }

  void onStop() async {
    await BackgroundLocator.unRegisterLocationUpdate();
    final _isRunning = await BackgroundLocator.isServiceRunning();
    setState(() {
      isRunning = _isRunning;
    });
  }

  void _onStart() async {
    if (await _checkLocationPermission()) {
      await _startLocator();
      final _isRunning = await BackgroundLocator.isServiceRunning();

      setState(() {
        isRunning = _isRunning;
        lastLocation = null;
      });
    } else {
      // show error
    }
  }

  Future<bool> _checkLocationPermission() async {
    final access = await LocationPermissions().checkPermissionStatus();
    switch (access) {
      case PermissionStatus.unknown:
      case PermissionStatus.denied:
      case PermissionStatus.restricted:
        final permission = await LocationPermissions().requestPermissions(
          permissionLevel: LocationPermissionLevel.locationAlways,
        );
        if (permission == PermissionStatus.granted) {
          return true;
        } else {
          return false;
        }
        break;
      case PermissionStatus.granted:
        return true;
        break;
      default:
        return false;
        break;
    }
  }

  Future<void> _startLocator() async{
    Map<String, dynamic> data = {'countInit': 1};
    return await BackgroundLocator.registerLocationUpdate(LocationCallbackHandler.callback,
        initCallback: LocationCallbackHandler.initCallback,
        initDataCallback: data,
        disposeCallback: LocationCallbackHandler.disposeCallback,
        iosSettings: IOSSettings(
            accuracy: LocationAccuracy.NAVIGATION, distanceFilter: 0),
        autoStop: false,
        androidSettings: AndroidSettings(
            accuracy: LocationAccuracy.NAVIGATION,
            interval: 5,
            distanceFilter: 0,
            client: LocationClient.google,
            androidNotificationSettings: AndroidNotificationSettings(
                notificationChannelName: 'Location tracking',
                notificationTitle: 'Start Location Tracking',
                notificationMsg: 'Track location in background',
                notificationBigMsg:
                    'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.',
                notificationIconColor: Colors.grey,
                notificationTapCallback:
                    LocationCallbackHandler.notificationCallback)));
  }
}

代码说明

  • ReceivePortIsolateNameServer 用于处理与后台服务之间的通信。
  • updateUI 方法负责更新UI以反映最新的位置信息。
  • _updateNotificationText 方法用于更新通知文本。
  • initPlatformState 方法初始化平台状态并检查服务是否正在运行。
  • _onStartonStop 方法分别用于启动和停止位置更新服务。
  • _checkLocationPermission 方法确保应用具有必要的权限来访问位置数据。
  • _startLocator 方法配置并注册位置更新服务,包括iOS和Android的具体设置。

以上就是关于background_locator插件的详细介绍及使用示例。希望对您有所帮助!如果有任何问题或者需要进一步的帮助,请随时提问。


更多关于Flutter后台定位插件background_locator的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter后台定位插件background_locator的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter应用中使用background_locator插件来实现后台定位的示例代码。这个插件允许应用在后台持续获取用户的位置信息。

首先,确保你已经在pubspec.yaml文件中添加了background_locatorgeolocator依赖项:

dependencies:
  flutter:
    sdk: flutter
  geolocator: ^9.0.2  # 请检查最新版本
  background_locator: ^2.0.0  # 请检查最新版本

然后,运行flutter pub get来安装这些依赖项。

接下来,按照以下步骤配置和使用background_locator插件:

1. 配置iOS和Android权限

iOS

ios/Runner/Info.plist文件中添加以下权限声明:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>需要后台定位权限</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>需要后台定位权限</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要定位权限</string>
<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>

Android

android/app/src/main/AndroidManifest.xml文件中添加以下权限声明:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

同时,在android/app/src/main/kotlin/[your_package_name]/MainActivity.kt(或者MainActivity.java,如果你使用的是Java)中添加以下代码以处理权限请求(对于Kotlin):

package com.example.yourappname

import io.flutter.embedding.android.FlutterActivity
import android.os.Bundle
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import android.Manifest

class MainActivity: FlutterActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 请求后台定位权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION), 1)
        }
    }
}

2. 初始化Background Locator

在你的Flutter应用中,初始化background_locator。在lib目录下创建一个新的Dart文件,比如location_service.dart,并添加以下代码:

import 'dart:async';
import 'package:background_locator/background_locator.dart';
import 'package:geolocator/geolocator.dart';

class LocationService {
  BackgroundLocatorCallback _callback;
  LocationCallback _locationCallback;

  LocationService._() {
    _locationCallback = (Position position) async {
      // 在这里处理位置更新,比如保存到数据库
      print('Latitude: ${position.latitude}, Longitude: ${position.longitude}');
    };

    _callback = BackgroundLocator.Callback(
      onLocationChanged: _locationCallback,
      onPermissionDenied: () {
        print('[BackgroundLocator] Permission denied');
      },
      onServiceDisabled: () {
        print('[BackgroundLocator] Service disabled');
      },
    );

    BackgroundLocator.registerLocationUpdate(_callback).then((_) {
      print('[BackgroundLocator] BackgroundLocator is configured successfully');
    }).catchError((onError) {
      print('[BackgroundLocator] FAILED to configure BackgroundLocator: $onError');
    });
  }

  static final LocationService _singleton = LocationService._();

  factory LocationService() => _singleton;

  Future<void> startLocationService() async {
    // 请求权限
    bool serviceEnabled;
    LocationPermission permission;

    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      return Future.error('Location services are disabled.');
    }

    permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        return Future.error('Location permissions are denied');
      }
    }

    if (permission == LocationPermission.deniedForever) {
      // 处理永久拒绝的情况
      return Future.error('Location permissions are permanently denied, we cannot request permissions.');
    }

    // 开始后台定位服务
    await BackgroundLocator.startLocationUpdates();
  }
}

3. 使用LocationService

在你的主应用文件(比如main.dart)中,使用LocationService来启动后台定位服务:

import 'package:flutter/material.dart';
import 'location_service.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    LocationService().startLocationService().then((_) {
      print('Location service started');
    }).catchError((onError) {
      print('Error starting location service: $onError');
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Background Locator Example'),
        ),
        body: Center(
          child: Text('Check the console for location updates'),
        ),
      ),
    );
  }
}

这样,你的Flutter应用就配置好了后台定位服务。当应用在后台运行时,它将持续获取用户的位置信息,并在控制台中打印出来。

请注意,实际应用中你可能需要处理更多的边缘情况和用户交互,比如处理权限请求的结果、在UI中显示位置信息、管理后台任务的生命周期等。此外,后台定位会消耗设备的电池和数据,因此确保你的应用有合理的后台定位策略,以优化用户体验和节省资源。

回到顶部