【问题标题】:Using 'Background location services' on iOS with gluon mobile将 iOS 上的“后台定位服务”与 gluon mobile 一起使用
【发布时间】:2017-09-16 20:47:11
【问题描述】:

我刚开始使用 gluon mobile,并且正在开发一个小型 iOS 应用程序。我设法使用 PositionService 来更新用户在 UI 中标签上的位置。现在,即使应用程序处于后台模式,我也想获得位置更新。由于苹果开发人员文档,这应该通过将以下密钥添加到应用程序列表中来工作

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

在 iPhone 上部署应用程序时,只要应用程序处于活动状态,我就可以看到更新。进入主屏幕时,更新停止,并且在终端(gradle -> launchIOSDevice)中显示“停止更新位置”消息。知道为什么我在后台模式下没有获得位置更新吗?

这里是相关代码:

    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();
        });
    });

这里是相关的 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>

【问题讨论】:

    标签: gluon gluon-mobile


    【解决方案1】:

    应用进入后台时Position服务不工作的原因可以找到here

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

    生命周期服务旨在防止在应用处于后台时执行不必要的操作,主要是为了节省电量。许多服务,包括位置或电池,默认使用它。

    到目前为止,还没有简单的方法可以删除侦听器,因为没有 API 可以启用或禁用它的使用。如果您认为应该添加此内容,您可以提交问题here

    您可以分叉 Charm Down 存储库,删除相关代码,然后使用您自己的快照重新构建它,但这当然不是一个好的长期解决方案。

    目前,我能想到的唯一方法是在不修改 Down 的情况下避免包含 iOS 的 Lifecycle 服务实现。

    这样做后,一旦您打开应用并实例化 Position 服务,startObserver 将被调用并且永远不会停止(直到您关闭应用)。

    在您的build.gradle 文件中,不要使用downConfig 块来包含position 插件,而是在dependencies 块中执行此操作,并删除对生命周期-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'
        }
    

    现在将其部署到您的 iOS 设备并检查定位服务是否在后台模式下工作。

    编辑

    正如所指出的,删除停止观察者的生命周期侦听器是不够的:位置不会在后台模式下更新。

    实现此功能的解决方案需要修改 iOS 的 Position 服务,并构建本地快照。

    这些是步骤(仅适用于 Mac):

    1.克隆/分叉 Charm Down

    Charm Down 是一个开源库,可以在 here 找到。

    2。编辑 iOS 的位置服务

    我们需要从IOSPositionService (link) 中注释掉或移除 Lifecycle 监听器:

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

    (虽然更好的方法是添加 API 以允许后台模式,并基于它安装侦听器。还需要一种停止观察者的方法)

    现在我们必须修改 Position.m (link) 的原生实现:

    - (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");
        ...
    }
    

    (同样,这应该基于后台模式 API 进行设置)

    3.构建和安装

    在 Charm Down 的根目录下,使用 Mac,运行:

    ./gradlew clean install
    

    (如果没有安装Android sdk,可以在settings.gradle注释掉android服务)。

    这将安装 Charm Down 服务的快照(当前为 3.7.0-SNAPSHOT)。

    4.更新 Gluon Mobile 项目

    编辑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'
        }
    

    保存并重新加载项目。

    5.在后台模式下使用定位服务

    正如 cmets 中所指出的,在后台模式下运行时,iOS 不允许在 UI 中进行更改。

    这意味着每当从服务中检索到新位置时,我们都必须使用缓冲区来存储它,并且只有当用户恢复应用时,我们才会使用所有这些缓冲位置对 UI 进行必要的更新.

    这是一个简单的用例:通过 Lifecycle 服务,我们知道我们是在前台还是后台,并且我们仅在应用程序在前台运行或刚刚从后台恢复时更新 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");
    }
    

    最后,正如 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.部署和运行

    允许定位使用,启动定位服务,进入后台模式时,iOS设备上的蓝色状态栏会显示App正在使用定位。

    请注意,这可能会很快耗尽电池

    【讨论】:

    • 感谢您的回复。我尝试了您的第二个解决方案,实际上,我不再在命令行中收到“停止更新位置”。不幸的是,我也没有在后台收到位置更新。我手动激活了应用程序的“始终”选项(设置->隐私->定位服务->始终)。您还有什么建议吗?
    • 我刚刚检查了服务,确实,本机实现中缺少一些东西。从 iOS 9.0 开始,它需要在后台模式下工作:self.locationManager.allowsBackgroundLocationUpdates = YES;。如果你可以克隆 Charm Down,将此行添加到startObserver(),保存,运行./gradlew clean install,你将能够使用 3.7.0-SNAPSHOT 版本(将mavenLocal() 添加到存储库)。
    • 好的,我尝试构建克隆的 Charm Down,但收到未定义 ANDROID_HOME 的错误。 * What went wrong: A problem occurred evaluating root project 'Charm Down'. &gt; Could not get unknown property 'ANDROID_HOME' for project ':core/android' of type org.gradle.api.Project.
    • 如果您没有安装 Android Sdk,您可能会从根目录下的 settings.gradle 文件中删除或注释掉所有 android 项目,至少可以快速构建。
    • 最后我设法构建了 iOS 的东西(必须添加 jni.h 的位置),我更进一步。即使应用程序处于非活动状态,似乎也会收到有关位置更新的通知,但我收到错误消息:2017-09-19 00:14:07.133589+0200 Playground[2950:1170133] GLDRendererMetal command buffer completion error: Error Domain=MTLCommandBufferErrorDomain Code=7 "Insufficient Permission (to submit GPU work from background) (IOAF code 6)" UserInfo={NSLocalizedDescription=Insufficient Permission (to submit GPU work from background) (IOAF code 6)}
    猜你喜欢
    • 1970-01-01
    • 2013-09-27
    • 2014-08-10
    • 1970-01-01
    • 2014-03-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多