前台服务被杀死

4

我有一个服务,定期更新后端用户的最新位置。然而,一些用户报告称前台通知有时会消失,但并非所有用户都会遇到这个问题。有些用户每天使用该应用8小时,但通知从未消失过,并且Crashlytics上也没有崩溃日志。

以下是我的ServiceClass。

package com.xxxxxxx.xxxxxxxx.service

import android.annotation.SuppressLint
import android.app.Notification
import android.app.Notification.FLAG_NO_CLEAR
import android.app.Notification.FLAG_ONGOING_EVENT
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.Service
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
import android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP
import android.graphics.Color
import android.location.Location
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.O
import android.os.IBinder
import android.util.Base64
import android.util.Base64.DEFAULT
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.model.LatLng
import com.xxxx.commons.XXXXXXCommons
import com.xxxxxxx.xxxxxxxx.R
import com.xxxxxxx.xxxxxxxx.di.DataSourceModule
import com.xxxxxxx.xxxxxxxx.domain.GetAppStateUseCase.Companion.LOCATION_DELIMITER
import com.xxxxxxx.xxxxxxxx.model.local.CachedLocation
import com.xxxxxxx.xxxxxxxx.model.local.DriverLocation
import com.xxxxxxx.xxxxxxxx.utils.LOCATION_PERMISSIONS
import com.xxxxxxx.xxxxxxxx.utils.OnGoingTrip
import com.xxxxxxx.xxxxxxxx.utils.checkPermissions
import com.xxxxxxx.xxxxxxxx.view.activity.SplashActivity
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.text.Charsets.UTF_8

class LocationTrackingService : Service() {

    private companion object {
        const val NOTIFICATION_ID = 856
        const val FOREGROUND_SERVICE_ID = 529
        const val LOCATION_UPDATE_INTERVAL = 10000L
        const val LOCATION_UPDATE_FASTEST_INTERVAL = 5000L
    }

    private val updateJob: Job = Job()
    private val errorHandler = CoroutineExceptionHandler { _, throwable ->
        XXXXXXCommons.crashHandler?.log(throwable)
    }
    private val locationTrackingCoroutineScope =
        CoroutineScope(Dispatchers.IO + updateJob + errorHandler)

    private val rideApiService by lazy { DataSourceModule.provideRideApiService() }
    private val locationDao by lazy { DataSourceModule.provideLocationDao(XXXXXXCommons.provideApplicationContext()) }

    private val locationRequest = LocationRequest().apply {
        interval = LOCATION_UPDATE_INTERVAL
        fastestInterval = LOCATION_UPDATE_FASTEST_INTERVAL
        priority = LocationRequest.PRIORITY_HIGH_ACCURACY
    }

    private val callback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult?) = onLocationReceived(result)
    }

    @SuppressLint("MissingPermission")
    private fun requestLocationUpdates() {
        if (checkPermissions(*LOCATION_PERMISSIONS)) {
            LocationServices.getFusedLocationProviderClient(this)
                .requestLocationUpdates(locationRequest, callback, null)
        }
    }

    private fun onLocationReceived(result: LocationResult?) = result?.lastLocation?.run {
        postDriverLocation(this)
    } ?: Unit

    private fun postDriverLocation(location: Location) {
        val driverLocation = DriverLocation(
            location.latitude.toFloat(),
            location.longitude.toFloat(),
            location.bearing,
            OnGoingTrip.rideData.rideId,
            OnGoingTrip.orderData.orderId
        )
        OnGoingTrip.currentLocation = LatLng(location.latitude, location.longitude)
        OnGoingTrip.reportLocationUpdate()
        locationTrackingCoroutineScope.launch(Dispatchers.IO) {
            cacheDriverLocation(driverLocation)
            rideApiService.postCurrentDriverLocation(driverLocation)
        }
    }

    private suspend fun cacheDriverLocation(driverLocation: DriverLocation) {
        val bytes = "${driverLocation.latitude}$LOCATION_DELIMITER${driverLocation.longitude}"
            .toByteArray(UTF_8)
        val safeLocation = Base64.encode(bytes, DEFAULT).toString(UTF_8)
        locationDao.createOrUpdate(CachedLocation(location = safeLocation))
    }

    override fun onBind(intent: Intent): IBinder? = null

    override fun onCreate() {
        super.onCreate()
        buildNotification(
            if (SDK_INT >= O) {
                createNotificationChannel(getString(R.string.app_name))
            } else {
                NOTIFICATION_ID.toString()
            }
        )
        requestLocationUpdates()
    }

    override fun onDestroy() {
        super.onDestroy()
        if (locationTrackingCoroutineScope.isActive) {
            locationTrackingCoroutineScope.cancel()
        }
        LocationServices.getFusedLocationProviderClient(this).removeLocationUpdates(callback)
        (getSystemService(NOTIFICATION_SERVICE) as? NotificationManager)?.cancel(NOTIFICATION_ID)
    }

    @RequiresApi(O)
    private fun createNotificationChannel(channelName: String): String {
        val chan = NotificationChannel(
            NOTIFICATION_ID.toString(),
            channelName,
            NotificationManager.IMPORTANCE_NONE
        )
        chan.lightColor = Color.CYAN
        chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
        val service = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        service.createNotificationChannel(chan)
        return NOTIFICATION_ID.toString()
    }

    private fun buildNotification(channelId: String) {
        val ctx = this
        (getSystemService(NOTIFICATION_SERVICE) as? NotificationManager)?.run {
            val intent = Intent(ctx, SplashActivity::class.java).apply {
                flags = FLAG_ACTIVITY_CLEAR_TOP or FLAG_ACTIVITY_SINGLE_TOP
            }
            val resultPendingIntent =
                PendingIntent.getActivity(ctx, 0, intent, FLAG_UPDATE_CURRENT)
            val notification = NotificationCompat.Builder(ctx, channelId)
                .setSmallIcon(R.drawable.ic_xxxxxx_icon_background)
                .setColor(ContextCompat.getColor(ctx, R.color.xxxxxxTurquoise))
                .setContentTitle(ctx.getString(R.string.app_name))
                .setOngoing(true)
                .setContentText(ctx.getString(R.string.usage_message))
                .setContentIntent(resultPendingIntent)
                .build().apply { flags = FLAG_ONGOING_EVENT or FLAG_NO_CLEAR }
            startForeground(FOREGROUND_SERVICE_ID, notification)
        }
    }
}

