Flutter后台定位插件background_location_tracker的使用

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

Flutter后台定位插件background_location_tracker的使用

background_location_tracker 是一个允许你在Android和iOS平台上进行后台位置跟踪的新Flutter插件。以下是如何配置和使用的详细指南。

Android配置

更新编译SDK

确保你的compileSdkVersiontargetSdkVersion至少为29:

android {
  ...
  compileSdkVersion 29
  ...

  defaultConfig {
    ...
    targetSdkVersion 29
    ...
  }
  ...
}

iOS配置

更新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>

更新AppDelegate

确保调用setPluginRegistrantCallback以使其他插件在后台可用:

import UIKit
import Flutter
import background_location_tracker

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        GeneratedPluginRegistrant.register(with: self)

        BackgroundLocationTrackerPlugin.setPluginRegistrantCallback { registry in
            GeneratedPluginRegistrant.register(with: registry)
        }

        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

Flutter实现

确保设置@pragma('vm:entry-point')以便在发布模式下找到回调函数:

@pragma('vm:entry-point')
void backgroundCallback() {
  BackgroundLocationTrackerManager.handleBackgroundUpdated(
    (data) async => Repo().update(data),
  );
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await BackgroundLocationTrackerManager.initialize(
    backgroundCallback,
    config: const BackgroundLocationTrackerConfig(
      loggingEnabled: true,
      androidConfig: AndroidConfig(
        notificationIcon: 'explore',
        trackingInterval: Duration(seconds: 4),
        distanceFilterMeters: null,
      ),
      iOSConfig: IOSConfig(
        activityType: ActivityType.FITNESS,
        distanceFilterMeters: null,
        restartAfterKill: true,
      ),
    ),
  );

  runApp(MyApp());
}

Future startLocationTracking() async {
  await BackgroundLocationTrackerManager.startTracking();
}

Future stopLocationTracking() async {
  await BackgroundLocationTrackerManager.stopTracking();
}

示例代码

以下是完整的示例代码,展示了如何使用background_location_tracker插件:

import 'dart:async';
import 'dart:io';
import 'dart:math';

import 'package:background_location_tracker/background_location_tracker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';

@pragma('vm:entry-point')
void backgroundCallback() {
  BackgroundLocationTrackerManager.handleBackgroundUpdated(
    (data) async => Repo().update(data),
  );
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await BackgroundLocationTrackerManager.initialize(
    backgroundCallback,
    config: const BackgroundLocationTrackerConfig(
      loggingEnabled: true,
      androidConfig: AndroidConfig(
        notificationIcon: 'explore',
        trackingInterval: Duration(seconds: 4),
        distanceFilterMeters: null,
      ),
      iOSConfig: IOSConfig(
        activityType: ActivityType.FITNESS,
        distanceFilterMeters: null,
        restartAfterKill: true,
      ),
    ),
  );

  runApp(MyApp());
}

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

class _MyAppState extends State<MyApp> {
  var isTracking = false;

  Timer? _timer;
  List<String> _locations = [];

