在Android 8或9上后台地理围栏无法工作

13

我试图在用户到达指定区域时向其显示推送提醒。

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

如果我的应用程序正在运行并跟踪用户的位置,则它可以完美地工作。

例如,如果我启动Google地图,则它也会跟踪我的位置。 推送将出现。

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

这是正常的吗? 如何始终使其正常工作? 如果需要前台服务跟踪您的位置,那么地理围栏有什么意义?

 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();
}
5个回答

13
我一直在使用GeoFence,经过尝试不同的解决方案后,我自己找到了答案。基本上,只有当手机中的任何应用程序正在获取位置信息一段时间时,GeoFence才会收到触发器。如果您测试谷歌提供的GeoFence样例应用,则可以看到只有在打开谷歌地图应用程序时,该应用程序才能正常工作,这是因为Google Maps是设备中唯一主动请求位置的应用程序。
您可以从下面的链接中克隆GeoFence示例和LocationUpdateForGroundService示例,以验证这一点: https://github.com/googlesamples/android-play-location 同时运行GeoFence和LocationUpdateForGroundService,通过更改模拟器中的纬度和经度,您将注意到现在不需要再打开Google地图,因为现在有另一个应用程序正在请求位置。
因此,在GeoFence应用程序中创建前台服务,并使用Fuse Location Client请求位置更新一段时间。

你能提供一些你尝试证明的代码吗? - Sumit Shukla
我为证明写的第二段,请按照这个。 - Mudassir Zulfiqar
是的,当谷歌地图应用程序打开时,它确实可以正常工作。否则,它就无法工作。 - Green Y.

8

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

我的GeofenceManager:

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分钟的时间? - LifeQuestioner
是的,这取决于您的业务。如果您正在使用服务积极跟踪位置,您可能会有更快的反应。 - Ben-J
1
@Ben-J 为什么你把服务替换成了广播接收器?有什么具体的原因吗? - Ambar Jain
1
由于Oreo上的新限制,我们无法启动后台服务,因此我们需要使用广播接收器。@AmbarJain - Brandon
我宁愿计算两个位置之间的位移,这太繁琐了。 - Pemba Tamang
显示剩余4条评论

3
我发现当我打开谷歌地图时,我的geoFenceBroadCastReceiver在模拟器中开始正常工作。我无法弄清楚问题出在哪里,结果很明显我缺少了一个重要的组成部分。这种行为也在这个问题中描述(恰如其分地命名):Geofence Broadcast Receiver not triggered but when I open the google map, it works,并且已经作为Android位置示例项目中的问题多次提交:Issue 239Issue 247Issue 266
我没有在这里看到这个问题的实际答案,所以为了后代,我提供一些建议。问题264 似乎 指向解决方案,即 使用JobIntentService
但是,
GeoFence示例LocationUpdateIntentService中有一个代码注释
注意:在“O”设备上运行的应用程序(无论目标SdkVersion如何)可能比在前台时指定的间隔频率更少地接收更新。

这似乎证实了@Vinayak在此处的回答中所指出的内容。

O.S不允许应用程序从后台获取位置更新。在前台服务上实现地理围栏位置代码即可按预期运行。

地理围栏广播接收器无法及时获取位置更新(甚至在IntentService中也是如此)。您需要在请求精确定位访问权限的前台服务中运行位置客户端,以获得更准确/及时的地理围栏触发。因此,看起来正确的答案是在前台服务中运行地理围栏。

如果您选择前台服务路线,还需要创建一个通知渠道,否则,您将遇到像这样的问题

另请参见:

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)


这个主题的总结令人印象深刻!我真的不得不写下这个评论来感谢你。这正是我在这个主题上需要的。 - Lance
另外感谢您的总结 <3。 - ketimaBU

1

我采用了@Mudassir Zulfiqar的前台服务实现方案。除非另一个应用程序不使用您的位置或您自己触发位置,否则后台中地理围栏将无法正常工作。

以下是我的前台服务实现:

class GeofenceForegroundService: Service() {

private lateinit var geofenceHelper: GeofenceHelper
private lateinit var geofencingClient: GeofencingClient
private lateinit var pendingIntent: PendingIntent
private lateinit var locationClient: FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
private val geofenceId = "Some_Geofence_Id"
private val geofenceRadius = 200.0

private val TAG = "GeofenceForegroundServi"

override fun onBind(p0: Intent?): IBinder? {
    return null
}

@SuppressLint("MissingPermission")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    geofenceHelper = GeofenceHelper(this)

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel =
            NotificationChannel("Geofence", "Geofence Loc", NotificationManager.IMPORTANCE_NONE)
        val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        manager.createNotificationChannel(channel)
    }

    val notification: Notification = NotificationCompat.Builder(this, "Geofence")
        .setContentTitle("Geofence Active")
        .setContentText("Location Updating...")
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentIntent(geofenceHelper.pendingIntent)
        .build()

    val latLng = intent?.getParcelableExtra<LatLng>("LATLNG")
    geofencingClient = LocationServices.getGeofencingClient(this)
    locationClient = LocationServices.getFusedLocationProviderClient(this)
    val geofence = geofenceHelper.getGeofence(geofenceId, latLng!!, geofenceRadius.toFloat())
    val geofencingRequest = geofenceHelper.getGeofencingRequest(geofence)
    pendingIntent = geofenceHelper.pendingIntent
    val locationRequest = LocationRequest.create().apply {
        interval = 10000
        fastestInterval = 5000
        priority = LocationRequest.PRIORITY_HIGH_ACCURACY

    }
    locationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {
            super.onLocationResult(locationResult)
            Log.d(TAG, "${locationResult.lastLocation.latitude} ${locationResult.lastLocation.longitude}")
        }
    }
    Looper.myLooper()?.let {
        locationClient.requestLocationUpdates(locationRequest, locationCallback,
            it
        )
    }

    geofencingClient.addGeofences(geofencingRequest, pendingIntent).run {
        addOnSuccessListener {
            Log.d(TAG, "onSuccess: Geofence Added...")

        }
        addOnFailureListener {
            Log.d(TAG, "onFailure ${geofenceHelper.getErrorString(it)}")
        }
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
    } else {
        startForeground(1, notification)
    }

    return START_STICKY
}

override fun onDestroy() {
    Log.i(TAG, "onDestroy: RUN")
    geofencingClient.removeGeofences(pendingIntent)
    locationClient.removeLocationUpdates(locationCallback)
    super.onDestroy()
}

override fun onTaskRemoved(rootIntent: Intent?) {
    Log.i(TAG, "onTaskRemoved: RUN")
    super.onTaskRemoved(rootIntent)
    stopSelf()
}
}

如果您对上述代码有任何疑问,请在评论中分享。

1

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


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