如何使用Android的融合位置构建地理缓存应用程序

根据牛津词典,寻宝是指“一种活动或消遣,其中一件物品或装有几件物品的容器隐藏在特定位置,供 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.ktFlowLocation

我们还将创建一个类来传递消息,以防我们的 GPS 关闭:

interface ClientInfo {
    fun getLocationUpdates(interval: Long): Flow<Location>
​
    class LocException(message: String): Exception()
}

现在,我们需要展示 .因此,趣知笔记在同一个根文件夹中,创建一个名为 的类文件,该文件将实现我们上面声明的接口 ()。然后,该类将采用两个构造函数参数:和 。ClientInfoDefaultClientInfo.ktClientInfoContextFusedLocationProviderClient

接下来,我们将覆盖该函数,并使用 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.ktserviceScopeClientInfo

class LocServices: Service(){
​
    private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private lateinit var clientInfo: ClientInfo
​
}

接下来,我们将创建一个将返回的函数,因为我们没有将我们的服务绑定到任何东西。然后,我们将使用该函数调用类,我们将在其中提供 和 作为 参数。onBindnullonCreateDefaultClientInfoapplicationContext``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 objectSTARTonStartCommand()intentstart()

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.ktprivate lateinitLocationCallbackLocationRequest``FusedLocationProviderClient

接下来,我们将创建三个函数;和。我们将在回调函数中调用它们。launchintentgetupdatedlocationstartupdate``onMapReady

该函数处理位置权限请求,该函数采用 和 .该函数还将使用其意图处理调用 start 函数。launchintentgetupdatedlocationLocationRequestLocationCallbackgetupdatedlocation

最后,在函数中,我们将使用该方法来调用回调函数、请求和循环器(设置为 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 的融合位置库创建了一个地图,该库会不断更新用户在地图上的位置。我们还创建了一个前台服务,用于确定用户与特定项目之间的距离。最后,我们为用户靠近缓存创建了一个通知。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pxr007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值