【问题标题】:Geofencing in background on Android 8 or 9 does not workAndroid 8 或 9 上的后台地理围栏不起作用
【发布时间】:2019-06-18 06:34:42
【问题描述】:

当用户到达定义的区域时,我尝试向用户显示推送警报。

所以我从 https://developer.android.com/training/location/geofencing 编写了我的应用程序

如果我的应用程序正在运行一个跟踪用户位置的服务,它会完美运行。

例如,如果我启动谷歌地图,它也可以工作,它也会跟踪我的位置。将出现推送。

但如果我关闭我的应用程序,推送将不会出现,因此如果没有应用程序跟踪我的位置,则不会检测到地理围栏。

正常吗? 如何让它始终工作? 如果您需要跟踪您所在位置的前台服务,那么地理围栏的意义何在?

 public void createGeofenceAlerts(LatLng latLng, int radius) {
    final Geofence enter = buildGeofence(ID_ENTER, latLng, radius, Geofence.GEOFENCE_TRANSITION_ENTER);
    final Geofence exit = buildGeofence(ID_EXIT, latLng, radius, Geofence.GEOFENCE_TRANSITION_EXIT);
    final Geofence dwell = buildGeofence(ID_DWELL, latLng, radius, Geofence.GEOFENCE_TRANSITION_DWELL);

    GeofencingRequest request = new GeofencingRequest.Builder()
            .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            .addGeofence(enter)
            .addGeofence(exit)
            .addGeofence(dwell)
            .build();

    fencingClient.addGeofences(request, getGeofencePendingIntent()).addOnSuccessListener(new OnSuccessListener<Void>() {
        @Override
        public void onSuccess(Void aVoid) {
            Timber.i("succes");
            Toast.makeText(mContext, "Geofence added", Toast.LENGTH_LONG).show();
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            Timber.e(e,"failure");
            Toast.makeText(mContext, "Geofence ERROR", Toast.LENGTH_LONG).show();
        }
    });
}

private PendingIntent getGeofencePendingIntent() {
    Intent intent = new Intent(mContext, GeofenceTransitionsIntentService.class);
    PendingIntent pending = PendingIntent.getService(
            mContext,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT);
    return pending;
}

private Geofence buildGeofence(String id, LatLng center, int radius, int transitionType) {
    Geofence.Builder builder = new Geofence.Builder()
            // 1
            .setRequestId(id)
            // 2
            .setCircularRegion(
                    center.getLatitude(),
                    center.getLongitude(),
                    radius)
            // 3
            .setTransitionTypes(transitionType)
            // 4
            .setExpirationDuration(Geofence.NEVER_EXPIRE);
    if (transitionType == Geofence.GEOFENCE_TRANSITION_DWELL) {
        builder.setLoiteringDelay(LOITERING_DELAY);
    }

    return builder.build();
}

