Android中的地理围栏触发问题

时间:2018-11-21 05:38:43

标签: android android-geofence

我正在使用android设备的本机Geofence服务。以下是实施细节:

跟踪的过渡类型:ENTER

范围:500米(1640英尺)

通知响应时间:500ms

添加的地理围栏计数:15-20

初始触发器(setInitialTrigger()):未设置

设备上的位置精度:高

位置权限:精细定位和粗略定位

设备上的位置服务:开启

应用的位置权限:是

Android Oreo支持:是(使用广播接收器和JobIntentService)

问题:

  1. 在某些设备上,相同的通知会在以下情况下一次又一次触发 用户使用相同的地理围栏移动。
  2. 在某些设备上,某些通知正在触发,而某些则没有。
  3. 在某些设备上,根本没有地理围栏触发。

我应该使用第三方地理围栏服务吗?如果是,请您为此提供任何好的服务?

创建目标:

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)
}

清单文件:

    <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" />

广播接收器:

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 个答案:

答案 0 :(得分:0)

让我告诉您我为类似任务所做的工作。下面的代码已用于实现地理围栏。

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) {}

}

为了获取地理围栏事件,我使用了以下代码:

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)
}

}

然后,您只需要按以下方式启动LocationService:

 val locationIntent = Intent(activity, LocationService::class.java)
                    activity.startService(locationIntent)

它已经过测试,可以正常工作。如有任何疑问,请与我联系。 谢谢