如何让Flutter Workmanager插件和Location插件一起工作

14

1. 问题描述

我的目标是构建一个Flutter应用,使用这个workmanager插件这个location插件获取定期的位置更新。但是当我的Workmanager回调触发时,我无法正确加载Location插件。我收到以下错误信息:

MissingPluginException(No implementation found for method getLocation on channel lyokone/location)

基本上,问题在于当Workmanager插件尝试运行dart代码时,它没有加载Location插件。
2. 其他我研究过的资源
我发现其他人也面临同样的问题,这里, 这里, 和 这里
据我所知,这些问题提供的解决方案归结为:创建一个名为CustomApplication.java的文件,它扩展了FlutterApplication,并注册您的插件。然后在AndoidManifest.xml文件中注册CustomApplication.java文件。
3. 我迄今为止的代码
我尝试制作一个最基本的应用程序,实现我需要的功能:
1. 实现Workmanager插件(工作正常) 2. 实现Location插件(工作正常) 3. 尝试组合这些特性(不起作用)
要查看我在每个步骤中所做的确切内容,请看这里:https://gitlab.com/tomoerlemans/workmanager_with_location/-/commits/master。(此存储库还可用于快速复制该问题)。
相关的代码文件如下:

main.dart

import 'package:flutter/material.dart';
import 'package:workmanager/workmanager.dart';
import 'package:location/location.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  Workmanager.initialize(callbackDispatcher, isInDebugMode: true);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              onPressed: () {
                Workmanager.registerPeriodicTask(
                    "1", "simpleTask");
              },
              child: Text("Start workmanager"),
            ),
            RaisedButton(
              onPressed: () {
                getLocation();
              },
              child: Text("Get current location"),
            ),
          ],
        ),
      ),
    );
  }
}

void callbackDispatcher() {
  Workmanager.executeTask((task, inputData) {
    print("Native called background task at ${DateTime.now().toString()}");
    getLocation();
    return Future.value(true);
  });
}

void getLocation() async {
  LocationData currentLocation;
  var location = new Location();
  try {
    currentLocation = await location.getLocation();
  } on Exception catch (e) {
    print("Error obtaining location: $e");
    currentLocation = null;
  }
  print("Location altitude: ${currentLocation.altitude}");
  print("Location longitude: ${currentLocation.longitude}");
}

pubspec.yaml

name: background_location
description: A new Flutter project.

version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  workmanager: ^0.2.0
  location: ^2.3.5


dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

CustomApplication.java

package io.flutter.plugins;

import io.flutter.app.FlutterApplication;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
import io.flutter.plugins.GeneratedPluginRegistrant;
import be.tramckrijte.workmanager.WorkmanagerPlugin;
import com.lyokone.location.LocationPlugin;

public class CustomApplication extends FlutterApplication implements PluginRegistry.PluginRegistrantCallback {
    @Override
    public void onCreate() {
        super.onCreate();
        WorkmanagerPlugin.setPluginRegistrantCallback(this);

    }
    @Override
    public void registerWith(PluginRegistry registry) {
        WorkmanagerPlugin.registerWith(registry.registrarFor("be.tramckrijte.workmanager.WorkmanagerPlugin"));
        LocationPlugin.registerWith(registry.registrarFor("com.lyokone.location.LocationPlugin"));
    }
}

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.background_location">        
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <application
        android:name="io.flutter.plugins.CustomApplication"
        android:label="background_location"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

最后,我正在运行以下版本的Dart/Flutter等:

Flutter 1.12.13+hotfix.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 27321ebbad (10 weeks ago) • 2019-12-10 18:15:01 -0800
Engine • revision 2994f7e1e6
Tools • Dart 2.7.0

2
你已经成功实现它了吗? - Basel Abuhadrous
你好,你找到解决方案了吗? - DarkMath
android:name="io.flutter.plugins.CustomApplication" 这在你的清单文件中是错误的。请使用正确的路径,如果它在根目录中,则只需使用 android:name=".CustomApplication"。 - Shahbaz Hashmi
2个回答

5
我还没有尝试过后台位置跟踪,但考虑到您只想从后台进行位置跟踪,那么我认为workmanager + geolocater 可以胜任。您应该看看这个项目ha_client,它使用workmanager和geolocater一起获取坐标。
此外,您正在使用的位置包具有自己的后台位置跟踪功能,该功能处于实验阶段Background Location Updates

2
使用这两个插件,您可以安排一次性任务并将当前位置传递给它。 be.tramckrijte.workmanager 有一些限制(它甚至声明不支持所有功能)。通过Dart结合这两个插件可能会走向死胡同,因为应用程序并不总是在运行。即使有一个simple_callback_dispatcher_registration.dart,显示了它的工作原理,这也会回调到Flutter引擎而不是查询其他插件。
编写并安排位置感知的 ListenableWorker 可能是运行任务时获取 GPS 定位的最佳选择,除非有任何方法可以通过 Flutter 引擎查询 LocationPlugin 插件在后台运行。我的意思是,如果 ListenableWorker 是位置感知的,它就不需要在其他地方获取位置了。 WorkManager 中的线程 直接说明了这一点:

ListenableWorkerWorkerCoroutineWorkerRxWorker 的基类。它适用于需要与基于回调的异步 API 交互的 Java 开发人员,例如 FusedLocationProviderClient...

因此,ListenableWorkerWorkerCoroutineWorker 将是可扩展的适用类。iOS 实现的等效部分将是位置感知的后台服务。Flutter 引擎可能会启动/停止它们,提供启动参数或接收回调,但通常在后台运行的代码不会是 Dart,因为它应该独立运行。

“它甚至声明并不支持所有功能”这种说法有点笼统。你能告诉我在哪里说明了我要找的功能是不可能实现的吗? - Tom O
1
这种思维方式有点荒谬;这就像是假装某个想象中的实体存在,只因为无法证明它不存在——不管你喜不喜欢,WorkManager文档已经非常清楚了。如果你认为这种抽象会负责任何本地或JVM实现(否则就不会有androidios目录),那么你可能并没有完全理解Flutter的实际含义......所有这个插件所做的就是按名称调度工作任务,并最终接收回调。 - Martin Zeitler
1
一个一次性的任务没有问题,因为抽象层可以直接传递经度/纬度 - 但对于定期任务,你最好只有工作任务被调度时的经度/纬度。任何有经验的 WorkManager 用户可能会建议使用位置感知的 Worker - 唯一的替代方案可能是 flutter_geofire,它在应用程序未运行时也存在限制。 - Martin Zeitler
1
工作管理器插件仓库的主要贡献者之一实际上谈到了如何从工作管理器回调中运行Flutter插件:github.com/vrtdev/flutter_workmanager/issues/6。我在我的原始问题中引用了这个。 - Tom O

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接