使用Kotlin在Android中实现地理围栏
Geofence是一个模仿的变量,它描述了一个真实的感兴趣的地理区域。Geofencing API允许你定义一个特定区域的轮廓或限制。当用户越过Geofence时,他们会收到通知提醒。
地理围栏API采用了设备传感器的使用,以节省电池的方式检测用户的位置。
地理围栏包括三种过渡类型。
- 进入- 这表明用户已经进入地理围栏。
- 居住--表明用户在一定时期内存在于地理围栏内。
- 退出- 这表明用户已经移出了地理围栏。
前提条件
要跟上这个教程,你应该。
- 在你的机器上安装最新版本的[Android Studio]。
- 拥有谷歌地图的基本知识。
- 拥有[Kotlin]编程语言的基本知识。
- 能够使用[ViewBinding]。
开始吧
第1步 - 创建一个Android项目
在这一步,我们将创建一个带有谷歌地图活动的Android Studio项目。
确保你已经选择了
Google Maps Activity模板。

第2步 - 包括所需的依赖项
在你的应用级build.gradle 文件中包括以下依赖项。
implementation 'com.google.android.gms:play-services-maps:17.0.1'
implementation 'com.google.android.gms:play-services-location:18.0.0'
第3步 - 添加所需的权限
要开始使用Geofencing API,用户必须首先允许定位权限。
在Android manifest 文件中,添加以下权限。
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
检查权限
在声明该功能之前,确保该应用程序有在前台和后台运行的权限。查一下设备的Android API版本是很有用的。
在你的MainActivity 文件中添加以下代码。
private val gadgetQ = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q
为了确定权限是否已被授予,创建以下函数。
@TargetApi(29)
private fun approveForegroundAndBackgroundLocation(): Boolean {
val foregroundLocationApproved = (
PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION
))
val backgroundPermissionApproved =
if (gadgetQ) {
PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
} else {
true
}
return foregroundLocationApproved && backgroundPermissionApproved
}
如果设备运行的是Android Q(API 29),确保权限ACCESS_BACKGROUND_LOCATION 和ACCESS_FINE_LOCATION 被启用。
如果设备运行的是旧的安卓版本,你不需要权限就可以在后台访问用户的位置。
private fun authorizedLocation(): Boolean {
val formalizeForeground = (
PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION
))
val formalizeBackground =
if (gadgetQ) {
PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
} else {
true
}
return formalizeForeground && formalizeBackground
}
请求后台和精细的位置权限
这是你向用户请求权限的地方,如果没有被授予的话,你可以访问他们的位置。
在
global scope或companion object中添加以下变量。
private val REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE = 3 // random unique value
private val REQUEST_FOREGROUND_ONLY_PERMISSIONS_REQUEST_CODE = 4
private val REQUEST_TURN_DEVICE_LOCATION_ON = 5
private fun askLocationPermission() {
if (authorizedLocation())
return
var grantingPermission = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
val customResult = when {
gadgetQ -> {
grantingPermission += Manifest.permission.ACCESS_BACKGROUND_LOCATION
REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE
}
else -> REQUEST_FOREGROUND_ONLY_PERMISSIONS_REQUEST_CODE
}
Log.d(TAG, "askLocationPermission")
ActivityCompat.requestPermissions(
this, grantingPermission, customResult
)
}
一旦用户对权限请求做出回应,你应该在onRequestPermissionsResult() 方法中处理他们的回应,如下图所示。
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE ||
requestCode == REQUEST_FOREGROUND_ONLY_PERMISSIONS_REQUEST_CODE) {
if (grantResults.size > 0 && (grantResults[0] == PackageManager.PERMISSION_GRANTED)){
validateGadgetAreaInitiateGeofence()
}
}
}
第4步 - 检查小工具的位置。
如果用户的设备位置被停用,授予的权限将毫无价值。为了验证设备的位置是否被启用,添加以下代码。
检查设备位置设置并启动地理围栏
private fun validateGadgetAreaInitiateGeofence(resolve: Boolean = true) {
// create a location request that request for the quality of service to update the location
val locationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_LOW_POWER
}
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
// check if the client location settings are satisfied
val client = LocationServices.getSettingsClient(this)
// create a location response that acts as a listener for the device location if enabled
val locationResponses = client.checkLocationSettings(builder.build())
locationResponses.addOnFailureListener { exception ->
if (exception is ResolvableApiException && resolve) {
try {
exception.startResolutionForResult(
this, REQUEST_TURN_DEVICE_LOCATION_ON
)
} catch (sendEx: IntentSender.SendIntentException) {
Log.d(TAG, "Error getting location settings resolution: ${sendEx.message}")
}
} else {
Toast.makeText(this, "Enable your location", Toast.LENGTH_SHORT).show()
}
}
locationResponses.addOnCompleteListener {it ->
if (it.isSuccessful) {
addGeofence()
}
}
}
检查用户是否接受或拒绝了onActivityResult() 方法中的请求。如果他们没有,再次提示他们。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
validateGadgetAreaInitiateGeofence(false)
}
第5步 - 添加和删除地理围栏
添加地理围栏 你需要一个继承自PendingIntent 的方法来管理地理围栏的转换。
一个PendingIntent ,既描述了一个intent ,也描述了应该对其进行的action 。
我们将为一个BroadcastReceiver 定义一个待定意图,以控制Geofence的转换。
private val geofenceIntent: PendingIntent by lazy {
val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
GeofencingClient 是与Geofencing APIs互动的最基本方式。
因此,创建一个GeofencingClient 的实例。
private lateinit var geoClient: GeofencingClient
在onCreate() 方法中,初始化geofencingClient 。
geoClient = LocationServices.getGeofencingClient(this)
还是在onCreate 方法中,添加一个持有geofences 的geofenceList 。
在这一步中,我们将添加一个地理围栏,但你可以根据自己的意愿来添加。
val latitude = 0.616016
val longitude = 34.521816
val radius = 100f
geofenceList.add(Geofence.Builder()
.setRequestId("entry.key")
.setCircularRegion(latitude,longitude,radius)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build())
创建一个指定地理围栏的函数,如下图所示。
private fun seekGeofencing(): GeofencingRequest {
return GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofences(geofenceList)
}.build()
}
要将地理围栏与pendingIntent ,创建一个地理围栏函数,并在其中包括以下实现。
private fun addGeofence(){
if (ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
geofencingClient?.addGeofences(getGeofencingRequest(), geofenceIntent)?.run {
addOnSuccessListener {
Toast.makeText(this@MapsActivity, "Geofence(s) added", Toast.LENGTH_SHORT).show()
}
addOnFailureListener {
Toast.makeText(this@MapsActivity, "Failed to add geofence(s)", Toast.LENGTH_SHORT).show()
}
}
}
删除一个地理围栏
当不使用时,删除与PendingIntent 相关的任何地理围栏是一个好的做法。我们使用下面的方法来这样做。
private fun removeGeofence(){
geofencingClient?.removeGeofences(geofenceIntent)?.run {
addOnSuccessListener {
Toast.makeText(this@MapsActivity, "Geofences removed", Toast.LENGTH_SHORT).show()
}
addOnFailureListener {
Toast.makeText(this@MapsActivity, "Failed to remove geofences", Toast.LENGTH_SHORT).show()
}
}
}
在onDestroy 方法中,调用removeGeofence() 函数。
override fun onDestroy() {
super.onDestroy()
removeGeofence()
}
第6步 - 创建一个BroadcastReceiver类
Android应用程序可以在设备上发送和接收广播信息。
BroadcastReceiver 侦听Geofence的转换,并在设备进入一个特定的geofence区域时提供一个通知。
BroadcastReceiver的实现方法如下。
class GeofenceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceStatusCodes.getStatusCodeString(geofencingEvent.errorCode)
Log.e(TAG, errorMessage)
return
}
val geofenceTransition = geofencingEvent.geofenceTransition
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
val triggeringGeofences = geofencingEvent.triggeringGeofences
// Creating and sending notification
val notificationManager = ContextCompat.getSystemService(
context!!, NotificationManager::class.java
) as NotificationManager
notificationManager.sendGeofenceEnteredNotification(context)
} else {
Log.e(TAG, "Invalid type transition $geofenceTransition")
}
}
}
在你的清单中,添加以下代码来注册BroadCastReceiver。
<application>
...
<receiver android:name=".GeofenceBroadcastReceiver"/>
</application>
设置通知
我们使用以下代码设置了一个通知。
private const val NOTIFICATION_ID = 33
private const val CHANNEL_ID = "GeofenceChannel"
fun createChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannel =
NotificationChannel(CHANNEL_ID, "Channel1", NotificationManager.IMPORTANCE_HIGH)
val notificationManager = context.getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(notificationChannel)
}
}
// extension function
fun NotificationManager.sendGeofenceEnteredNotification(context: Context) {
// Opening the notification
val contentIntent = Intent(context, MapsActivity::class.java)
val contentPendingIntent = PendingIntent.getActivity(
context,
NOTIFICATION_ID,
contentIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
// Building the notification
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle(context.getString(R.string.app_name))
.setContentText("You have entered a geofence area")
.setSmallIcon(R.drawable.ic_baseline_notifications_24)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(contentPendingIntent)
.build()
this.notify(NOTIFICATION_ID, builder)
}
结语
在本文中,我们了解了什么是地理围栏,如何添加和删除地理围栏,使用广播接收器监听地理围栏事件,以及在有人进入地理围栏时显示通知。