Android 8 或 9 上的后台地理围栏不起作用

Posted

技术标签:

【中文标题】Android 8 或 9 上的后台地理围栏不起作用【英文标题】:Geofencing in background on Android 8 or 9 does not work 【发布时间】: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();

【问题讨论】:

【参考方案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 239、Issue 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)

【讨论】:

以上是关于Android 8 或 9 上的后台地理围栏不起作用的主要内容,如果未能解决你的问题,请参考以下文章

在后台服务中创建地理围栏

应用程序在后台 ios 中时地理围栏不起作用

在后台某个时间后未触发Android地理围栏?

Android 地理围栏背景限制

在后台广播的工作地理围栏示例

如何在奥利奥后台监控地理围栏?