I start a foreground service that downloads a file when users click on a button. What happens now is when I click again on the button, the thread inside service starts second time and I see in a notification that progress is updated for both threads (jumps presentae between threads).
How can I prevent starting the second thread in case the first one is running, how can I prevent startService() or onStartCommand() is being called if the service is still running?
class ForegroundService : Service() {
private val CHANNEL_ID = "ForegroundService Kotlin"
companion object {
fun startService(context: Context, message: String) {
val startIntent = Intent(context, ForegroundService::class.java)
startIntent.putExtra("inputExtra", message)
ContextCompat.startForegroundService(context, startIntent)
}
fun stopService(context: Context) {
val stopIntent = Intent(context, ForegroundService::class.java)
context.stopService(stopIntent)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val input = intent?.getStringExtra("inputExtra")
createNotificationChannel()
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
//start doing some work and update the notification
//when done calling stopSelf();
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service Kotlin Example")
.setContentText(input)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
return START_NOT_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID, "Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(NotificationManager::class.java)
manager!!.createNotificationChannel(serviceChannel)
}
}
}
Calling
buttonGoNext.setOnClickListener {
val intent = Intent(this, DownloadActivity::class.java)
startActivity(intent)
}
In your startService function you want to check if this service is already running. If the service is already running, you could just early return. There is multiple SO threads already regarding the problem of how to find out if a service is already running.
To have a little more control over your background work, I would suggest you familiarize yourself with Android's WorkManager. You could create a unique OneTimeWorkRequest with a KEEP policy, that would solve the problem as well.
Also, you should seperate your responsibilities in your code a little better. Your ForegroundService class currently takes care of too many things at once. You create notification channels, show notifications and probably also want to take care of the behaviour you described. It is generally good practice to seperate those concerns and move them to different classes. This also makes it more testable.
Related
I am writing an Android Wear app that uses a foreground service.
I have verified that the foreground service wokrs, and the code inside the service executes as needed (SensorEventListener) and I even use intents to display information in the UI.
However, the notification for the service is not posting in the list of notifications. I use a Galaxy Watch4 for testing.
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
broadcaster = LocalBroadcastManager.getInstance(this)
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, FLAG_IMMUTABLE)
val notificationChannel = NotificationChannel(foregroundChannelId, "Foreground Service", NotificationManager.IMPORTANCE_HIGH)
notificationChannel.lightColor = Color.BLUE
notificationChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(notificationChannel)
val notification = NotificationCompat.Builder(this, foregroundChannelId).setOngoing(true)
.setContentTitle("Monitoring sensors").setContentText("app_name is actively monitoring your biosensors.")
.setContentIntent(pendingIntent).setCategory(Notification.CATEGORY_SERVICE).setSmallIcon(R.mipmap.ic_launcher).build()
startForeground(1, notification)
prepareSensors()
return START_NOT_STICKY
}
There is no sticky notification in the notification panel.
I've tried removing the .setOngoing.
Maybe it was delayed in showing, as the operation was too quick ?
Try adding this:
builder.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
Sorry if i done anything wrong here its my first stackoverflow question :)
Having issue with foreground service in android, if dont stop foreground service before removing task my app doesnt work correctly after second enter.
There is a recyler view that keeps songs. When i play a song and kill the app while song is playing. When i enter the app again all song are duplicated and selecting a song crashes app.
override fun onPrepared(p0: MediaPlayer?) {
showNotification(R.drawable.ic_pause, 1F, true)
}
fun showNotification(playPauseButton: Int, playbackSpeed: Float, isPlaying: Boolean) {
val prevIntent: Intent = Intent(this,NotificationReceiver::class.java)
.setAction(ApplicationClass.ACTION_PREVIOUS)
val prevPendingIntent: PendingIntent = PendingIntent.getBroadcast(this,0,prevIntent, PendingIntent.FLAG_IMMUTABLE)
val pauseIntent: Intent = Intent(this,NotificationReceiver::class.java)
.setAction(ApplicationClass.ACTION_PLAY)
val pausePendingIntent: PendingIntent = PendingIntent.getBroadcast(this,0,pauseIntent, PendingIntent.FLAG_IMMUTABLE)
val nextIntent: Intent = Intent(this,NotificationReceiver::class.java)
.setAction(ApplicationClass.ACTION_NEXT)
val nextPendingIntent: PendingIntent = PendingIntent.getBroadcast(this,0,nextIntent, PendingIntent.FLAG_IMMUTABLE)
val destroyIntent: Intent = Intent(this,NotificationReceiver::class.java)
.setAction(ApplicationClass.ACTION_DESTROY)
val destroyPendingIntent: PendingIntent = PendingIntent.getBroadcast(this,0,destroyIntent, PendingIntent.FLAG_IMMUTABLE)
val notification = NotificationCompat.Builder(this, ApplicationClass.CHANNEL_ID_1)
.setSmallIcon(R.drawable.ic_baseline_music_note)
.setLargeIcon(notiArt)
.setContentTitle(nameGetter)
.setContentText(artistGetter)
.addAction(R.drawable.ic_previous ,"Previous", prevPendingIntent)
.addAction(playPauseButton ,"Play, Pause", pausePendingIntent)
.addAction(R.drawable.ic_next ,"Next", nextPendingIntent)
.addAction(R.drawable.ic_baseline_close ,"Destroy", destroyPendingIntent)
.setStyle(androidx.media.app.NotificationCompat.MediaStyle()
.setShowActionsInCompactView(0,1,2)
.setMediaSession(mediaSessionCompat.sessionToken))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.build()
startForeground(4, notification)
}
Tried to stop foreground like this and the problem fixed, i think that confirms problem is with foreground but i need foreground to work.
startForeground(4, notification);
stopForeground(false)
So i tried to stop foreground while killing app but this doesnt fix my problem
override fun onTaskRemoved(rootIntent: Intent?) {
stopSelf()
stopForeground(true)
super.onTaskRemoved(rootIntent)
}
Need to silence a notification that is shown by a foreground service. To silence the notification I have used many suggested solution from SO. But nothing seems to silence the annoying notification sound. It is silenced on SDK25. But on SDK 29 emulator it is not silenced. What can be the solution?
Code:
//service
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
createNotificationChannel()
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
val notification: Notification =
NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("app_name")
.setContentText("Updating database.")
.setSmallIcon(R.drawable.logo)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
thread() {
Thread.sleep(3000)
stopSelf()
}
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = SettingsHelper.KEY_NOTIFICATION_channel_name_service
val description = SettingsHelper.KEY_NOTIFICATION_CH_ID_SERVICE
val channel = NotificationChannel(
SettingsHelper.KEY_NOTIFICATION_CH_ID,
name,
NotificationManager.IMPORTANCE_NONE
)
channel.description = description
channel.setSound(null, null)
val manager = getSystemService(
NotificationManager::class.java
)
manager.createNotificationChannel(channel)
}
}
Could you use setSilent() from NotificationCompat.Builder
If true, silences this instance of the notification, regardless of the sounds or vibrations set on the notification or notification channel. If false, then the normal sound and vibration logic applies. Defaults to false.
Note, that if you are changing notification settings, I recommend you to uninstall and re-install the app everytime since it might not be updated the settings you set in the new run.
I am starting a background service which receives data in the background, so for this, I have used android foreground service, the service works perfectly in some mobiles (MI A2 Stock Android), but in some mobiles when I remove the application from background tray the service gets destroyed.
class MyService : Service() {
private val CHANNEL_ID = "ForegroundService"
companion object {
fun stopService(context: Context) {
val stopIntent = Intent(context, MyService::class.java)
context.stopService(stopIntent)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// I get some data from intent
// My code which runs in the background
createNotificationChannel()
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("App is syncing")
.setContentText("")
.setPriority(2)
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setContentIntent(pendingIntent)
.build()
startForeground(190, notification)
return START_NOT_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID, "Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(NotificationManager::class.java)
manager!!.createNotificationChannel(serviceChannel)
}
}
}
This how I start the service
val serviceIntent = Intent(this, MyService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent)
} else {
startService(serviceIntent)
}
So my question is how can I make my service running even when the APP is removed from the background tray.
Do these things
1) Restart the service when app is closed by overriding this method in
your service class , copy and paste this
override fun onTaskRemoved(rootIntent: Intent?) {
val restartServiceIntent = Intent(applicationContext, this.javaClass)
restartServiceIntent.setPackage(packageName)
val restartServicePendingIntent = PendingIntent.getService(
applicationContext,
1,
restartServiceIntent,
PendingIntent.FLAG_ONE_SHOT
)
val alarmService =
applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmService[AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000] =
restartServicePendingIntent
super.onTaskRemoved(rootIntent)
}
2) Change this START_NOT_STICKY to START_STICKY
3) Ask user to enable autorun permission from settings , this feature
is provided in custom os like mini devices, vivo,huawei and oppo etc.
4) and you forgot to restart the service on device boot up like you
need to use a broadcast receiver to restart service when the device
restarts
I'm currently building an app that starts a session by sending a request to the backend. After that, the app has to send a heartbeat request every 4.5 minutes. If the app does not send a request to the backend after 4.5 minutes since the last successful request, the session will get terminated. The heartbeat request can be sent earlier which also means that the next heartbeat has to be sent 4.5 minutes after that request.
Once the user has started the session, he should be able to put the app to the background to use the device for other things.
I'm struggling with coming up with a solution that works with the background restrictions (doze mode, etc).
I'm currently running the app with a foreground service. But the requests stop after a couple of minutes if I don't use the device actively. I tried the WorkManager and the AlarmManager. But the requests keep getting delayed.
I played around with REQUEST_IGNORE_BATTERY_OPTIMIZATIONS and this seems to work but I don't want to use that approach since Google seems to really dislike apps using this permission.
I created a test app to play around with different approaches. Maybe I'm doing something completely wrong?
Service:
class MainService : Service() {
private lateinit var wakeLock: PowerManager.WakeLock
override fun onBind(intent: Intent?): IBinder? = null
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= 26) {
val appName = getString(R.string.app_name)
val channelName = "$appName channel name"
val channelImportance = NotificationManager.IMPORTANCE_HIGH
val channelDescription = "$appName channel description"
createNotificationChannel(this, NOTIFICATION_CHANNEL_ID, channelName, channelImportance, channelDescription)
}
val notification = createOngoingNotification(this, NOTIFICATION_REQUEST_CODE, R.mipmap.ic_launcher_round, "Content Text")
startForeground(1000, notification)
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
acquire(1 * 60 * 60 * 1000L)
}
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.action?.let {
if (it == "Heartbeat") {
val v = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE))
}
}
}
setNext()
return START_STICKY
}
private fun setNext() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val intent = Intent(applicationContext, MainService::class.java)
intent.action = "Heartbeat"
val pendingIntent = PendingIntent.getService(applicationContext, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5 * 1000, pendingIntent)
}
}
override fun onDestroy() {
stopForeground(true)
wakeLock.release()
super.onDestroy()
}
companion object {
const val REQUEST_CODE = 101
const val NOTIFICATION_REQUEST_CODE = 100
const val NOTIFICATION_CHANNEL_ID = "notification_channel_id"
fun createOngoingNotification(context: Context, requestCode: Int, icon: Int, text: String): Notification {
val contentIntent = Intent(context, MainActivity::class.java)
.setAction(Intent.ACTION_MAIN)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
val contentPendingIntent = PendingIntent.getActivity(context, requestCode, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT)
return NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setOngoing(true)
.setSmallIcon(icon)
.setContentTitle("Test Notification")
.setContentText(text)
.setContentIntent(contentPendingIntent)
.build()
}
#RequiresApi(api = 26)
fun createNotificationChannel(context: Context,
id: String, name: String, importance: Int,
description: String) {
val channel = NotificationChannel(id, name, importance)
channel.description = description
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
}
I think that foreground service may be stopped as you are not executing anything inside it after you set the alarm. Foreground service keeps running if you are using it, so for making it work periodically i would put an observable that emits a value every 5 seconds for example. You only need the one emmited after 4.5 minutes but that will keep the foreground service active until you need it. Using rxjava:
Observable.intervalRange( 0 , 54, 0, 5, TimeUnit.SECONDS )
.doOnNext{
//You may not need this
}
.doOnComplete {
//heartbeat
//start this observable again for other 4.5 minutes
}
You will emit a value every 5 seconds 54 times. 54x5 = 270 seconds (what is 4.5 minutes)