根据牛津词典,寻宝是指“一种活动或消遣,其中一件物品或装有几件物品的容器隐藏在特定位置,供 GPS 用户使用互联网上发布的坐标找到。
对于地理缓存应用程序,我们希望应用程序在用户处于项目 A 的特定半径内时通知用户。假设用户(由标记表示)将项目存储在由另一个标记表示的坐标中。如何在Windows 11中重置网络设置?只需6步轻松搞定在这种情况下,项的标记是静态的,而用户的标记是动态的。
使用 Android 中的融合位置库,我们可以构建一个地理缓存应用程序,该应用程序提供有关当前用户坐标的后台服务通知。如果用户在缓存的五英里半径范围内,则用户将收到通知,如果他们靠近或远离项目,如何关闭Windows 11中的“搜索网络”结果?禁用搜索窗口教程则将继续更新距离计算。
先决条件
阅读器需要在其特定设备上安装 Android Studio 代码编辑器和 Kotlin。
开始
我们将从创建一个开始 谷歌 .为此,请创建一个新的 Android Studio 项目。选择 Google 地图活动记录作为模板,然后填写我们的应用名称和程序包名称。这样做会占用很多流程,因为现在我们只需要从 Google 控制台获取一个 API 密钥:MapFragment
接下来,我们将转到Google开发人员的控制台以获取API密钥。
然后,选择“创建凭据和 API 密钥”以创建 API 密钥:
复制我们新创建的密钥,前往文件,并使用关键字 API key 将其粘贴到元数据标签属性中:AndroidManifest.xml``value
创建功能
按照上述步骤操作后,我们只有一个由Android Studio自动创建的自定义Google地图。在本节中,我们希望使用 API 为用户获取持续的位置更新,即使在关闭应用程序后也是如此。我们将通过后台通知更新来实现此目的。fusedlcation
首先,转到模块文件并添加下面的依赖项:build.gradle
implementation 'com.google.android.gms:play-services-location:20.0.0' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
接下来,返回到文件并在应用程序标记的正上方设置以下权限:AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
创建抽象和权限设置
我们不想打开应用程序并自动拥有位置访问权限(好吧,应用程序不是那样工作的)。相反,我们希望客户端收到通知,要求访问某些系统设置。
我们将构建一个接口文件,该文件抽象根文件夹中的位置更新并调用它。在界面中,我们将创建一个带有参数间隔的函数,该函数指定我们希望更新位置的频率。该函数将从我们之前添加到依赖项的协程库中返回 of 类型。ClientInfo.ktFlow
Location
我们还将创建一个类来传递消息,以防我们的 GPS 关闭:
interface ClientInfo { fun getLocationUpdates(interval: Long): Flow<Location> class LocException(message: String): Exception() }
现在,我们需要展示 .因此,趣知笔记在同一个根文件夹中,创建一个名为 的类文件,该文件将实现我们上面声明的接口 ()。然后,该类将采用两个构造函数参数:和 。ClientInfoDefaultClientInfo.kt
ClientInfoContext
FusedLocationProviderClient
接下来,我们将覆盖该函数,并使用 callbackFlow 实例,我们首先检查用户是否已接受位置权限。为此,我们将在同一个根文件夹中创建一个实用程序文件,该文件调用以编写返回布尔值的扩展函数。getLocationUpdates``ExtendContext.kt
此函数将检查是否授予了 和 权限:COARSE``FINE_LOCATION
fun Context.locationPermission(): Boolean{ return ContextCompat.checkSelfPermission( this, Manifest.permission.ACCESS_COARSE_LOCATION )== PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED }
如果用户具有允许权限,我们希望检查他们是否可以使用 .SytemService LocationManager
现在我们可以获取用户的位置,我们需要创建一个请求,该请求将指定我们获取用户位置的频率和数据的准确性。此外,趣知笔记网站地图我们将创建一个回调,每当 FusedLocationProviderClient 获取新位置时,该回调都将使用该函数。onLocationResult
最后,我们将使用该方法调用回调函数、请求和循环器。以下是实现:fusedlocation.requestLocationUpdates
class DefaultClientInfo( private val context:Context, private val fusedlocation: FusedLocationProviderClient ):ClientInfo{ @SuppressLint("MissingPermission") override fun getLocationUpdates(interval: Long): Flow<Location> { return callbackFlow { if(!context.locationPermission()){ throw ClientInfo.LocException("Missing Permission") } val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager val hasGPS = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) val hasNetwork = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) if(!hasGPS && hasNetwork){ throw ClientInfo.LocException("GPS is unavailable") } val locationRequest = LocationRequest.create().apply { setInterval(interval) fastestInterval = interval priority = Priority.PRIORITY_HIGH_ACCURACY } val locationCallback = object : LocationCallback(){ override fun onLocationResult(result: LocationResult) { super.onLocationResult(result) result.locations.lastOrNull()?.let{ location -> launch { send(location) } } } } fusedlocation.requestLocationUpdates( locationRequest, locationCallback, Looper.getMainLooper() ) awaitClose { fusedlocation.removeLocationUpdates(locationCallback) } } } }
创建前台服务
为了创建前台服务,我们将在根项目中创建另一个类文件,称为并使其继承自服务类。使用协程,我们将创建一个绑定到服务生存期的类,调用我们之前创建的抽象,以及一个存储缓存坐标信息的类。locservices.ktserviceScope
ClientInfo
class LocServices: Service(){ private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private lateinit var clientInfo: ClientInfo }
接下来,我们将创建一个将返回的函数,因为我们没有将我们的服务绑定到任何东西。然后,我们将使用该函数调用类,我们将在其中提供 和 作为 参数。onBindnull
onCreateDefaultClientInfo
applicationContext``LocationServices.getFusedLocationProviderClient(applicationContext)
class LocServices: Service(){ // do something override fun onBind(p0: Intent?): IBinder? { return null } override fun onCreate() { super.onCreate() clientInfo = DefaultClientInfo( applicationContext, LocationServices.getFusedLocationProviderClient(applicationContext) ) } }
现在,我们将创建一个,并在其中创建一个 常量值 ,当我们想要开始跟踪时,我们会将其发送到服务。然后,我们将为服务调用函数,并提供我们之前创建的常量作为我们链接到函数的常量:companion objectSTART
onStartCommand()intent
start()
class LocServices: Service(){ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when(intent?.action){ START -> start() } return super.onStartCommand(intent, flags, startId) } @SuppressLint("NewApi") private fun start(){ } companion object{ const val START = "Start" } }
该函数将处理通知,以提醒用户其位置正在受到主动监视。这意味着我们要为用户提供的信息是他们与缓存之间的距离(以米为单位)。为此,我们将使用 Haversine 公式,该公式使用球体上两点之间的坐标计算它们之间的距离。start
因此,使用我们的 ,我们将调用该方法,并使用 提供的方法,我们将能够获得更新的纬度和经度。callbackflowclientInfo.getLocationUpdates(interval)
onEach``coroutines
正如我们之前所说,我们希望用户知道它们与缓存之间的距离,但有一个问题。我们不希望用户收到一连串一致的通知,告诉他们他们与缓存之间的距离。
因此,我们将创建一个条件语句,用于检查用户是否在缓存的千米半径范围内。如果为 true,则用户将收到持续通知,通知他们是否离缓存越来越远或越来越近。一旦他们进入 50 米半径内,就会收到一条不同的消息通知,服务将停止:
class LocServices: Service(){ @SuppressLint("NewApi") private fun start(){ val notif = NotificationCompat.Builder(this, "location") .setContentTitle("Geocaching") .setContentText("runnning in the background") .setSmallIcon(R.drawable.ic_launcher_background) .setOngoing(true) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager clientInfo .getLocationUpdates(1000L) .catch { e -> e.printStackTrace() } .onEach { location -> val lat1 = location.latitude val long1 = location.longitude val radius = 6371 //in km val lat2 = secrets.d val long2 = secrets.d1 val dlat = Math.toRadians(lat2 - lat1) val dlong = Math.toRadians(long2 - long1) val a = sin(dlat / 2) * sin(dlong / 2) + cos(Math.toRadians(lat1)) * cos(Math.toRadians(lat2)) * sin(dlong / 2) * sin(dlong / 2) val c = 2 * asin(sqrt(a)) val valuresult = radius * c val km = valuresult / 1 val meter = km * 1000 val truemeter = String.format("%.2f", meter) if (meter > 100 && meter <= 1000){ val updatednotif = notif .setContentText("You are $truemeter meters away") notificationManager.notify(1, updatednotif.build()) } if (meter < 100){ val getendnotice = notif .setContentText("You are $truemeter meters away, continue with your search") .setOngoing(false) notificationManager.notify(1, getendnotice.build()) stopForeground(STOP_FOREGROUND_DETACH) stopSelf() } } .launchIn(serviceScope) startForeground(1, notif.build()) } }
最后,我们将创建一个函数,该函数在关闭应用程序或清除系统缓存时取消服务。下面是代码的实现:onDestroy
class LocServices: Service(){ override fun onDestroy() { super.onDestroy() serviceScope.cancel() } }
现在我们已经准备好了前台服务,我们将返回到元数据标记正上方的文件和标记:AndroidManifest.xml
<service android:name=".fusedLocation.LocServices" android:foregroundServiceType = "location"/>
通知通道
如果我们想为用户到缓存的距离创建通知,我们需要创建一个通道来发送通知。让我们首先创建一个名为 .LocationApp.kt``Application()
在该函数中,我们将创建一个从 Android oreo 操作系统向上的通知通道。代码的外观如下:onCreate
class LocationApp: Application() { override fun onCreate() { super.onCreate() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( "location", "Location", NotificationManager.IMPORTANCE_LOW ) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } } }
最后,我们将属性添加到下面文件的应用程序标记中:AndroidManifest.xml
android:name=".fusedLocation.LocationApp"
MapsActivity.kt
当我们创建Google地图活动时,我们得到了一个文件而不是常规文件。此文件处理带有标记的地图的创建。我们需要对此进行一些更改。因此,让我们创建三个变量:和 。MapsActivity.ktMainActivity.kt
private lateinitLocationCallback
LocationRequest``FusedLocationProviderClient
接下来,我们将创建三个函数;和。我们将在回调函数中调用它们。launchintentgetupdatedlocation
startupdate``onMapReady
该函数处理位置权限请求,该函数采用 和 .该函数还将使用其意图处理调用 start 函数。launchintentgetupdatedlocation
LocationRequestLocationCallback
getupdatedlocation
最后,在函数中,我们将使用该方法来调用回调函数、请求和循环器(设置为 null)。startupdate``fusedlocation.requestLocationUpdates
代码的外观如下:
class MapsActivity : AppCompatActivity(), OnMapReadyCallback{ companion object{ private var firsttime = true } private lateinit var mMap: GoogleMap private lateinit var binding: ActivityMapsBinding private lateinit var locationCallback: LocationCallback private lateinit var locationRequest: LocationRequest private lateinit var fusedLocationProviderClient: FusedLocationProviderClient private var mMarker: Marker? = null private var secrets = Secretlocation() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMapsBinding.inflate(layoutInflater) setContentView(binding.root) fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(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) } private fun launchintent() { ActivityCompat.requestPermissions( this, arrayOf( Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION ), 0 ) } private fun getupdatedlocation(){ locationRequest = LocationRequest.create().apply { interval = 10000 fastestInterval = 5000 priority = Priority.PRIORITY_HIGH_ACCURACY } locationCallback = object : LocationCallback(){ override fun onLocationResult(result: LocationResult) { if (result.locations.isNotEmpty()){ val location = result.lastLocation if (location != null){ mMarker?.remove() val lat1 = location.latitude val long1 = location.longitude val d = secrets.d val d1 = secrets.d1 val latlong = LatLng(lat1, long1) val stuff = LatLng(d, d1) val stuffoption= MarkerOptions().position(stuff).title("$stuff").icon( BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE)) mMarker = mMap.addMarker(stuffoption) val markerOptions = MarkerOptions().position(latlong).title("$latlong") mMarker = mMap.addMarker(markerOptions) if (firsttime){ mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latlong, 17f )) firsttime = false } } } } } Intent(applicationContext, LocServices::class.java).apply { action = LocServices.START startService(this) } } @SuppressLint("MissingPermission") private fun startupdate(){ fusedLocationProviderClient.requestLocationUpdates( locationRequest, locationCallback, null ) } override fun onMapReady(googleMap: GoogleMap) { mMap = googleMap launchintent() getupdatedlocation() startupdate() mMap.uiSettings.isZoomControlsEnabled = true } }
当我们运行我们的应用程序时,我们应该得到以下结果:
结论
在本教程中,我们使用 Android 的融合位置库创建了一个地图,该库会不断更新用户在地图上的位置。我们还创建了一个前台服务,用于确定用户与特定项目之间的距离。最后,我们为用户靠近缓存创建了一个通知。