  @override
  void initState() {
    super.initState();
    _getTrackingStatus();
    _startLocationsUpdatesStream();
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Container(
          width: double.infinity,
          child: Column(
            children: [
              Expanded(
                child: Column(
                  children: [
                    MaterialButton(
                      child: const Text('Request location permission'),
                      onPressed: _requestLocationPermission,
                    ),
                    if (Platform.isAndroid) ...[
                      const Text(
                          'Permission on android is only needed starting from sdk 33.'),
                    ],
                    MaterialButton(
                      child: const Text('Request Notification permission'),
                      onPressed: _requestNotificationPermission,
                    ),
                    MaterialButton(
                      child: const Text('Send notification'),
                      onPressed: () => sendNotification('Hello from another world'),
                    ),
                    MaterialButton(
                      child: const Text('Start Tracking'),
                      onPressed: isTracking
                          ? null
                          : () async {
                              await BackgroundLocationTrackerManager.startTracking();
                              setState(() => isTracking = true);
                            },
                    ),
                    MaterialButton(
                      child: const Text('Stop Tracking'),
                      onPressed: isTracking
                          ? () async {
                              await LocationDao().clear();
                              await _getLocations();
                              await BackgroundLocationTrackerManager.stopTracking();
                              setState(() => isTracking = false);
                            }
                          : null,
                    ),
                  ],
                ),
              ),
              const SizedBox(height: 8),
              Container(
                color: Colors.black12,
                height: 2,
              ),
              const Text('Locations'),
              MaterialButton(
                child: const Text('Refresh locations'),
                onPressed: _getLocations,
              ),
              Expanded(
                child: Builder(
                  builder: (context) {
                    if (_locations.isEmpty) {
                      return const Text('No locations saved');
                    }
                    return ListView.builder(
                      itemCount: _locations.length,
                      itemBuilder: (context, index) => Padding(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 16,
                          vertical: 12,
                        ),
                        child: Text(
                          _locations[index],
                        ),
                      ),
                    );
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Future<void> _getTrackingStatus() async {
    isTracking = await BackgroundLocationTrackerManager.isTracking();
    setState(() {});
  }

  Future<void> _requestLocationPermission() async {
    final result = await Permission.location.request();
    if (result == PermissionStatus.granted) {
      print('GRANTED'); // ignore: avoid_print
    } else {
      print('NOT GRANTED'); // ignore: avoid_print
    }
  }

  Future<void> _requestNotificationPermission() async {
    final result = await Permission.notification.request();
    if (result == PermissionStatus.granted) {
      print('GRANTED'); // ignore: avoid_print
    } else {
      print('NOT GRANTED'); // ignore: avoid_print
    }
  }

  Future<void> _getLocations() async {
    final locations = await LocationDao().getLocations();
    setState(() {
      _locations = locations;
    });
  }

  void _startLocationsUpdatesStream() {
    _timer?.cancel();
    _timer = Timer.periodic(
        const Duration(milliseconds: 250), (timer) => _getLocations());
  }
}

class Repo {
  static Repo? _instance;

  Repo._();

  factory Repo() => _instance ??= Repo._();

  Future<void> update(BackgroundLocationUpdateData data) async {
    final text = 'Location Update: Lat: ${data.lat} Lon: ${data.lon}';
    print(text); // ignore: avoid_print
    sendNotification(text);
    await LocationDao().saveLocation(data);
  }
}

class LocationDao {
  static const _locationsKey = 'background_updated_locations';
  static const _locationSeparator = '-/-/-/';

  static LocationDao? _instance;

  LocationDao._();

  factory LocationDao() => _instance ??= LocationDao._();

  SharedPreferences? _prefs;

  Future<SharedPreferences> get prefs async =>
      _prefs ??= await SharedPreferences.getInstance();

  Future<void> saveLocation(BackgroundLocationUpdateData data) async {
    final locations = await getLocations();
    locations.add(
        '${DateTime.now().toIso8601String()}       ${data.lat},${data.lon}');
    await (await prefs)
        .setString(_locationsKey, locations.join(_locationSeparator));
  }

  Future<List<String>> getLocations() async {
    final prefs = await this.prefs;
    await prefs.reload();
    final locationsString = prefs.getString(_locationsKey);
    if (locationsString == null) return [];
    return locationsString.split(_locationSeparator);
  }

  Future<void> clear() async => (await prefs).clear();
}

void sendNotification(String text) {
  const settings = InitializationSettings(
    android: AndroidInitializationSettings('app_icon'),
    iOS: IOSInitializationSettings(
      requestAlertPermission: false,
      requestBadgePermission: false,
      requestSoundPermission: false,
    ),
  );
  FlutterLocalNotificationsPlugin().initialize(
    settings,
    onSelectNotification: (data) async {
      print('ON CLICK $data'); // ignore: avoid_print
    },
  );
  FlutterLocalNotificationsPlugin().show(
    Random().nextInt(9999),
    'Title',
    text,
    const NotificationDetails(
      android: AndroidNotificationDetails('test_notification', 'Test'),
      iOS: IOSNotificationDetails(),
    ),
  );
}

通过以上步骤和示例代码,你应该能够成功地在Flutter项目中集成并使用background_location_tracker插件进行后台位置跟踪。如果有任何问题或需要进一步的帮助,请随时提问!


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

1 回复

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


当然,以下是如何在Flutter项目中集成和使用background_location_tracker插件的详细步骤,包括必要的代码示例。

1. 添加依赖

首先,在你的pubspec.yaml文件中添加background_location_tracker依赖:

dependencies:
  flutter:
    sdk: flutter
  background_location_tracker: ^x.y.z  # 请将x.y.z替换为最新版本号

然后运行flutter pub get来安装依赖。

2. 配置Android权限

打开android/app/src/main/AndroidManifest.xml,添加以下权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yourapp">

    <!-- 其他配置 -->

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />

    <!-- 其他配置 -->

    <application
        android:label="yourapp"
        android:icon="@mipmap/ic_launcher">
        <!-- 其他配置 -->

        <!-- 声明前台服务 -->
        <service
            android:name="com.rekab.location.foreground.service.ForegroundService"
            android:foregroundServiceType="location" />

        <!-- 其他配置 -->
    </application>
</manifest>

3. 配置iOS权限

打开ios/Runner/Info.plist,添加以下权限:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Your app needs access to your location when in use and in the background</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Your app needs access to your location even when it is not in use</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your app needs access to your location when it is in use</string>
<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>

4. 初始化并使用插件

在你的Flutter项目的Dart代码中,初始化并使用background_location_tracker插件。以下是一个简单的示例:

import 'package:flutter/material.dart';
import 'package:background_location_tracker/background_location_tracker.dart';
import 'package:permission_handler/permission_handler.dart';

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

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

class _MyAppState extends State<MyApp> {
  BackgroundLocationTracker? _locationTracker;
  bool _locationServiceEnabled = false;
  PermissionStatus? _permissionStatus;

  @override
  void initState() {
    super.initState();
    _locationTracker = BackgroundLocationTracker();
    
    // 请求位置权限
    _requestLocationPermission();

    // 初始化位置服务
    _initLocationService();
  }

  Future<void> _requestLocationPermission() async {
    var status = await Permission.locationAlways.request();
    setState(() {
      _permissionStatus = status;
    });

    if (status.isGranted) {
      // 权限被授予
      _initLocationService();
    } else if (status.isDenied || status.isPermanentlyDenied) {
      // 权限被拒绝
      // 可以显示一个对话框提示用户去设置中开启权限
    }
  }

  Future<void> _initLocationService() async {
    if (_permissionStatus == PermissionStatus.granted) {
      bool serviceEnabled;
      LocationAccuracy accuracy = LocationAccuracy.HIGH;
      AndroidSettings androidSettings = AndroidSettings(
        accuracy: accuracy,
        distanceFilter: 10.0,
        interval: 60000,
        fastestInterval: 12000,
        activitiesInterval: 10000,
        pauseLocationUpdates: false,
        stopOnTerminate: false,
        startOnBoot: true,
      );
      AppleSettings appleSettings = AppleSettings(
        accuracy: accuracy,
        pauseLocationUpdates: false,
        activityType: ActivityType.automotiveNavigation,
        allowDeferredLocationUpdatesUntilTraveled: Distance(meters: 1000),
        deferredLocationUpdatesInterval: 60,
        disableMotionActivityUpdates: false,
      );

      serviceEnabled = await _locationTracker!.configure(
        accuracy: accuracy,
        distanceFilter: 10.0,
        androidSettings: androidSettings,
        appleSettings: appleSettings,
      );

      if (serviceEnabled) {
        setState(() {
          _locationServiceEnabled = true;
        });

        // 开始位置更新
        _locationTracker!.startLocationUpdates()
          .listen((LocationDto locationDto) {
            // 处理位置更新
            print('Location: ${locationDto.latitude}, ${locationDto.longitude}');
          });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Background Location Tracker'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Location Service Enabled: $_locationServiceEnabled'),
              Text('Permission Status: $_permissionStatus'),
            ],
          ),
        ),
      ),
    );
  }
}

注意事项

  1. 权限处理:确保你处理了权限请求的结果,并在必要时提示用户去设置中手动开启权限。
  2. 后台运行:在iOS上,确保你的应用具有后台运行位置更新的能力。这通常需要在项目的Capabilities中启用后台模式,并添加UIBackgroundModes键值对。
  3. 电池优化:长时间后台运行位置更新可能会影响电池寿命,建议仅在必要时使用。

这个示例展示了如何集成和使用background_location_tracker插件进行后台位置跟踪。根据你的具体需求,你可能需要调整配置和代码。

回到顶部