简体   繁体   English

在iOS和Gluon Mobile上使用“背景定位服务”

[英]Using 'Background location services' on iOS with gluon mobile

I just started using gluon mobile and I am developing a small iOS app. 我刚刚开始使用gluon mobile,并且正在开发一个小型iOS应用程序。 I managed to use the PositionService to update the users position on a label in the UI. 我设法使用PositionService更新了用户界面中标签上的用户位置。 Now I would like to get Location Updates even if the app is in background mode. 现在,即使应用程序处于后台模式,我也想获取位置更新。 Due to apple developers documentation this should work by adding the following key to the apps plist 由于苹果开发人员的文档,这应该通过将以下密钥添加到应用程序plist中来起作用

<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>

When deploying the app on an iPhone I can see the updates as long as the App is active. 在iPhone上部署应用程序时,只要该应用程序处于活动状态,我就可以看到更新。 When going to the home screen, the updating stops and in the terminal (gradle -> launchIOSDevice) the message "Stop updating location" is shown. 转到主屏幕时,更新停止,并且在终端(等级-> launchIOSDevice)中显示消息“停止更新位置”。 Any idea why I don't get Location Updates in background mode? 知道为什么我没有在后台模式下获得位置更新吗?

Here the relevant code: 这里相关代码:

    Services.get(PositionService.class).ifPresent(service -> {
        service.positionProperty().addListener((obs, oldPos, newPos) -> {
            posLbl.setText(String.format(" %.7f %.7f\nLast update: " + LocalDateTime.now().toString(),
                    newPos.getLatitude(), newPos.getLongitude()));
            handleData();
        });
    });

Here is the relevant plist entry: 这是相关的plist条目:

<key>NSLocationAlwaysUsageDescription</key>
<string>A good reason.</string>
<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>
<key>NSLocationWhenInUseUsageDescription</key>
<string>An even better reason.</string>

The reason why the Position service is not working when the app goes into background mode can be found here : 当应用程序进入后台模式时,Position服务不起作用的原因可以在这里找到:

Services.get(LifecycleService.class).ifPresent(l -> {
        l.addListener(LifecycleEvent.PAUSE, IOSPositionService::stopObserver);
        l.addListener(LifecycleEvent.RESUME, IOSPositionService::startObserver);
    });
startObserver();

The lifecycle service is designed to prevent doing unnecessary stuff when the app is in background mainly to save battery. 生命周期服务旨在防止应用程序在后台运行时做不必要的事情,主要是为了节省电池。 Many services, including Position or Battery, make use of it by default. 默认情况下,许多服务(包括位置或电池)都使用它。

So far, there is no easy way to remove the listener, as there is no API to enable or disable its use. 到目前为止,还没有删除监听器的简便方法,因为没有启用或禁用监听器的API。 If you think this should be added, you can file an issue here . 如果您认为应该添加此内容,则可以在此处提出问题。

You could fork the Charm Down repository, removing the related code, and build it again, using your own snapshot, but of course this is not a good long term solution. 您可以使用自己的快照创建Charm Down存储库,删除相关代码,然后再次构建它,但是,这当然不是一个好的长期解决方案。

For now, the only way I can think of, without modifying Down, is avoiding the inclusion of the Lifecycle service implementation for iOS. 就目前而言,我能想到的唯一无需修改Down的方法就是避免包含适用于iOS的Lifecycle Service实现。

Doing so, once you open the app and instantiate the Position service, startObserver will be called and never stopped (until you close the app). 这样做后,一旦打开应用程序并实例化Position服务, startObserver就会被调用并且永远不会停止(直到您关闭应用程序)。

In your build.gradle file, instead of using the downConfig block to include the position plugin, do it in the dependencies block, and remove the traversal dependency to lifecycle-ios: 在您的build.gradle文件中,不要使用downConfig块来包括position插件,而应在dependencies块中执行此操作,然后将遍历依赖项删除到lifecycle-ios:

dependencies {
    compile 'com.gluonhq:charm:4.3.7'
    compile 'com.gluonhq:charm-down-plugin-position:3.6.0'
    iosRuntime('com.gluonhq:charm-down-plugin-position-ios:3.6.0') {
        exclude group: 'com.gluonhq', module: 'charm-down-plugin-lifecycle-ios'
    }
}

jfxmobile {
    downConfig {
        version = '3.6.0'
        plugins 'display', 'statusbar', 'storage'
    }

Now deploy it to your iOS device and check if the position service works on background mode. 现在将其部署到您的iOS设备,并检查位置服务是否在后台模式下工作。

EDIT 编辑

As pointed out, removing the lifecycle listener that stopped the observer is not enough: the location is not updated in background mode. 如所指出的,仅仅删除停止观察者的生命周期侦听器是不够的:该位置不会在后台模式下更新。

The solution to get this working requires modifying the Position service for iOS, and building a local snapshot. 解决此问题的解决方案需要修改iOS的Position服务,并构建本地快照。

These are the steps (for Mac only): 这些步骤(仅适用于Mac):

1. Clone/fork Charm Down 1.克隆/分叉魅力

Charm Down is an open source library that can be found here . Charm Down是一个开源库,可以在这里找到。

2. Edit the Position Service for iOS 2.编辑iOS的排名服务

We need to comment out or remove the Lifecycle listeners from IOSPositionService ( link ): 我们需要注释掉或从IOSPositionService链接 )中删除生命周期侦听器:

