Search code examples
androidkotlinforeground-service

Android Kotlin Foreground Service stops after some time


I am working on a foreground location service for users tracking. Each time the location updates this service sends a request to the API to update current position. The issue is when the app is put to the background or the screen is locked the service stops sending requests after some time (usually around 1 minute during which around 10 requests are sent). After the application is restored the service starts working again and after minimizing/locking the screen the scenario repeats.

Inside the onStartCommand I tried to return multiple start options, neither has worked. I have tested the app on Android 10 and 11.

The service source code:

class LocationService: Service()  {

    @Inject
    lateinit var apiService: ApiService

    private val composite = CompositeDisposable()

    private var locationManager: LocationManager? = null
    private var locationListener: LocationListener? = null

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

    val NOTIFICATION_CHANNEL_ID = "location_tracking"
    val NOTIFICATION_CHANNEL_NAME = "Location tracking"
    val NOTIFICATION_ID = 101

    var isFirstRun = true

    @SuppressLint("MissingPermission")
    override fun onCreate() {
        App.component.inject(this)

        setupLocationListener()

        locationManager = getSystemService(LOCATION_SERVICE) as LocationManager?
        val criteria = Criteria()
        criteria.accuracy = Criteria.ACCURACY_FINE
        val provider = locationManager?.getBestProvider(criteria, true)

        val minTime = 5*1000L
        if(provider != null) locationManager?.requestLocationUpdates(provider, minTime, 0f, locationListener)

        super.onCreate()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if (isFirstRun) {
            startForegroundService()
            isFirstRun = false
        } else {
            Timber.d {"Resuming service"}
        }

        return super.onStartCommand(intent, flags, startId)
    }

    private fun startForegroundService() {
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createNotificationChannel(notificationManager)

        val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .setAutoCancel(false)
                .setOngoing(true)
                .setSmallIcon(R.drawable.ic_notification)
                .setContentTitle(NOTIFICATION_CHANNEL_NAME)
                .setContentIntent(getMainActivityIntent())

        startForeground(NOTIFICATION_ID, notificationBuilder.build())
    }

    private fun getMainActivityIntent()
        = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java)
            .also { it.action = R.id.action_global_navigationScreenFragment.toString() }, FLAG_UPDATE_CURRENT)

    @RequiresApi(Build.VERSION_CODES.O)
    private fun createNotificationChannel(notificationManager: NotificationManager) {
        val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, IMPORTANCE_LOW)
        notificationManager.createNotificationChannel(channel)
    }

    private fun setupLocationListener() {
        locationListener = object: LocationListener {

            override fun onLocationChanged(location: Location) {
                val cords = GeoCoordinatesDTO(location.latitude.toFloat(), location.longitude.toFloat())
                try {
                    composite.add(apiService.mobileUserAccountReportPosition(cords)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(
                            {},
                            { t ->
                                if(t is RuntimeException) {
                                    e(t)
                                }
                            }
                        ))
                } catch(e: Exception) {
                    Log.e("GPS", "error: $e")
                }
            }

            override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}

            override fun onProviderEnabled(provider: String) {}

            override fun onProviderDisabled(provider: String) {}

        }
    }

    override fun onDestroy() {
        try {
            locationManager?.removeUpdates(locationListener)
        } catch(e: DeadObjectException) {}
        super.onDestroy()
    }

}

The service is started from onStart funciorn in MainActivity

private fun initializeLocationMonitor() {
    locationService = Intent(this, LocationService::class.java)
    if(!this.isServiceRunning(LocationService::class.java)) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            startForegroundService(locationService)
        } else {
            startService(locationService)
        }
        sendBroadcast(locationService)
    }
}

I have following permissions in the manifest as well as registered the serivce:

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

<service
    android:name=".Services.LocationService"
    android:enabled="true"
    android:exported="true"
    tools:ignore="ExportedService,InnerclassSeparator"
    android:foregroundServiceType="location"/>

Solution

    1. Keep the device awake - https://developer.android.com/training/scheduling/wakelock
    // global service variable
    private var wakeLock: PowerManager.WakeLock? = null
    ...
    // initialize before setupLocationListener() is called
    wakeLock =
                (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
                    newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "LocationService::lock").apply {
                        acquire(10*1000L)  // 10 seconds
                    }
                }
    
    

    Manifest:

    <uses-permission android:name="android.permission.WAKE_LOCK" />
    
    1. Check if your app has restricted data usage and battery sever inside application settings.