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"/>
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" />
Check if your app has restricted data usage and battery sever inside application settings.
It is preferable to use WorkManager if your min sdk is 14+. It will save you from a lot of hassles of using and managing a foreground service.
https://developer.android.com/topic/libraries/architecture/workmanager
Sending location updates is one of the best use case of WorkManager.
Related
I'm running a foreground service when the app goes into background and sending latitude and longitude to a server every 10 seconds , the issue is that the foreground service keeps only sending data when on foreground or in background it keeps sending data until the phone screen turns off and after a while it stops working and the foreground service is cleared .. i'm not really sure what to do about this case , i have also whitelisted the app for battery optimization but did not work ..
Starting service from my location code
locationCallback = object : LocationCallback() {
override fun onLocationResult(location: LocationResult) {
super.onLocationResult(location)
val latitude = location.lastLocation.latitude
val longitude = location.lastLocation.longitude
val serviceIntent = Intent(this#MainActivity,LocationService::class.java)
serviceIntent.putExtra("latitude",latitude)
serviceIntent.putExtra("longitude",longitude)
startService(serviceIntent)
}
My Service and sending data into server
#AndroidEntryPoint
class LocationService : Service() {
#Inject
lateinit var sharedPreferences : SharedPreferences
override fun onBind(intent: Intent?): IBinder? = null
#SuppressLint("NewApi")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val latitude = intent?.getDoubleExtra("latitude",0.0)
val longitude = intent?.getDoubleExtra("longitude",0.0)
postValuesToServer(latitude!!, longitude!!)
showNotification(latitude,longitude)
return START_STICKY
}
#RequiresApi(Build.VERSION_CODES.M)
private fun showNotification(latitude: Double, longitude: Double) {
val intent = Intent(this,MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this,0,intent,PendingIntent.FLAG_IMMUTABLE)
val notificationCompat = NotificationCompat.Builder(this,Utils.NOTIFICATION_ID)
.setContentTitle("Longitud : $longitude")
.setContentText("Latitud : $latitude")
.setSmallIcon(R.drawable.ic_baseline_circle_notifications_24)
.setContentIntent(pendingIntent)
.build()
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
startForeground(1,notificationCompat)
}
else {
NotificationManagerCompat.from(this).notify(1,notificationCompat)
}
}
Service Added To Manifest File
<service android:name=".services.LocationService"
android:foregroundServiceType="location|dataSync"/>
I am getting issue regarding foreground service is that some time foreground being killed by os so should i resolve Service class issue or move to workmanager . i have completed the application and only getting a single os issue suggestion required thanks .
// this is the service class
class SmsService : Service() {
private var wakeLock: PowerManager.WakeLock? = null
private lateinit var smsScheduler : SmsScdeduler
private var isServiceStarted = false
private var notificationBuilder : NotificationCompat.Builder?=null
private var serviceScope = CoroutineScope(Dispatchers.Main)
private var notificationManager : NotificationManager?=null
fun isServiceRunning():Boolean{
return isServiceStarted }
companion object{
const val MESSAGE_ID: String ="messageId"
const val USER_ID:String ="userId"
const val DELIVERED: String ="sb.app.messageschedular.sms_schedulers.DELIVERYKEY"
const val ADD_SERVICE = "sb.spp.message_scheduler.add"
const val DELETE_SERVICE ="sb.spp.message_scheduler.delete"
const val Add_kEY ="ADD_KEY"
const val SENT ="sb.app.messageschedular.sms_schedulers.SENT"
private const val channelId = "default_notification_channel_id"
private const val NOTIFICATION_ID =1995L
}
/************* Delivery BroaddCast ENd **************/
override fun onCreate() {
super.onCreate()
smsScheduler = SmsScdeduler.getInstance(this )
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
notificationBuilder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(sb.app.messageschedular.R.mipmap.ic_launcher)
.setContentTitle("Message Scheduled")
// .setContentIntent(pendingIntent)
.setAutoCancel(true)
}
#RequiresApi(Build.VERSION_CODES.O)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
showNotification(NOTIFICATION_ID)
wakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EndlessService::lock").apply {
acquire()
}
}
println("intent ${intent }")
isServiceStarted =true
if(intent!=null) {
val action = intent?.action
val sms = intent?.getParcelableExtra<Sms>(Add_kEY)
schedule(sms!!)
}else{
serviceScope.launch(Dispatchers.IO) {
smsScheduler.reSchedule()
}
}
return START_STICKY }
#RequiresApi(Build.VERSION_CODES.O)
private fun showNotification(messageid:Long ) {
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId, "test1", NotificationManager.IMPORTANCE_DEFAULT)
channel.description = "test otificaiton"
notificationManager?.createNotificationChannel(channel)
}
this.startForeground(messageid.toInt() ,notificationBuilder!!.build())
}
override fun onBind(intent: Intent?): IBinder? {
return LocalBinder() }
inner class LocalBinder : Binder(){
fun getService(): SmsService = this#SmsService
}
override fun onDestroy() {
super.onDestroy()
isServiceStarted =false
if( serviceScope.isActive){
println("Service Scope is cancelled ")
serviceScope.cancel() }
}
}
fun finish() {
println("service finished ")
println("Stop self")
println("Stop foreground service")
stopForeground(true )
println("service state false ")
isServiceStarted =false
stopSelf()
}
}
// calling service
fun scheduleService(sms: Sms) {
if(!mService.isServiceRunning()){
val intent = Intent(this , SmsService::class.java)
intent.action = SmsService.ADD_SERVICE
intent.putExtra(SmsService.Add_kEY, sms)
ContextCompat.startForegroundService(this.applicationContext,intent)
}
/// Permission of Service
<service android:name=".service.SmsService"
android:foregroundServiceType="dataSync"
/>
I am creating a foreground service that creates a geofence area in user local, and when the user exit from the geofence area i create another geofence area, again in user local.
I have a low RAM Quantity so i tested on my phone android 9, and with the fake gps app, it works fine, but when i use the real gps don't. I defined the high accuracy on android phone configuration and still doesn't work. Finally i had decided to put fire on my pc and use the emulator to control the phone location, and yet doesn't work. Not even de initial trigger is called.
So what is the difference between the fake gps and de real gps that defines if my code will work or not
class LocationForegroundService : LifecycleService() {
private lateinit var geofencingClient: GeofencingClient
private lateinit var locationClient: FusedLocationProviderClient
private lateinit var pendingBroadcastIntent: PendingIntent
private var geoId = ""
companion object {
private var count = 0
private var geoAlreadyInitialized = false
private const val TRACKING_CHANNEL = "tracking_channel"
private const val TRACKING_NOTIFICATION_ID = 1
private lateinit var mContext: Context
private val exitFromGeofence = MutableLiveData(false)
var isTrackingRider: Boolean = false
private set
fun startService(context: Context) {
mContext = context
val startIntent = Intent(context, LocationForegroundService::class.java)
ContextCompat.startForegroundService(context, startIntent)
isTrackingRider = true
}
fun stopService(context: Context) {
val stopIntent = Intent(context, LocationForegroundService::class.java)
context.stopService(stopIntent)
isTrackingRider = false
}
class GeofenceReceiver : BroadcastReceiver() {
#SuppressLint("MissingPermission")
override fun onReceive(context: Context?, intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
return
}
when (geofencingEvent.geofenceTransition) {
GEOFENCE_TRANSITION_EXIT -> {
geofencingEvent.triggeringGeofences.forEach {
context?.showShortToast("EXIT geo ${it.requestId}")
}
exitFromGeofence.value = true
}
GEOFENCE_TRANSITION_ENTER -> {
geofencingEvent.triggeringGeofences.forEach {
context?.showShortToast("Enter geo ${it.requestId}")
}
}
GEOFENCE_TRANSITION_DWELL -> {
context?.showShortToast("dwell geo blabla")
}
}
}
}
}
override fun onCreate() {
super.onCreate()
locationClient = LocationServices.getFusedLocationProviderClient(mContext)
geofencingClient = LocationServices.getGeofencingClient(mContext)
val intent = Intent(mContext, GeofenceReceiver::class.java)
pendingBroadcastIntent = PendingIntent.getBroadcast(
mContext,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
createObservers()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
createNotificationChannel()
// ... creates notification for foreground
updateLocation()
return START_NOT_STICKY
}
#SuppressLint("MissingPermission")
private fun createNewGeofence(location: Location) {
removeActualGeofence()
geoId = "geo_id_$count"
val geofence = getGeofence(location)
val geofencingRequest = getGeofenceRequest(geofence)
initGeofence(geofencingRequest)
}
#SuppressLint("MissingPermission")
private fun initGeofence(geofencingRequest: GeofencingRequest) {
geofencingClient.addGeofences(geofencingRequest, pendingBroadcastIntent)
.addOnSuccessListener {
geoAlreadyInitialized = true
}
.addOnFailureListener {
}
}
#SuppressLint("MissingPermission")
private fun updateLocation() {
locationClient.lastLocation.addOnSuccessListener { location ->
createNewGeofence(location)
}
}
private fun getGeofenceRequest(geofence: Geofence) =
GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(geofence)
.build()
private fun getGeofence(location: Location) = Builder()
.setCircularRegion(location.latitude, location.longitude, 200F)
.setRequestId(geoId)
.setTransitionTypes(GEOFENCE_TRANSITION_ENTER or GEOFENCE_TRANSITION_EXIT)
.setExpirationDuration(NEVER_EXPIRE)
.setNotificationResponsiveness(0)
.build()
private fun removeActualGeofence() {
geofencingClient.removeGeofences(mutableListOf(geoId))
.addOnFailureListener {}
.addOnSuccessListener {
if (geoAlreadyInitialized) count++
}
}
private fun createObservers() {
exitFromGeofence.observe(this) {
updateLocation()
}
}
private fun createNotificationChannel() {
// ...create notification channel
}
}
The problem is that i was trying to use locationClient.lastLocation to get the user actual location, and when there was no last location to get it fail on create the geofence o the right place, just when i use the fake gps app the phone recorded a location and it works, so i changed to use requestLocationUpdates to get the actual locale.
Currently, I need a bound (Music)Service, because I need to interact with it. But I also want it to not be stopped, even when all components have unbound themselves.
My service code:
class ServicePlayer : LifecycleService() {
var mediaPlayer: MediaPlayer? = null
var notificationManager: NotificationManager? = null
var notificationBuilder: NotificationCompat.Builder? = null
private val mBinder: IBinder = PlayerBinder()
private val NOTIFICATION_ID = 1111
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_REDELIVER_INTENT
}
inner class PlayerBinder : Binder() {
val service: ServicePlayer
get() = this#ServicePlayer
}
override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
return mBinder
}
override fun onCreate() {
super.onCreate()
mediaPlayer = MediaPlayer()
mediaPlayer!!.setOnCompletionListener(this)
mediaPlayer!!.setOnBufferingUpdateListener(this)
mediaPlayer!!.setOnErrorListener(this)
val filter = IntentFilter()
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED)
filter.addAction(Intent.ACTION_SCREEN_ON)
registerReceiver(receiver, filter)
}
override fun onDestroy() {
super.onDestroy()
mediaPlayer!!.reset()
mediaPlayer!!.release()
Log.i("DESTROY SERVICE", "destroy")
unregisterReceiver(receiver)
}
fun play(trackIndex: Int, tracks: ArrayList<Track>?) {
...
val intent = Intent(BUFFERING)
this#ServicePlayer.sendBroadcast(intent)
}
fun pause() {
if (mediaPlayer!!.isPlaying) {
mediaPlayer!!.pause()
PlayerLiveData.isPlaying.value = false
val intent = Intent(UPDATE_UI)
this#ServicePlayer.sendBroadcast(intent)
//Show notification
CoroutineScope(Dispatchers.Default).launch {
showNotification()
}
}
}
private fun hideNotification() {
notificationManager!!.cancel(NOTIFICATION_ID)
stopForeground(true)
}
private fun showNotification() {
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val CHANNEL_ID = "controls_channel_id"
val CHANNEL_NAME = "Play tracks"
val channel = NotificationChannel(CHANNEL_ID,CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW)
...
val mMediaSession = MediaSessionCompat(applicationContext, getString(R.string.app_name))
mMediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
notificationManager!!.createNotificationChannel(channel)
notificationBuilder = NotificationCompat.Builder(applicationContext)
.setChannelId(CHANNEL_ID)
.setContentText(artistText)
.setContentTitle(track.title)
...
} else {
notificationBuilder = NotificationCompat.Builder(applicationContext)
...
notificationBuilder!!
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(contentIntent)
.setCustomContentView(remoteSmallViews)
.setCustomBigContentView(remoteViews)
}
CoroutineScope(Dispatchers.Default).launch {
val notification = notificationBuilder!!.build()
startForeground(NOTIFICATION_ID, notification)
val notificationTarget = NotificationTarget(
applicationContext
, R.id.imgThumb, remoteViews
, notification, NOTIFICATION_ID
)
...
lifecycleScope.launch {
val request = ImageRequest.Builder(applicationContext)
.data(thumb)
.error(R.drawable.placeholder_song)
.placeholder(R.drawable.placeholder_song)
.build()
val drawable = imageLoader.execute(request).drawable
val bitmap = (drawable as BitmapDrawable).bitmap
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationBuilder!!.setLargeIcon(bitmap)
val notification = notificationBuilder!!.build()
notificationManager!!.notify(NOTIFICATION_ID,notification)
//Start Foreground service
startForeground(NOTIFICATION_ID, notification)
}
}
}
}
}
Manifest file declaration:
<service android:name=".services.ServicePlayer" android:enabled="true" android:exported="true"/>
Using service in activity
class MainActivity : AppCompatActivity() {
lateinit var binding: MainActivityBinding
private lateinit var audioPlayerService: ServicePlayer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = Intent(this, ServicePlayer::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
binding = DataBindingUtil.setContentView(this, R.layout.main_activity)
binding.lifecycleOwner = this
binding.viewmodel = mainViewModel
}
private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName) {
// audioPlayerService = null;
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
audioPlayerService = (service as ServicePlayer.PlayerBinder).service
if (audioPlayerService.trackIndex !== -1) {
//updatePlaybackUI()
}
}
}
}
How can I keep my service running in background even after activity destroyed. I refer few threads of StackOverflow but they are not helpful.
Use Service instead LifecycleService as parent class.
Add partial wake lock start and stop calls to onCreate and onDestroy methods respectively.
private val powerManager
get() = (this.getSystemService(Context.POWER_SERVICE) as PowerManager)
private var wakeLock: PowerManager.WakeLock? = null
private fun startWakeLock() {
if (wakeLock == null) {
wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"${packageName}:wakeLock"
)
wakeLock?.acquire()
}
}
private fun stopWakeLock() {
if (wakeLock?.isHeld == true) {
wakeLock?.release()
wakeLock = null
}
}
Add the following tag to service at mainfest
android:foregroundServiceType="mediaPlayback"
You should start service as foreground from the activity
A bound service stops once every client unbinds from it, and that happens automatically when the client (your Activity) is destroyed
If your client is still bound to a service when your app destroys the client, destruction causes the client to unbind. It is better practice to unbind the client as soon as it is done interacting with the service. Doing so allows the idle service to shut down.
If you want the service to just keep running, a Started Service will do that. You can still bind to it, but it won't stop until you explicitly tell it to stop and there are no clients bound.
Honestly though, if you're making some kind of media player, you'll probably want to use the MediaBrowserServiceCompat framework. It allows you to create a service that plays nice with MediaBrowser (which does the binding, among other things) and lets you use a MediaSession to get a media notification with controls and all that.
A few links about that stuff:
MediaBrowserServiceCompat and the modern media playback app by Ian Lake from the Android team
Background Audio in Android With MediaSessionCompat - Java but gets into a lot of the nonsense you'll have to wrangle
Developer docs about building media apps - a few sections here and it's all kinda spread out, I feel like the other links give a better overview
If you don't care about any of that then startService/startForegroundService (or ContextCompat#startForegroundService) will get you a service that just runs, but those links might give you some pointers about stuff
I made a Service that is actually a simple background counter.
It just pluses 1 to a last number and then it goes to UI.
My previous problem was about the fact that Handler() sometimes worked very slow when smartphone was turned off or if it wasn't charging. Recently I found the same problem in this forum.
I added PowerManager.WakeLock to my Service and everything worked fine...
But I decided to test it for a longer time and started the app simultaneously on three smartphones and leave them for about an hour and a half. When I returned I have seen a complete difference between three of them.
The first shows 5100 (1 h 25 mins), the second - 2800 (46 mins) and the third - 5660 (1 h 34 mins).
I was pretty sure that wakelock will do the job correctly but now I don't know what happened there.
Here is a code of my Service:
class Timer_Service : Service() {
companion object {
val PARAM_OUT_MSG = "0"
}
var i = 0
private lateinit var wakeLock: PowerManager.WakeLock
private lateinit var mHandler: Handler
private lateinit var mRunnable: Runnable
override fun onBind(p0: Intent?): IBinder? {
TODO("not implemented")
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"ExampleApp:Wakelock"
)
wakeLock.acquire()
val broadcastIntent = Intent()
broadcastIntent.action = "com.example.infocell.action.RESPONSE"
mHandler = Handler()
mRunnable = Runnable {
showOrderNumber()
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT)
broadcastIntent.putExtra(PARAM_OUT_MSG, i.toString())
sendBroadcast(broadcastIntent)
}
mHandler.postDelayed(mRunnable, 1000)
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
mHandler.removeCallbacks(mRunnable)
}
private fun showOrderNumber() {
i += 1
mHandler.postDelayed(mRunnable, 1000)
}
}
Manifest also contains <uses-permission android:name="android.permission.WAKE_LOCK" />
Finally after various tests I got the most precise way to make a simple counter. Instead of relatively reliable Handle() method I would recommend to use Timer(). It worked absolutely equal on all of my four smartphones. Wakelock is also required for that. I would also test JobScheduler() and CountDownTimer() for getting all testing results but I am glad with timer so far.
I will share my code if someone is looking for solution for such tasks.
class Timer_Service : Service() {
companion object {
val PARAM_OUT_MSG = "0"
}
var i = 0
private lateinit var wakeLock: PowerManager.WakeLock
private lateinit var timer: Timer
override fun onBind(p0: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"ExampleApp:Wakelock"
)
wakeLock.acquire()
val broadcastIntent = Intent()
broadcastIntent.action = "com.example.infocell.action.RESPONSE"
timer = Timer()
val task = object : TimerTask() {
override fun run() {
if (Trigger.getTrigger() == 0){
showOrderNumber()
// bring 'i' value to main activity
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT)
broadcastIntent.putExtra(PARAM_OUT_MSG, i.toString())
sendBroadcast(broadcastIntent)
}
}
}
timer.schedule(task,0, 1000)
return START_STICKY
}
override fun onCreate() {
super.onCreate()
var notification = createNotification()
startForeground(1, notification)
}
private fun createNotification(): Notification {
val notificationChannelId = "ENDLESS SERVICE CHANNEL"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
val channel = NotificationChannel(
notificationChannelId,
"My Service",
NotificationManager.IMPORTANCE_HIGH
).let {
it.description = "Service channel"
it.enableLights(true)
it.lightColor = Color.RED
it.enableVibration(true)
it.vibrationPattern = longArrayOf(100)
it
}
notificationManager.createNotificationChannel(channel)
}
val pendingIntent: PendingIntent = Intent(this, MainActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent, 0)
}
val builder: Notification.Builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) Notification.Builder(
this,
notificationChannelId
) else Notification.Builder(this)
return builder
.setContentTitle("My Service")
.setContentText("Endless service working...")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setTicker("Ticker text")
.setPriority(Notification.PRIORITY_HIGH) // for under android 26 compatibility
.build()
}
override fun onDestroy() {
super.onDestroy()
// Trigger is a separate kotlin class with variables
if (Trigger.getTrigger() == 1){
timer.cancel()
timer.purge()
}
}
private fun showOrderNumber() {
i += 1
}
}