根据Geofencing文档,我定义了一个BroadcastReceiver以接收用户与地理围栏交互时的ENTRY和EXIT更新。但我的问题是,该应用程序将在路上使用,因此当用户驾车进入地理围栏时,我会收到通知,当他退出时也会发生相同的情况。然而,当接收到退出事件时,我需要从客户端和Google Maps地图中删除触发的地理围栏。这两者都存在于MapsActivity中(这是我基于文档设置接收器和事件通知过程的地方),因此我想从接收器中调用我的活动的removeGeofences(…)方法。 我查看了大量关于此事的帖子,但似乎没有涉及Geofencing用例的。我尝试通过代码动态声明接收器,而不是通过清单静态声明,但在这种情况下,我需要意图过滤器,而对于Geofencing,我找不到它们。有什么想法如何实现这一点?
广播接收器:
广播接收器:
class GeofenceReceiver : BroadcastReceiver() {
private val TAG = GeofenceReceiver::class.java.simpleName
override fun onReceive(context: Context?, intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceStatusCodes
.getStatusCodeString(geofencingEvent.errorCode)
Log.e(TAG, "error: $errorMessage")
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
) {
// Get the geofences that were triggered. A single event can trigger
// multiple geofences.
val triggeringGeofences = geofencingEvent.triggeringGeofences
val ids = arrayListOf<String>()
for (geofence in triggeringGeofences) {
Log.d(TAG, "Geofence ${geofence.requestId} triggered!")
ids.add(geofence.requestId)
}
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER)
Log.d(TAG, "User entered geofence!")
else {
Log.d(TAG, "User exited geofence!")
//activity.removeGeofences(ids)
}
} else {
// Log the error.
Log.e(TAG, "Invalid transition")
}
}
}
地图活动:
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private var mMap: GoogleMap? = null
private var geofenceClient: GeofencingClient? = null
private var geofenceList: ArrayList<Geofence> = arrayListOf()
private var geofenceMapMarks: MutableMap<String, Pair<Marker, Circle>> = mutableMapOf()
private var currLocationMarker: Marker? = null
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(this, GeofenceReceiver::class.java)
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences()
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
private val mLocationListener: LocationListener = LocationListener {
currLocationMarker?.remove()
currLocationMarker = mMap?.addMarker(
MarkerOptions().position(LatLng(it.latitude, it.longitude)).title("Current Location")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE))
)
animateCameraToLocation(currLocationMarker?.position!!)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
geofenceClient = LocationServices.getGeofencingClient(this)
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
val mLocationManager = getSystemService(LOCATION_SERVICE) as LocationManager
if (!shouldRequestPermissions()) subscribeToLiveCurrentLocation(mLocationManager)
}
@SuppressLint("MissingPermission")
private fun subscribeToLiveCurrentLocation(mLocationManager: LocationManager) {
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000,
50F, mLocationListener
)
}
private fun requestPermissionsIfNeeded() {
if (!shouldRequestPermissions()) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
2
)
else ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
1
)
}
@SuppressLint("InlinedApi")
private fun shouldRequestPermissions(): Boolean = ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) != PackageManager.PERMISSION_GRANTED
/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the camera. In this case,
* we just add a marker near Sydney, Australia.
* If Google Play services is not installed on the device, the user will be prompted to install
* it inside the SupportMapFragment. This method will only be triggered once the user has
* installed Google Play services and returned to the app.
*/
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
mMap?.setOnMapClickListener { latlong ->
drawGeofenceOnMap(latlong)
addGeofenceToList(latlong)
}
}
private fun drawGeofenceOnMap(latlong: LatLng) {
val marker = mMap?.addMarker(MarkerOptions().position(latlong).title("Geofence"))
val circle = mMap?.addCircle(
CircleOptions().center(latlong).radius(defaultGeofenceRadius.toDouble()).strokeColor(
resources.getColor(android.R.color.holo_red_light, null)
).fillColor(
resources.getColor(android.R.color.transparent, null)
)
)
mMap?.moveCamera(CameraUpdateFactory.newLatLng(latlong))
geofenceMapMarks["Geofence #" + (geofenceList.size + 1)] = Pair(marker!!, circle!!)
}
private fun animateCameraToLocation(latlong: LatLng) {
val cameraPosition = CameraPosition.Builder()
.target(latlong)
.zoom(17f)
.build()
mMap?.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
}
private fun addGeofenceToList(latlong: LatLng) {
geofenceList.add(
Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
.setRequestId("Geofence #" + (geofenceList.size + 1))
// Set the circular region of this geofence.
.setCircularRegion(
latlong.latitude,
latlong.longitude,
defaultGeofenceRadius
)
// Set the expiration duration of the geofence. This geofence gets automatically
// removed after this period of time.
.setExpirationDuration(Geofence.NEVER_EXPIRE)
// Set the transition types of interest. Alerts are only generated for these
// transition. We track entry and exit transitions in this sample.
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
// Create the geofence.
.build()
)
}
private fun getGeofencingRequest(): GeofencingRequest {
return GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofences(geofenceList)
}.build()
}
private fun addGeofences() {
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
Toast.makeText(this, "Need to grant permissions to continue", Toast.LENGTH_LONG).show()
requestPermissionsIfNeeded()
return
}
geofenceClient?.addGeofences(getGeofencingRequest(), geofencePendingIntent)?.run {
addOnSuccessListener {
Toast.makeText(this@MapsActivity, "Geofences added!", Toast.LENGTH_LONG).show()
}
addOnFailureListener {
Toast.makeText(this@MapsActivity, "Failed to add geofences!", Toast.LENGTH_LONG)
.show()
Log.d("MAPSACTIVITY", it.message.toString())
}
}
}
fun removeGeofences(ids: ArrayList<String>) {
removeGeofencesFromClient(ids)
removeGeofencesFromMap(ids)
}
private fun removeGeofencesFromMap(ids: ArrayList<String>) {
for (id in ids) {
if (geofenceMapMarks.keys.contains(id)) geofenceMapMarks.remove(id)
}
}
private fun removeGeofencesFromClient(ids: ArrayList<String>) {
for (fence in geofenceList) {
if (ids.contains(fence.requestId)) geofenceList.remove(fence)
}
geofenceClient?.removeGeofences(ids)
?.addOnCompleteListener { task ->
if (task.isSuccessful) {
Toast.makeText(
this,
"Geofences have been removed!",
Toast.LENGTH_LONG
).show()
} else {
Toast.makeText(
this,
"Failed to remove geofences",
Toast.LENGTH_LONG
).show()
Log.d("MAPSACTIVITY", "error ==> " + task.exception)
}
}
}
override fun onDestroy() {
super.onDestroy()
mMap = null
geofenceClient = null
}
@SuppressLint("InlinedApi")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
var grantFailed = false
when (requestCode) {
1 -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "GRANTED", Toast.LENGTH_SHORT).show()
} else grantFailed = true
}
2 -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "GRANTED first", Toast.LENGTH_SHORT).show()
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
3
)
} else grantFailed = true
}
3 -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "GRANTED second", Toast.LENGTH_SHORT).show()
} else grantFailed = true
}
}
if (grantFailed) {
if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) ||
shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
) {
Toast.makeText(this, "Show permission rationale", Toast.LENGTH_LONG).show()
} else Toast.makeText(
this,
"You must grant the requested permissions to continue",
Toast.LENGTH_SHORT
).show()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.addGeofence -> {
addGeofences()
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.options_menu, menu)
return true
}
}
清单:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.spap.geofencepoc">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.GeoFencePoC"
tools:ignore="AllowBackup">
<receiver android:name=".receivers.GeofenceReceiver"/>
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />
<activity
android:name=".presentation.MapsActivity"
android:label="@string/title_activity_maps">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>