public IOSPositionService() {
    position = new ReadOnlyObjectWrapper<>();

    startObserver();
}

(though a better approach will be adding API to allow background mode, and install the listeners based on it. Also a way to stop the observer should be required) (尽管更好的方法是添加API以允许后台模式,并基于该模式安装侦听器。还需要一种停止观察器的方法)

And now we have to modify the native implementation at Position.m ( link ): 现在,我们必须在Position.mlink )上修改本机实现:

- (void)startObserver 
{
    ...
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
    {
        // try to save battery by using GPS only when app is used:
        [self.locationManager requestWhenInUseAuthorization];
    }

    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0)
    {
        // allow background mode
        self.locationManager.allowsBackgroundLocationUpdates = YES;
    }
    NSLog(@"Start updating location");
    ...
}

(again, this should be set based on a background mode API) (同样,应基于后台模式API进行设置)

3. Build and install 3.构建并安装

At the root of Charm Down, and using a Mac, run this: 在Charm Down的根部,并使用Mac,运行以下命令:

./gradlew clean install

(if the Android sdk is not installed, the android services can be commented out at settings.gradle ). (如果未安装Android sdk,则可以在settings.gradle处注释掉android服务)。

This will install a snapshot of the Charm Down services (currently 3.7.0-SNAPSHOT). 这将安装Charm Down服务的快照(当前为3.7.0-SNAPSHOT)。

4. Update the Gluon Mobile project 4.更新Gluon Mobile项目

Edit the build.gradle file, and set the mavenLocal() repository, and the snapshot version: 编辑build.gradle文件,并设置mavenLocal()存储库和快照版本:

repositories {
    mavenLocal()
    jcenter()
    maven {
        url 'http://nexus.gluonhq.com/nexus/content/repositories/releases'
    }
}

dependencies {
    compile 'com.gluonhq:charm:4.3.7'
}

jfxmobile {
    downConfig {
        version = '3.7.0-SNAPSHOT'
        plugins 'display', 'lifecycle', 'position', 'statusbar', 'storage'
    }

Save and reload the project. 保存并重新加载项目。

5. Use Position service in background mode 5.在后台模式下使用位置服务

As pointed out in the comments, when running on background mode, iOS doesn't allow making changes in the UI. 正如评论中指出的那样,在后台模式下运行时,iOS不允许在UI中进行更改。

This means that whenever a new position is retrieved from the service, we'll have to use a buffer to store it and only when user resumes the app, we'll make the necessary update to the UI with all those buffered locations. 这意味着,每当从服务中检索到新职位时,我们都必须使用缓冲区来存储它,并且只有当用户恢复应用程序时,我们才会使用所有这些缓冲区位置对UI进行必要的更新。

This is a simple use case: with the Lifecycle service we know if we are on foreground or background, and we only update the ListView control (UI) when the app is running on foreground, or it just resumed from background. 这是一个简单的用例:使用Lifecycle Service,我们知道我们是在前台还是在后台,并且仅在应用程序在前台运行时才更新ListView控件(UI),或者仅从后台恢复它。

private final BooleanProperty foreground = new SimpleBooleanProperty(true);

private final Map<String, String> map = new LinkedHashMap<>();
private final ObservableList<String> positions = FXCollections.observableArrayList();

public BasicView(String name) {
    super(name);

    Services.get(LifecycleService.class).ifPresent(l -> {
        l.addListener(LifecycleEvent.PAUSE, () -> foreground.set(false));
        l.addListener(LifecycleEvent.RESUME, () -> foreground.set(true));
    });
    ListView<String> listView = new ListView<>(positions);

    Button button = new Button("Start GPS");
    button.setGraphic(new Icon(MaterialDesignIcon.GPS_FIXED));
    button.setOnAction(e -> {
        Services.get(PositionService.class).ifPresent(service -> {
            foreground.addListener((obs, ov, nv) -> {
                if (nv) {
                    positions.addAll(map.values());
                }
            });
            service.positionProperty().addListener((obs, oldPos, newPos) -> {
                if (foreground.get()) {
                    positions.add(addPosition(newPos));
                } else {
                    map.put(LocalDateTime.now().toString(), addPosition(newPos));
                }
            });
        });
    });

    VBox controls = new VBox(15.0, button, listView);
    controls.setAlignment(Pos.CENTER);

    setCenter(controls);
}

private String addPosition(Position position) {
    return LocalDateTime.now().toString() + " :: " + 
            String.format("%.7f, %.7f", position.getLatitude(), position.getLongitude()) +
            " :: " + (foreground.get() ? "F" : "B");
}

Finally, as pointed out by the OP, make sure to add the required keys to the plist: 最后,如OP所指出的,确保将所需的键添加到plist中:

<key>NSLocationAlwaysUsageDescription</key>
<string>A good reason.</string>
<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>
<key>NSLocationWhenInUseUsageDescription</key>
<string>An even better reason.</string>

6. Deploy and run 6.部署并运行

Allow location use, start the location service, and when entering background mode, a blue status bar on the iOS device will show that the App is using location. 允许使用位置信息,启动位置服务,进入后台模式时,iOS设备上的蓝色状态栏将显示该应用程序正在使用位置信息。

Note that this could drain the battery really fast . 请注意,这可能会很快耗尽电池电量

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM