问题
I'm using android device's native Geofence service. Here are implementation details:
Tracked transition type: ENTER
Range: 500 meters (1640 feet)
Notification responsive time: 500ms
Added Geofence count: 15-20
Initial trigger (setInitialTrigger()): Not set
Location accuracy on device: High
Location permissions: FINE LOCATION and COARSE LOCATION
Location service on device: ON
Location permission to app: Yes
Android Oreo support: Yes (Used Broadcast receiver and JobIntentService)
Issues:
- On some device, same notification is triggering again and again when user is moving withing same geofence.
- On some device, some notifications are triggering some are not.
- On some device, no geofence in triggering at all.
Shall I move to third-party geofence services? If yes, could you please suggest any good service at this?
Creating goefence:
private const val NOTIFICATION_RESPONSIVENESS_TIME = 500
private const val GEOFENCE_RADIUS_IN_METERS = 500f
private const val GEOFENCE_PENDING_INTENT_REQUEST_CODE = 1
private fun createGeofences(context: Context, communityList: List<Community>) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return
}
//Adding geofence for all received communities
val geofenceList = communityList
.asSequence()
.filter { community -> isValidCommunityForGeofence(community) }
.map { community -> toGeofence(community) }
.toList()
val geofencingRequest = GeofencingRequest.Builder()
.addGeofences(geofenceList)
.build()
val pendingIntent = getGeofencePendingIntent(context)
val geofencingClient: GeofencingClient = LocationServices.getGeofencingClient(context)
geofencingClient.addGeofences(geofencingRequest, pendingIntent)
.addOnCompleteListener(GeofenceAddRemoveListener(true))
}
private fun toGeofence(community: Community): Geofence {
return Geofence.Builder()
.setRequestId(community.bdxCommunityId.toString())//unique ID for geofence
.setCircularRegion(community.latitude, community.longitude, GEOFENCE_RADIUS_IN_METERS)
.setNotificationResponsiveness(NOTIFICATION_RESPONSIVENESS_TIME)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setLoiteringDelay(0)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build()
}
private fun getGeofencePendingIntent(context: Context): PendingIntent {
val intent = Intent(context, GeofenceBroadcastReceiver::class.java)
return PendingIntent.getBroadcast(context, GEOFENCE_PENDING_INTENT_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun isValidCommunityForGeofence(community: Community): Boolean {
return community.latitude != null && community.longitude != null && community.latitude != 0.0
&& community.longitude != 0.0 && !TextUtils.isEmpty(community.name)
}
Manifest file:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.location.network" />
<uses-feature android:name="android.hardware.location.gps" />
<receiver
android:name=".misc.geofence.GeofenceBroadcastReceiver"
android:enabled="true"
android:exported="true" />
<service
android:name=".misc.geofence.GeofenceTransitionsJobIntentService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
Broadcast receiver:
class GeofenceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Enqueues a JobIntentService passing the context and intent as parameters
GeofenceTransitionsJobIntentService.enqueueWork(context, intent)
}
}
JobIntentService:
class GeofenceTransitionsJobIntentService : JobIntentService() {
companion object {
fun enqueueWork(context: Context, intent: Intent) {
JobIntentService.enqueueWork(context, GeofenceTransitionsJobIntentService::class.java, JobServiceID.GEOFENCE_JOB_ID, intent)
}
}
/**
* Handles incoming intents.
*
* @param intent sent by Location Services. This Intent is provided to Location Services (inside a PendingIntent)
* when @GeofenceInteractor#refreshGeofences() is called.
*/
override fun onHandleWork(intent: Intent) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceErrorMessages.getErrorString(geofencingEvent.errorCode)
Logger.e(this, errorMessage)
return
}
val geofenceTransition = geofencingEvent.geofenceTransition
val userCommunityList = GeofenceInteractor.getUserCommunityList(this)
// Get the geofences that were triggered. A single event can trigger multiple geofences.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
val triggeringGeofences = geofencingEvent.triggeringGeofences
//Showing notification for each geofence which triggered ENTER transition.
for (geofence in triggeringGeofences) {
val community = userCommunityList.asSequence().filter { community -> community.bdxCommunityId == geofence.requestId.toInt() }.firstOrNull()
if (community != null) {
val transitionMessage = String.format(resources.getString(R.string.community_geofence_transition_entered), community.name)
sendGeofenceNotification(transitionMessage, community)
}
Logger.d(this, "Geofene triggered. Transition: " + geofenceTransition + " Community:" + community?.name)
}
} else {
Logger.e(this, getString(R.string.geofence_transition_invalid_type, geofenceTransition))
}
}
private fun sendGeofenceNotification(contentText: String, community: Community) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?
?: return
val notificationBuilder = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
NotificationCompat.Builder(this)
} else {
val notificationChannel = NotificationUtil.getOrCreateGeofenceNotificationChannel(this, notificationManager)!!
NotificationCompat.Builder(this, notificationChannel.id)
}
val nextNotificationId = NotificationUtil.getNextNotificationId(this)
val viewCommunityPendingIntent = getViewCommunityPendingIntent(nextNotificationId, community)
val mapNavigationPendingIntent = getGeofenceMapNavigationPendingIntent(nextNotificationId, community)
notificationBuilder.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))
.setContentTitle(community.name)
.setContentText(contentText)
.setContentIntent(viewCommunityPendingIntent)
.setAutoCancel(true)
.setGroup(NotificationUtil.GEOFENCE_GROUP)
.addAction(0, getString(R.string.navigate_to_community), mapNavigationPendingIntent)
.addAction(0, getString(R.string.view), viewCommunityPendingIntent)
notificationManager.notify(nextNotificationId, notificationBuilder.build())
}
private fun getViewCommunityPendingIntent(notificationId: Int, community: Community): PendingIntent? {
val notificationBundle = Bundle()
notificationBundle.putParcelable(Constants.COMMUNITY, community)
notificationBundle.putInt(Constants.NOTIFICATION_ID, notificationId)
val notificationIntent = Intent(applicationContext, SplashActivity::class.java)
notificationIntent.putExtras(notificationBundle)
val stackBuilder = TaskStackBuilder.create(this)
stackBuilder.addParentStack(SplashActivity::class.java)
stackBuilder.addNextIntent(notificationIntent)
return stackBuilder.getPendingIntent(notificationId, PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun getGeofenceMapNavigationPendingIntent(notificationId: Int, community: Community): PendingIntent? {
val notificationBundle = Bundle()
notificationBundle.putParcelable(Constants.COMMUNITY, community)
notificationBundle.putInt(Constants.NOTIFICATION_ID, notificationId)
val geofenceMapNavigationIntent = Intent(this, GeofenceMapNavigationActivity::class.java)
geofenceMapNavigationIntent.putExtras(notificationBundle)
val mapNavigationStackBuilder = TaskStackBuilder.create(this)
mapNavigationStackBuilder.addParentStack(SplashActivity::class.java)
mapNavigationStackBuilder.addNextIntent(geofenceMapNavigationIntent)
return mapNavigationStackBuilder.getPendingIntent(notificationId, PendingIntent.FLAG_UPDATE_CURRENT)
}
}
回答1:
Let me show you what i have done for a similar task. Below code has been used to achieve geofencing.
class LocationService : Service(), GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks {
var mLocationManager: LocationManager? = null
var googleApiClient: GoogleApiClient? = null
var pendingIntent: PendingIntent? = null
var geofencingRequest: GeofencingRequest? = null
var mGeofenceList: ArrayList<Geofence>? = null
private inner class LocationListener(provider: String) : android.location.LocationListener {
private var mLastLocation: Location = Location(provider)
override fun onLocationChanged(location: Location) {
mLastLocation.set(location)
}
override fun onProviderDisabled(provider: String) {}
override fun onProviderEnabled(provider: String) {}
override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
}
internal var mLocationListeners = arrayOf<android.location.LocationListener>(LocationListener(LocationManager.GPS_PROVIDER), LocationListener(LocationManager.NETWORK_PROVIDER))
override fun onBind(arg0: Intent): IBinder? {
return null
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
mGeofenceList = ArrayList()
populateGeofenceList()
super.onStartCommand(intent, flags, startId)
return Service.START_STICKY
}
override fun onCreate() {
initializeLocationManager()
try {
mLocationManager!!.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, Constant.LOCATION_INTERVAL.toLong(), Constant.LOCATION_DISTANCE, mLocationListeners[1])
} catch (ex: java.lang.SecurityException) {
ex.printStackTrace()
} catch (ex: IllegalArgumentException) {
ex.printStackTrace()
}
try {
mLocationManager!!.requestLocationUpdates(
LocationManager.GPS_PROVIDER, Constant.LOCATION_INTERVAL.toLong(), Constant.LOCATION_DISTANCE,
mLocationListeners[0])
} catch (ex: java.lang.SecurityException) {
ex.printStackTrace()
} catch (ex: IllegalArgumentException) {
ex.printStackTrace()
}
}
override fun onDestroy() {
super.onDestroy()
if (mLocationManager != null) {
for (i in mLocationListeners.indices) {
try {
mLocationManager!!.removeUpdates(mLocationListeners[i])
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
}
private fun initializeLocationManager() {
googleApiClient = GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this).build()
googleApiClient!!.connect()
if (mLocationManager == null) {
mLocationManager = applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager?
}
}
private fun startLocationMonitor() {
val locationRequest = LocationRequest.create()
.setInterval(Constant.LOCATION_INTERVAL.toLong())
.setFastestInterval(Constant.LOCATION_INTERVAL.toLong())
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
try {
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, object : com.google.android.gms.location.LocationListener {
override fun onLocationChanged(location: Location) {
DashBoardActivity.latitude = location.latitude
DashBoardActivity.longitude = location.longitude
if (BuildConfig.DEBUG) {
Log.e("LocationChanged:", location.latitude.toString() + " ," + location.longitude)
}
}
})
} catch (e: SecurityException) {
e.printStackTrace()
}
}
private fun startGeofencing() {
pendingIntent = getGeofencePendingIntent()
geofencingRequest = GeofencingRequest.Builder()
.setInitialTrigger(Geofence.GEOFENCE_TRANSITION_ENTER)
.addGeofences(mGeofenceList)
.build()
if (googleApiClient?.isConnected!!) {
try {
LocationServices.GeofencingApi.addGeofences(googleApiClient, geofencingRequest, pendingIntent).setResultCallback(object : ResultCallback<Status> {
override fun onResult(status: Status) {
}
})
} catch (e: SecurityException) {
e.printStackTrace()
}
}
}
private fun populateGeofenceList() {
for (entry in Constant.AREA_LANDMARKS.entries) { // Replace with your Location List
mGeofenceList?.add(Geofence.Builder()
.setRequestId(entry.key)
.setCircularRegion(entry.value.latitude, entry.value.longitude, Constant.GEOFENCE_RADIUS_IN_METERS)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setNotificationResponsiveness(1000)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
.build())
}
}
private fun getGeofencePendingIntent(): PendingIntent? {
if (pendingIntent != null) {
return pendingIntent
}
val intent = Intent(this, GeofenceRegistrationService::class.java)
pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
return pendingIntent
}
override fun onConnected(bundle: Bundle?) {
startGeofencing()
startLocationMonitor()
}
override fun onConnectionSuspended(i: Int) {}
override fun onConnectionFailed(connectionResult: ConnectionResult) {}
}
For getting the geofence events, i have used below code:
class GeofenceRegistrationService : IntentService("GeoIntentService") {
val TAG = "GeoIntentService"
var mGeofencList: ArrayList<Geofence>? = null
override fun onHandleIntent(intent: Intent?) {
mGeofencList = ArrayList()
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Error" + geofencingEvent.errorCode)
}
} else {
try {
val transaction = geofencingEvent.geofenceTransition
val geofences = geofencingEvent.triggeringGeofences
for (i in 0 until geofences.size) {
mGeofencList?.add(geofences[i])
}
if (transaction == Geofence.GEOFENCE_TRANSITION_ENTER) {
sendBroadCast(true)
if (BuildConfig.DEBUG) {
Log.d(TAG, "You are inside Geofenced area")
}
}
if (transaction == Geofence.GEOFENCE_TRANSITION_EXIT) {
sendBroadCast(false)
if (BuildConfig.DEBUG) {
Log.d(TAG, "You are outside Geofenced area")
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun sendBroadCast(isInside: Boolean) {
val broadCastIntent = Intent(Constant.SRI_GEO_FENCE)
broadCastIntent.putExtra(Constant.KEY_GEOFENCE_STATE, isInside)
broadCastIntent.putExtra(Constant.KEY_GEOFENCE_LIST, mGeofencList)
LocalBroadcastManager.getInstance(this).sendBroadcast(broadCastIntent)
}
}
Then you just have to start the LocationService as follows:
val locationIntent = Intent(activity, LocationService::class.java)
activity.startService(locationIntent)
It has been tested and working perfectly. If there is any question, please approach me. Thanks
来源:https://stackoverflow.com/questions/53405841/geofence-triggering-issues-in-android