В приложении Android возникает ошибка " java.lang.OutOfMemoryError"

Есть приложение, основная задача которого - при нажатии на кнопку создать фоновый процесс, в котором определять свое местоположение и отправлять его в чат телеграмм. Приложение работает, но через некоторое время (10-15 минут) фоновый процесс вылетает с ошибкой:

 *** Uncaught remote exception!  (Exceptions are not yet supported across processes.)
java.lang.OutOfMemoryError: Failed to allocate a 24 byte allocation with 0 free bytes and 0B until OOM, target footprint 268435488, growth limit 268435456
    at android.os.Parcel.createTypedArrayList(Parcel.java:2850)
    at com.google.android.gms.common.internal.safeparcel.SafeParcelReader.createTypedList(com.google.android.gms:play-services-basement@@18.1.0:3)
    at com.google.android.gms.location.zzag.createFromParcel(com.google.android.gms:play-services-location@@21.1.0:6)
    at com.google.android.gms.internal.location.zzc.zza(com.google.android.gms:play-services-location@@21.1.0:2)
    at com.google.android.gms.location.zzv.zza(com.google.android.gms:play-services-location@@21.1.0:5)
    at com.google.android.gms.internal.location.zzb.onTransact(com.google.android.gms:play-services-location@@21.1.0:3)
    at android.os.Binder.execTransactInternal(Binder.java:1159)
    at android.os.Binder.execTransact(Binder.java:1123)

Никак не могу разобраться, что кушает память. Подскажите пожалуйста, кто в теме, как исправить.

Манифест:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET"/>
    <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"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Base.Theme.AlertButtonEnd"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".location.LocationService"
            android:foregroundServiceType="location"
            android:enabled="true"/>
    </application>

</manifest>

MainActivity:

package com.stepa0751.alertbuttonend

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.stepa0751.alertbuttonend.databinding.ActivityMainBinding
import com.stepa0751.alertbuttonend.fragments.MainFragment
import com.stepa0751.alertbuttonend.fragments.MapFragment
import com.stepa0751.alertbuttonend.fragments.SettingsFrag
import com.stepa0751.alertbuttonend.utils.openFragment

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        onBottomNavClicks()
        openFragment(MainFragment.newInstance())
    }

//    Слушатель нажатия на кнопки меню внизу экрана
    private fun onBottomNavClicks(){
        binding.bNaw.setOnItemSelectedListener {
            when(it.itemId){
//        обычные фрагменты запускаются вот так:
                R.id.id_home -> openFragment(MainFragment.newInstance())
                R.id.id_map -> openFragment(MapFragment.newInstance())
//        а фрагменты с настройками вот так:
                R.id.id_settings -> openFragment(SettingsFrag())
            }
            true
        }
    }

}

MainFragment (из которого запускается сервис):

package com.stepa0751.alertbuttonend.fragments

import android.Manifest
import android.content.Context
import android.content.Intent
import android.location.LocationManager
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.MutableLiveData
import com.stepa0751.alertbuttonend.R
import com.stepa0751.alertbuttonend.databinding.FragmentMainBinding
import com.stepa0751.alertbuttonend.location.LocationService
import com.stepa0751.alertbuttonend.utils.DialogManager
import com.stepa0751.alertbuttonend.utils.TimeUtils
import com.stepa0751.alertbuttonend.utils.checkPermission
import com.stepa0751.alertbuttonend.utils.showToast
import java.util.Timer
import java.util.TimerTask


class MainFragment : Fragment() {
    private var timer: Timer? = null
    private var startTime = 0L
    val timeData = MutableLiveData<String>()
    private lateinit var pLauncher: ActivityResultLauncher<Array<String>>

    //    Создаем переменную binding
    private lateinit var binding: FragmentMainBinding
    private var isServiceRunning = false