【问题讨论】:

    标签: android background location maps geofencing


    【解决方案1】:

    我已经使用 GeoFence 这么长时间了,我有同样的问题,在尝试不同的解决方案后我自己得到了答案,所以基本上,如果手机中的任何应用程序正在获取位置,GeoFence 才会触发一些 x 持续时间。如果您测试 google 提供的 GeoFence 示例应用程序,您会发现该应用程序仅在您打开 Google 地图应用程序时才有效,这是因为 Google 地图是设备中唯一被动请求位置的应用程序。

    为了证明,您可以从以下链接克隆 GeoFence 示例和 LocationUpdateForGroundService 示例 https://github.com/googlesamples/android-play-location 同时运行它们的 GeoFence 和 LocationUpdateForGroundService,您会注意到通过更改模拟器中的纬度和经度,您现在不需要再打开 Google 地图了,因为现在有另一个应用程序正在请求位置。

    因此,请在 GeoFence 应用程序中创建前台服务,并使用 Fuse Location Client 请求位置更新一段时间。

    【讨论】:

    • 你能提供一些你想要证明的代码吗!
    • 我写的第二段是为了证明。请遵循此。
    • 是的,当谷歌地图应用程序打开时它运行良好。否则,它不起作用。
    【解决方案2】:

    我想我找到了一个解决方案,在 Android 9 上进行了测试。我使用了 Google 文档 https://developer.android.com/training/location/geofencing,但我用广播接收器替换了该服务。

    我的地理围栏管理器:

    private val braodcastPendingIntent: PendingIntent
        get() {
            val intent = Intent(mContext, GeofenceTransitionsBroadcastReceiver::class.java)
            val pending = PendingIntent.getBroadcast(
                    mContext.applicationContext,
                    0,
                    intent,
                    PendingIntent.FLAG_UPDATE_CURRENT)
            return pending
        }
    
     fun createGeofenceAlerts(latLng: LatLng, radiusMeter: Int, isBroadcast: Boolean) {
        val enter = buildGeofence(ID_ENTER, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_ENTER)
        val exit = buildGeofence(ID_EXIT, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_EXIT)
        val dwell = buildGeofence(ID_DWELL, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_DWELL)
    
        val request = GeofencingRequest.Builder()
                .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
                .addGeofence(enter)
                .addGeofence(exit)
                .addGeofence(dwell)
                .build()
    
        val pending = if (isBroadcast) {
            braodcastPendingIntent
        } else {
            servicePendingIntent
        }
        fencingClient.addGeofences(request, pending).addOnSuccessListener {
            Timber.i("succes")
            Toast.makeText(mContext, "Geofence added", Toast.LENGTH_LONG).show()
        }.addOnFailureListener { e ->
            Timber.e(e, "failure")
            Toast.makeText(mContext, "Geofence ERROR", Toast.LENGTH_LONG).show()
        }
    }
    
    private fun buildGeofence(id: String, center: LatLng, radius: Int, transitionType: Int): Geofence {
        val builder = Geofence.Builder()
                // 1
                .setRequestId(id)
                // 2
                .setCircularRegion(
                        center.latitude,
                        center.longitude,
                        radius.toFloat())
                // 3
                .setTransitionTypes(transitionType)
                // 4
                .setExpirationDuration(Geofence.NEVER_EXPIRE)
        if (transitionType == Geofence.GEOFENCE_TRANSITION_DWELL) {
            builder.setLoiteringDelay(LOITERING_DELAY)
        }
    
        return builder.build()
    }
    

    我的 BroadcastReceiver,显然你需要在清单中声明它:

    class GeofenceTransitionsBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Timber.i("received")
        val geofencingEvent = GeofencingEvent.fromIntent(intent)
        if (geofencingEvent.hasError()) {
            Timber.e("Geofence error")
            return
        }
    
        // Get the transition type.
        val geofenceTransition = geofencingEvent.geofenceTransition
    
        // Test that the reported transition was of interest.
        if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT
                || geofenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL) {
    
            // Get the geofences that were triggered. A single event can trigger
            // multiple geofences.
            val triggeringGeofences = geofencingEvent.triggeringGeofences
    
            // Get the transition details as a String.
            val geofenceTransitionDetails = GeofenceManager.getGeofenceTransitionDetails(
                    geofenceTransition,
                    triggeringGeofences, true
            )
    
            // Send notification and log the transition details.
            GeofenceManager.sendNotification(context, geofenceTransition, geofenceTransitionDetails)
            Timber.i(geofenceTransitionDetails)
        } else {
            // Log the error.
            Timber.e("Unknown geo event : %d", geofenceTransition)
        }
    }
    

    重要的是要知道,在 Android 8 和 9 上,地理围栏有 2 分钟的延迟。

    【讨论】:

    • 有没有办法减少这 2 分钟?
    • 是的,这取决于您的业务,如果您通过服务主动跟踪职位,您可能会有更快的反应。
    • @Ben-J 为什么用广播接收器替换服务?这背后有什么具体原因吗?
    • @AmbarJain 由于奥利奥的新限制,我们无法启动后台服务,所以我们需要使用广播接收器
    • @Ben-J 你说你用广播接收器替换了服务,但后来在代码中你有:` val pending = if (isBroadcast) { braodcastPendingIntent } else { servicePendingIntent } ` 你能发布servicePendingIntent 代码?另外,还有一个isBroadcast 布尔值,所以,你能澄清谁/如何调用createGeofenceAlerts吗?
    【解决方案3】:

    操作系统将不允许应用从后台获取位置更新。在前台服务上实施地理围栏位置代码。它将按预期运行

    【讨论】:

      【解决方案4】:

      当我打开谷歌地图时,我发现我的 geoFenceBroadCastReceiver 开始在模拟器中正常工作。我根本无法弄清楚问题出在哪里,事实证明,我显然错过了一块拼图。此问题(标题恰当)中也描述了该行为:Geofence Broadcast Receiver not triggered but when I open the google map, it works,并在 Android 位置示例项目Issue 239Issue 247Issue 266 中多次作为问题提交。

      我没有看到这个问题的实际答案作为答案发布在这里,所以为了后代我会提供一些建议。 Issue 264 似乎指向解决方案,即using a JobIntentService

      但是

      GeoFence 示例 LocationUpdateIntentService 中有一个 code comment

      注意:在“O”设备上运行的应用程序(无论 targetSdkVersion 是什么)可能 接收更新的频率低于 {@link com.google.android.gms.location.LocationRequest} 当应用不再在前台时。

      这似乎证实了@Vinayak 在他的answer here 中指出的内容

      操作系统将不允许应用从后台获取位置更新。在前台服务上实施地理围栏位置代码。它将按预期运行

      不允许地理围栏广播接收器及时获取位置更新(显然即使在 IntentService 中也是如此)。您必须在请求精细位置访问的前台服务中运行位置客户端,以获得更准确/及时的地理围栏触发。所以看起来正确的答案是在foreGroundService 中运行geoFence。

      如果你走foreGroundService路线,你还需要创建一个通知通道,否则你会遇到问题like this one

      另见:

      https://developer.android.com/guide/components/foreground-services

      https://developer.android.com/training/location/change-location-settings#location-request

      https://developer.android.com/training/location/request-updates

      https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient.html#requestLocationUpdates(com.google.android.gms.location.LocationRequest,%20com.google.android.gms.location.LocationCallback)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-18
        相关资源
        最近更新 更多