我是如何启动这个服务的:

startService(Intent(this, LocationTrackingService::class.java));

这是我的权限清单以及我如何在AndroidManifest.xml上声明的:

<service
            android:name=".service.LocationTrackingService"
            android:enabled="true"
            android:exported="true" />

<uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <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" />

编辑

大多数设备运行Android 9和10,少量设备运行5到8,几乎没有设备运行11。

如果需要更多信息,我很乐意分享。


这是许多设备都存在的问题。请参考此网站 - Xid
1
@KeyserSöze 用户使用另一个应用程序,该应用程序具有另一个服务位置,并且另一个应用程序永远不会被终止,只有我的应用程序。 - Victor Castillo Torres
3个回答

4

最近Android 11出现了一些限制,很难提供确切的解决方案。以下是您应该重新检查的几个事项,可能会解决您的问题:

  • 在设备中禁用电池优化
  • 检查设备是否没有在待机模式下运行。
  • 在清单文件中指定android:foregroundServiceType

2
这里有几件事情需要注意。我在你的清单文件中没有看到 <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>。同时,请确保在清单文件中的服务属性中设置了 foregroundServiceType="location"。此外,如果您的前台服务是用于位置,则不需要使用 ACCESS_BACKGROUND_LOCATION
另一方面,小米设备会在用户从应用切换器中关闭应用程序时杀死前台服务(小米是最糟糕的),因此您应该要求小米用户手动将您的应用程序排除在电池节省模式或应用程序详细信息中的额外权限中被杀死(即使他们这样做了,也不能保证不会被杀死,而且不同的 MiUI 版本有不同的规则)。
此外,使用 ContextCompat.startForegroundService 启动您的服务,并在服务的 onStartCommand 中调用 startForeground

我会在本周尝试,非常感谢您的帮助! - Victor Castillo Torres
它奏效了,我们还实现了一个警报,但似乎问题仍在发生,我们将继续监控。感谢您的回答。 - Victor Castillo Torres

0
请解决此问题,请检查以下代码。
  1. 在onCreate()方法中创建一个方法 - checkBatteryOptimization()

  2. 在checkBatteryOptimization()方法中实现给定的代码。

    boolean isBatteryOptimized = false;
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
         Intent intent = new Intent();
         String packageName = getActivity().getPackageName();
         PowerManager pm = (PowerManager) getActivity().getSystemService(POWER_SERVICE);
         if (!pm.isIgnoringBatteryOptimizations(packageName)) {
             intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
             intent.setData(Uri.parse("package:" + packageName));
             startActivity(intent);
         } else {
             isBatteryOptimized = pm.isIgnoringBatteryOptimizations(packageName);
             Log.e("hello battery optimize ",""+pm.isIgnoringBatteryOptimizations(packageName));
         }
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接