    // Надуваем инфлейтер и получаем доступ ко всем элементов вью
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // Инициализируем binding c инфлейтером, который пришел нам в onCreateView см. выше
        binding = FragmentMainBinding.inflate(inflater, container, false)
        // полцчаем доступ ко всем элементам разметки
        return binding.root
    }

    //  Инициализируем все, что необходимо после создания вью
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        registerPermissions()
        checkServiceState()
        setOnClicks()
        updateTime()

    }

    //  Когда возвращаемся в вью проверяем доступность местонахождения в телефоне
    override fun onResume() {
        super.onResume()
        checkLocPermission()

    }

    private fun registerPermissions() {
        pLauncher = registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ) {

            if (it[Manifest.permission.ACCESS_FINE_LOCATION] == true) {
//                initOsm()
                checkLocationEnabled()
            } else {
                showToast("You have not given permission for location tracking. The app don't work!")

            }
        }
    }


    private fun checkServiceState() {
        isServiceRunning = LocationService.isRunning
        if (isServiceRunning) {
            binding.startStop.setImageResource(R.drawable.ic_alarm_red)
        }
    }


    //  Функция инициализации слушателя нажатий для ВСЕГО ВЬЮ
    fun setOnClicks() = with(binding) {
        val listener = onClicks()
        startStop.setOnClickListener(listener)
    }

    //  Функция сработки слушателя нажатий на этом вью
    private fun onClicks(): View.OnClickListener {
        return View.OnClickListener {
            when (it.id) {
                R.id.start_stop -> startStopService()
            }
        }
    }


    //  Обновление времени в tv_text с помощью обсервера, который слушает изменения в переменной timeData типа MutableLiveData<String>()
    private fun updateTime() {
        timeData.observe(viewLifecycleOwner) {
            binding.tvTime.text = it
        }
    }


    //  Запуск таймера, который (если что-то там есть) возьмет startTime из LocationService.startTime
    //  И запускать нужно в основном потоке, иначе значения в текст вью меняться не будут!
    private fun startTimer() {
        timer?.cancel()
        timer = Timer()
        startTime = LocationService.startTime
        timer?.schedule(object : TimerTask() {
            override fun run() {
                activity?.runOnUiThread {
                    timeData.value = getCurrentTime()
                }
            }
        }, 1000, 1000)
    }

    //  Функция выбора набора разрешений для более старых и новых версий андроид
    private fun checkLocPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            checkPermissionAfter10()
        } else {
            checkPermissionBefore10()
        }
    }


    //   Если больше или равно 10 версии андроида
    @RequiresApi(Build.VERSION_CODES.Q)
    private fun checkPermissionAfter10() {
        if (checkPermission(Manifest.permission.ACCESS_FINE_LOCATION)
            && checkPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
        ) {
//            initOsm()
            checkLocationEnabled()

        } else {
            pLauncher.launch(
                arrayOf(
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_BACKGROUND_LOCATION
                )
            )
        }
    }

    //   Если меньше 10 версии андроида
    private fun checkPermissionBefore10() {
        if (checkPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
//            initOsm()
            checkLocationEnabled()
        } else {
            pLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION))
        }
    }

    //    Определение включен ли GPS и вызов диалог менеджера, если выключен
    private fun checkLocationEnabled() {
        val lManager = activity?.getSystemService(Context.LOCATION_SERVICE) as LocationManager
        val isEnabled = lManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
        if (!isEnabled) {
            DialogManager.showLocEnableDialog(activity as AppCompatActivity,
                object : DialogManager.Listener {
                    override fun onClick() {
                        startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
                    }

                })
        } else {
            showToast("Location enabled")
        }
    }

    //  Здесь получаем время следования по маршруту, если приложение закрыли и работал только сервис
    //  От текущего времени отнимаем стартовое (все в миллисекундах!!!)
    private fun getCurrentTime(): String {
        return "Elapsed time: ${TimeUtils.getTime(System.currentTimeMillis() - startTime)}"
    }


    //  Функция запуска и остановки сервиса, в зависимости от состояния переменной isServiceRunning
    private fun startStopService() {
        if (!isServiceRunning) {
//            Здесь я подставил включение зеленой кнопки, зачем??????
            binding.startStop.setImageResource(R.drawable.ic_disalarm_green)
            startLocService()
        } else {
            activity?.stopService(Intent(activity, LocationService::class.java))
            binding.startStop.setImageResource(R.drawable.ic_disalarm_green)
            timer?.cancel()
        }
        isServiceRunning = !isServiceRunning
    }


    //  Запуск сервиса, в зависимости от нажатия на кнопку "старт" или "стоп"
    private fun startLocService() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            activity?.startForegroundService(Intent(activity, LocationService::class.java))
//            Здесь я подставил включение красной кнопки, зачем??????
            binding.startStop.setImageResource(R.drawable.ic_alarm_red)
        } else {
            activity?.startService(Intent(activity, LocationService::class.java))
        }
        binding.startStop.setImageResource(R.drawable.ic_alarm_red)
        LocationService.startTime = System.currentTimeMillis()
        startTimer()
    }


    companion object {

        @JvmStatic
        fun newInstance() = MainFragment()
    }
}

Бэкграунд сервис:

package com.stepa0751.alertbuttonend.location

import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.os.Build
import android.os.IBinder
import android.os.Looper
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import com.android.volley.Request
import com.android.volley.RequestQueue
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
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.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority.PRIORITY_HIGH_ACCURACY
import com.stepa0751.alertbuttonend.MainActivity
import com.stepa0751.alertbuttonend.R
import org.osmdroid.util.GeoPoint


class LocationService : Service() {

    //  Переменная для хранения последнего местоположения для измерения расстояния между старой и новой точками
    private var lastLocation: Location? = null

    //  Переменная для хранения высчитанного расстояния
    private var distance = 0.0f
    private var latit = 0.0f
    private var longit = 0.0f

