Geofence triggering issues in Android

て烟熏妆下的殇ゞ 提交于 2019-12-08 03:27:51

问题


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:

  1. On some device, same notification is triggering again and again when user is moving withing same geofence.
  2. On some device, some notifications are triggering some are not.
  3. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!