我想创建一个可以在后台更新位置的Android应用程序.我翻阅了文档、文章和教程,但没有达到我的目标.当应用程序最小化时,它会暂停更新并再次启动它们.我返回到它.

我的 list 文件:

<service android:name=".LocationService" android:foregroundServiceType="location" />
...
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

我的活动代码启动定位服务:

val serviceIntent = Intent(applicationContext, LocationService::class.java).apply {
    action = LocationService.ACTION_START
    startService(this)
}

applicationContext.startService(serviceIntent)

我的定位服务:

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
import com.google.android.gms.location.LocationServices
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

class LocationService: Service() {

    private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private lateinit var locationClient: LocationClient

    override fun onBind(p0: Intent?): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        locationClient = LocationClient(
            applicationContext,
            LocationServices.getFusedLocationProviderClient(applicationContext)
        )
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when(intent?.action) {
            ACTION_START -> start()
            ACTION_STOP -> stop()
            ACTION_PAUSE -> pause()
            ACTION_REFRESH -> refresh()
        }
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy() {
        super.onDestroy()
        serviceScope.cancel()
    }

    private fun start() {    
        locationClient
            .getLocationUpdates(1000L)
            .catch { e -> e.printStackTrace() }
            .onEach { location ->
                Log.i("LOCATION", DistanceTracker.getInstance().fullDistance.toString())
                DistanceTracker.getInstance().addDistance(location)
            }
            .launchIn(serviceScope)
    }

    private fun stop() {
        stopSelf()
        DistanceTracker.getInstance().resetDistances()
        refresh()
    }

    private fun pause() {
        stopSelf()
        DistanceTracker.getInstance().clearLocation()
        refresh()
    }

    companion object {
        const val ACTION_START = "ACTION_START"
        const val ACTION_STOP = "ACTION_STOP"
        const val ACTION_PAUSE = "ACTION_PAUSE"
        const val ACTION_REFRESH = "ACTION_REFRESH"
    }
}

最后是我的定位客户:

import android.annotation.SuppressLint
import android.content.Context
import android.location.Location
import android.location.LocationManager
import android.os.Looper
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationRequest.PRIORITY_HIGH_ACCURACY
import com.google.android.gms.location.LocationResult
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch

class LocationClient(
    private val context: Context,
    private val client: FusedLocationProviderClient
): ILocationClient {
    @SuppressLint("MissingPermission")
    override fun getLocationUpdates(interval: Long): Flow<Location> {
        val locationFlow = callbackFlow {
            if(!context.hasLocationPermission()) {
                throw ILocationClient.LocationException("Missing location permission")
            }

            val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
            val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
            val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
            if(!isGpsEnabled && !isNetworkEnabled) {
                throw ILocationClient.LocationException("GPS is disabled")
            }

            val request = LocationRequest.create()
                .setInterval(interval)
                .setFastestInterval(interval)
                .setPriority(PRIORITY_HIGH_ACCURACY)

            val locationCallback = object : LocationCallback() {
                override fun onLocationResult(result: LocationResult) {
                    val location = result.locations.minWith(Comparator.comparingDouble{it.accuracy as Double})

                    if(location.accuracy < 50) {
                        super.onLocationResult(result)
                        result.locations.lastOrNull()?.let { location ->
                            launch { send(location) }
                        }
                    }
                }
            }

            client.requestLocationUpdates(
                request, locationCallback, Looper.getMainLooper())

            awaitClose {
                client.removeLocationUpdates(locationCallback)
            }
        }

        return locationFlow
    }
}

我遗漏了什么?我想要跟踪我已经覆盖的距离,而不需要将我的应用程序永久放在前台.在这种情况下,真的很不方便.

推荐答案

您应该使用前台服务.前台服务是一种具有更高优先级的特殊类型的服务,即使应用程序在后台,它也可以继续运行.您已经使用android:forecronServiceType="Location"将该服务指定为 list 中的前台服务...但你需要将该服务作为前台服务启动,并在你的应用程序最小化时发出通知.喜欢:

private fun start() {
    val notification = createNotification()
    startForeground(NOTIFICATION_ID, notification)

    locationClient
        .getLocationUpdates(1000L)
        .catch { e -> e.printStackTrace() }
        .onEach { location ->
            Log.i("LOCATION", DistanceTracker.getInstance().fullDistance.toString())
            DistanceTracker.getInstance().addDistance(location)
        }
        .launchIn(serviceScope)
}

您需要定义createNotification()函数来为前台服务创建通知,NOTIFICATION_ID只是一个整数常量来标识通知.

所以:

即使您已经拥有ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION权限...在您的 list 中,您已经拥有android.permission.ACCESS_BACKGROUND_LOCATION,,但您还需要在运行时请求此权限.

val backgroundLocationPermission = Manifest.permission.ACCESS_BACKGROUND_LOCATION
if (ContextCompat.checkSelfPermission(this, backgroundLocationPermission) == PackageManager.PERMISSION_GRANTED) {

} else {
    ActivityCompat.requestPermissions(this, arrayOf(backgroundLocationPermission), REQUEST_CODE_BACKGROUND_LOCATION)
}

最后:

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    if (requestCode == x) {
        if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        } else {
        }
    }
}

示例createNotification:

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat

class LocationService : Service() {

    private fun createNotification(): Notification {
        val channelId = "location_service_channel"
        val channelName = "Location Service"
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

        val intent = Intent(this, YourMainActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)

        val notification = NotificationCompat.Builder(this, channelId)
            .setContentTitle("Tracking Location")
            .setContentText("tracking location.")
            .setSmallIcon(R.drawable.ic_notification_icon)
            .setContentIntent(pendingIntent)
            .build()

        return notification
    }
}

Android相关问答推荐

为什么使用. DeliverveAsState()时会出现空指针异常?

以正确的方式从房间收集流量

derivedStateOf与使用key和MutableState记住

约束布局:垂直链中的视图应将内容包裹到空间的1/3

用于小部件泄漏警告的伙伴对象中的Kotlin Lateinit

如果我的圆形图像的顶部居中于卡片内部,我如何在其下方画一条弧线?

Android-LVL库始终返回NOT_SUBLISTED

如何修复Google Play市场中有关已删除广告库的错误消息?

更改活动(上下文)对接收到的uri的访问权限的影响?

Jetpack Compose X.dp 性能问题?

如何在 compose android中将图像覆盖在另一个图像上

Android kotlin 中闪屏 API 执行完成后如何根据身份验证将用户导航到特定屏幕

使用 Jetpack Compose 在 Android TV 上启用系统声音

如何避免多次调用 Jetpack Compose 的 onClick 回调

更改当前活动并返回后,Android webview 滚动不起作用

如何像 XML 一样在 Compose Android Studio 中折叠/展开小部件代码区域/区域

如何放置在柱子的角落(底端)

状态值更改时屏幕未重新组合 - Jetpack Compose

有什么方法可以确定正在使用哪个 Android 手机的麦克风进行录音?

如何在 Jetpack compose 中将 Canvas 中的文本居中?