    //    Эта переменная нужна для того, чтобы подключаться к провайдеру GPS и получать у него данные о местоположении
    private lateinit var locProvider: FusedLocationProviderClient
    private lateinit var locRequest: LocationRequest
    private lateinit var geoPointsList: ArrayList<GeoPoint>


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


    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startNotification()
        startLocationUpdates()
        isRunning = true
        return START_STICKY
    }

    override fun onCreate() {
        super.onCreate()
        geoPointsList = ArrayList()
        initLocation()

    }

    override fun onDestroy() {
        super.onDestroy()
        //  Переменную "работает" делаем ложь
        isRunning = false
        //  Отписываеся от обновлений местоположения
        locProvider.removeLocationUpdates(locCallBack)
    }


    //   Все что ниже - темный лес, но нужен он для запуска сервиса и отображения его в фореграунд.
    private fun startNotification() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val nChannel = NotificationChannel(
                CHANNEL_ID,
                "Location Service",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            val nManager = getSystemService(NotificationManager::class.java) as NotificationManager
            nManager.createNotificationChannel(nChannel)
        }
        val nIntent = Intent(this, MainActivity::class.java)
        val pIntent = PendingIntent.getActivity(
            this,
            10,
            nIntent,
            PendingIntent.FLAG_IMMUTABLE
        )
        val notification = NotificationCompat.Builder(
            this,
            CHANNEL_ID
        ).setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle("Alarm tracker running")
            .setContentIntent(pIntent).build()
        startForeground(99, notification)
    }

    //  Инициализация клиента доступа к подписке на местоположение
    private fun initLocation() {
        locRequest = LocationRequest.create().apply {
            interval = PreferenceManager.getDefaultSharedPreferences(baseContext)
                .getString("update_time_key", "5000")?.toLong() ?: 5000
            fastestInterval = 5000
            priority = PRIORITY_HIGH_ACCURACY
        }
        locProvider = LocationServices.getFusedLocationProviderClient(baseContext)
    }


    // Сюда приходит информация о местоположении в lResult
    private val locCallBack = object : LocationCallback() {
        override fun onLocationResult(lResult: LocationResult) {
            super.onLocationResult(lResult)
            try {
                val currentLocation: Location?
                currentLocation = lResult.lastLocation
                if (lastLocation != null && currentLocation != null) {
                    if ((currentLocation?.speed ?: 0.0f) > 0.5) distance += (currentLocation
                        ?: lastLocation)?.let { lastLocation?.distanceTo(it) }!!
                    geoPointsList.add(GeoPoint(currentLocation.latitude, currentLocation.longitude))
                    val locModel = LocationModel(
                        currentLocation.speed,
                        distance,
                        currentLocation.latitude.toFloat(),
                        currentLocation.longitude.toFloat(),
                        geoPointsList
                    )
                    latit = currentLocation.latitude.toFloat()
                    longit = currentLocation.longitude.toFloat()

                    sendLocData(locModel)
                    sendDataAndLocation()



                }
                lastLocation = currentLocation
            } catch (e: Exception) {
                println(e.message)
            }


        }
    }

    private fun sendLocData(locModel: LocationModel) {
        val i = Intent(LOC_MODEL_INTENT)
        i.putExtra(LOC_MODEL_INTENT, locModel)
        LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(i)
    }

    //  Функция запуска слушателя местоположения, для нее нужны несколько параметров:
    
    
    private fun startLocationUpdates() {
        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) return

        locProvider.requestLocationUpdates(
            //  Этот параметр получаем в initLocation
            locRequest,
            // Сюда будет приходить информация о нашем местоположении
            locCallBack,
            //  И лупер нужен, чтобы повторять поток запроса инфы о местоположении,
            // т.к. поток закрывается после выполнения всех команд в нем.
            Looper.myLooper()
        )
    }

    private fun sendDataAndLocation() {

            val user_id_pref = PreferenceManager.getDefaultSharedPreferences(baseContext)
                .getString("id_user_key", "0000")
            val token_pref = PreferenceManager.getDefaultSharedPreferences(baseContext)
                .getString("token_key", "")
            val chat_id_pref = PreferenceManager.getDefaultSharedPreferences(baseContext)
                .getString("chat_id_key", "")
            val queue = Volley.newRequestQueue(baseContext)
            val lat = latit.toString()
            val lon = longit.toString()
            val url =
                "https://api.telegram.org/bot${token_pref}/sendmessage?chat_id=-${chat_id_pref}&text=User ID:${user_id_pref};Alert button pressed!;${lat},$lon"

            val sRequest = StringRequest(
                Request.Method.GET,
                url, { response ->
                    Log.d("MyLog", "Response : ${response.subSequence(1, 10)}")
                                    },
                { Log.d("MyLog", "Error request: $it") }
            )
            queue.add(sRequest)


    }



    companion object {
        const val CHANNEL_ID = "channel_1"
        const val LOC_MODEL_INTENT = "loc_intent"
        var isRunning = false
        var startTime = 0L
    }
}

Ответы (0 шт):