Force stop loop function in IntentService - android

There a function in my intent service that works like a countdown. It is called counter.
What should be added to IntentService or directly into counter to stop this loop after some action in MainActivity?
class IntentServiceExample : IntentService("Loop_test") {
private val CHANNEL_ID = "ForegroundService Kotlin"
companion object {
val PARAM_OUT_MSG = "None"
}
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)
}
}
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 = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service Kotlin Example")
.setContentText("kylsha")
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
return super.onStartCommand(intent, flags, startId)
}
override fun onHandleIntent(p0: Intent?) {
Toast.makeText(this,"Service started",Toast.LENGTH_LONG).show()
val broadcastIntent = Intent()
broadcastIntent.action = "com.example.intenttest.action.RESPONSE"
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT)
sendBroadcast(broadcastIntent)
counter(broadcastIntent)
}
fun counter(bc: Intent){
for (i in 1..100){
bc.putExtra(PARAM_OUT_MSG, i.toString())
Thread.sleep(1000)
d("number", i.toString())
sendBroadcast(bc)
}
}
override fun onDestroy() {
super.onDestroy()
stopSelf()
}
}

create a variable in the class.
Create a setter to set the variable true.
In you rcounter routine, check for the variable being set.
private val cancelCounter = false
public fun setToCancel() {
cancelCounter = true
}
/*Stuff*/
fun counter(bc: Intent){
for (i in 1..100){
if (cancelCounter) {
cancelCounter = false
break
}
bc.putExtra(PARAM_OUT_MSG, i.toString())
Thread.sleep(1000)
d("number", i.toString())
sendBroadcast(bc)
}
}
You may not have direct access to the object from main - if not then you should create this class with a singleton pattern ;)
I don't code in kotlin enough to now the "right way" to do it, but some links to the right way:
https://blog.mindorks.com/how-to-create-a-singleton-class-in-kotlin
https://medium.com/swlh/singleton-class-in-kotlin-c3398e7fd76b
Both of these links have some information about why they make the decisions they make in the structure pattern, and some of how the code behind for the implementations works too ;)

For someone who also learn Kotlin I will post my solution as well. It looks pretty simple, however, there probably could be more solutions.
I created a simple Kotlin object like:
object Trigger {
var triggerStop = 0
fun getTrigger(): Int{
return triggerStop
}
}
As you can see variable triggerStop can be changed and called with function getTrigger()
So I added this object into MainActivity to buttons' setOnClickListeners:
class MainActivity : AppCompatActivity() {
lateinit var i:Intent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
i = Intent(this, IntentServiceExample::class.java)
buttonStart.setOnClickListener{
Trigger.triggerStop = 0 // this variable will be checked in IntentService
startService(i)
}
buttonEnd.setOnClickListener{
Trigger.triggerStop = 1 // this variable will be checked in IntentService
stopService(i)
}
}
}
Then I put this object into my IntentService. In a loop that I want to be stopped by user interaction I put a check like in #Watachiaieto's answer.
fun counter(bc: Intent){
for (i in 1..100){
val stopIt = Trigger.getTrigger() // get trigger value
if (stopIt == 1) {
break
}
bc.putExtra(PARAM_OUT_MSG, i.toString())
Thread.sleep(1000)
sendBroadcast(bc)
}
}

Related

Android Foreground service is not starting with jetpack compose

I'm creating a countdown timer and I want to notify the user all the time that the countdown timer runs. Therefore I have created a service which is started by a ViewModel. I use Hilt for dependency injection as I want to inject the service into the ViewModel. Additionally the UI library is jetpack compose. Following is my approach.
This is my service.
#AndroidEntryPoint
class TimerService: Service(){
override fun onBind(p0: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Notifications.createNotification(applicationContext)
return super.onStartCommand(intent, flags, startId)
}
}
This is how notifications are created.
object Notifications {
private var notificationId = UUID.randomUUID().hashCode()
fun createNotification(context: Context){
val notification = NotificationCompat.Builder(context, "ChannelId")
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("Timer started")
.setContentText("Timer started and running...")
.build()
NotificationManagerCompat.from(context).notify(notificationId, notification)
}
fun createNotificationChannel(context: Context){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val name = "timerNotifyChannel"
val description = "Timer Notification Channel"
val importance = NotificationManager.IMPORTANCE_HIGH
// The notification channel
val channel = NotificationChannel("ChannelId", name, importance).apply {
description
}
val notificationManager : NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
}
And a manager for starting and stopping the service.
class TimerServiceManager #Inject constructor(
#ApplicationContext private val applicationContext: Context,
){
private val serviceIntent = Intent(applicationContext, TimerService::class.java)
fun startTimerService(){
ContextCompat.startForegroundService(applicationContext, serviceIntent)
}
fun stopTimerService(){
applicationContext.stopService(serviceIntent)
}
}
The Application.
#HiltAndroidApp
class TimerApp: Application(){}
The TimerViewModel, which starts the service whenever the service is started.
#HiltViewModel
class TimerViewModel #Inject constructor(
private val timerServiceManager: TimerServiceManager,
): ViewModel() {
//...
fun startcountDown(){
//...
countDownTimer = object : CountDownTimer(...){...}
countDownTimer?.start()
timerServiceManage.startTimerService()
}
private fun cancelTimer(){
countDownTimer?.cancel()
_isRunning.postValue(false)
timerServiceManager.stopTimerService()
}
}
And the MainActivity
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
// private val timerViewModel by viewModels<TimerViewModel>()
private val timeViewModel by viewModels<TimeViewModel>()
// val timeViewModel: TimeViewModel = ViewModelProvider(checkNotNull(LocalViewModelStoreOwner.current))[TimeViewModel::class.java]
override fun onCreate(savedInstanceState: Bundle?) {
//...
val timerViewModel = hiltViewModel<TimerViewModel>()
callUI(timerViewModel = timerViewModel, timeViewModel, localConfig = LocalConfiguration.current)
}
}
The problem is when I debug I can see that it calls the ContextCompact.startForegrounService(...) withing theTimerServiceManager.startTimerService() function. But it doesn't start the service. I say it doesn't start because when I put a break point in the onCreated(...) method in the TimerService it's never reached. Why is this failing? What's wrong?
After weeks of trial-and-errors I could show notifications from my app. I was being dumb at the first place for not allowing the application to access notifications in the emulator. So if you're also having this issue make sure your application has access to the notifications.
However I have also made some changes. I don't know whether the app working because of these changes or not.
I removed the Notificationobject and then add that functionality to the TimerService.
#AndroidEntryPoint
class TimerService: Service() {
private lateinit var notificationManager: NotificationManagerCompat
private lateinit var notification: Notification
override fun onCreate() {
// the notification channel creates when the Service is created
super.onCreate()
notificationManager = NotificationManagerCompat.from(this)
createNotificationChannel()
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// when the service starts, a notification will be created
notification = NotificationCompat.Builder(this, TIMER_SERVICE_NOTIFICATION_CHANNEL_ID)
.setContentTitle("Timer service")
.setContentText("Timer running")
.setSmallIcon(R.drawable.ic_launcher_background)
.setOngoing(true) // an ongoing notification means can't dismiss by the user.
.setOnlyAlertOnce(true)
.build()
startForeground(TIMER_SERVICE_NOTIFICATION_ID, notification)
return START_STICKY
}
private fun createNotificationChannel(){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val serviceChannel = NotificationChannel(
TIMER_SERVICE_NOTIFICATION_CHANNEL_ID,
getString(R.string.app_name),
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(serviceChannel)
}
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onDestroy() {
super.onDestroy()
stopForeground(STOP_FOREGROUND_REMOVE)
}
override fun onBind(p0: Intent?): IBinder? = null // We don't need a binder
companion object {
public const val TIMER_SERVICE_NOTIFICATION_CHANNEL_ID = "TimerServiceChannel"
public const val TIMER_SERVICE_NOTIFICATION_ID = 69
}
}

Using startForeground Service not keeping after Activity destroyed

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

Handler() works slower in Service when sreen is off

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
}
}

Pass data to Service in Kotlin

I have an app that uses Service class to perform task in foreground.
This service also contains a Handler object to run same function multiple times. I want to change attributes in my activity_main.xml while functions are running in Service. For example when function calculates something in Service the result prints in TextView.
How it would be correct access activity_main's objects to retrieve and change their values and attributes?
Here is what I have:
MainActivity.kt:
class MainActivity : AppCompatActivity() {
private var notificationManager: NotificationManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
buttonStart.setOnClickListener{
buttonStart.isEnabled = false
buttonStop.isEnabled = true
IdListener.startService(this, "Foreground Service is running...")
}
}
}
IdListener.kt:
class IdListener : Service() {
private val CHANNEL_ID = "ForegroundService Kotlin"
private lateinit var mainHandler: Handler
private lateinit var mRunnable: Runnable
companion object {
fun startService(context: Context, message: String) {
val startIntent = Intent(context, IdListener::class.java)
startIntent.putExtra("inputExtra", message)
ContextCompat.startForegroundService(context, startIntent)
}
fun stopService(context: Context) {
val stopIntent = Intent(context, IdListener::class.java)
context.stopService(stopIntent)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
mainHandler = Handler()
mRunnable = Runnable { showRandomNumber(tm) }
mainHandler.postDelayed(mRunnable, 1000)
val input = intent?.getStringExtra("inputExtra")
createNotificationChannel()
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service Kotlin Example")
.setContentText(input)
.setSmallIcon(R.drawable.ic_notofication)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
return START_NOT_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
mainHandler.removeCallbacks(mRunnable)
}
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)
}
}
/// function in which I want elements from activity_main.xml to be changed
fun showRandomNumber(manager: TelephonyManager){
myTextView.text = "Working..."
mainHandler.postDelayed(mRunnable, 1000)
}
}
Here's how I'd probably handle your case. I don't know exactly what you're doing, but I'm just having the text view show "Working..." when it starts the service until there's an ID available. I haven't tested this and haven't worked with services in a long time, so you might want other input.
object IdServiceData {
val id = MutableLiveData<String>()
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...
myTextView.text = "Working..." // should really use string resource here.
IdServiceData.id.observe(this) {
myTextView.text = it.value
}
}
}
When an Activity or Fragment observes a LiveData, they automatically stop observing when they are destroyed, so they are not leaked. So your Activity can be destroyed and recreated multiple times while the Service is running and it will keep getting the proper updates.
class IdListener : Service() {
//...
private fun broadcastNewId(id: String){
mainHandler.post {
IdServiceData.id.value = id
}
}
}
If you want better encapsulation, I suppose you could abstract out the MutableLiveData by creating a separate IdServiceDataProvider that has the MutableLiveData and is used by the service, and the IdServiceData would reference the data like this: val id: LiveData<String> = IdServiceDataProvider.id

How to keep service alive even after activity destroyed?

Here i am running a service for music play back.
This code snippet is in my onStart() method of my Activity
if(musicServiceStartIntent == null) {
musicServiceStartIntent = new Intent(this, MusicService.class);
startService(musicServiceStartIntent);
bindService(musicServiceStartIntent, musicConnection, Context.BIND_AUTO_CREATE);
}
First i'm starting my service then binding it. And i am calling unbindservice() in onDestroy() method. My Activity got destroyed and service stopped.
unbindService(musicConnection);
Manifest file declaration
<service android:name=".Services.MusicService"/>
How can i keep my service running in background even after activity destroyed. I refer few threads of StackOverflow but they are not helpful.
You just need to start the service, don't bind it to activity lifecycle
Intent intent = new Intent(context, SomeService.class);
startService(intent);
And your service can use START_STICKY / START_REDELIVER_INTENT to make sure that your service will be re-created when the android system kill your service
#Override
public int onStartCommand(final Intent intent, int flags, int startId) {
//other code
return START_STICKY;
}
If needed you can use Service.startForeground(notificationId, notification) to make sure that your service will not be killed by the system
Use your service in startForeground, using Notification you can keep your service alive..
Refer to https://developer.android.com/guide/components/services.html#Foreground.
A music player that plays music from a service should be set to run in the foreground, because the user is explicitly aware of its operation. The notification in the status bar might indicate the current song and allow the user to launch an activity to interact with the music player.
To request that your service run in the foreground, call startForeground().
return service.START_STICKY or service.START_REDELIVER_INTENT in onStartCommand
There is three important tricks:
Call startForegroundService which creates a long running service not limited to the binded context and make a promise to call startForeground later.
Return START_STICKY in onStartComand
Call startForeground with a notification as promised in (1).
For example, if you want to run a TimerService, in your TimerActivity you will do:
private var timerService: TimerService? = null
private val timerServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as TimerService.Binder
timerService = binder.getService()
}
override fun onServiceDisconnected(arg0: ComponentName) {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
...
startButton.setOnClickListener {
timerService?.startTimer(60L, 0L)
}
}
override fun onStart() {
super.onStart()
Intent(this, TimerService::class.java).also {
ContextCompat.startForegroundService(this, it) // that's the first trick
bindService(it, timerServiceConnection, Context.BIND_AUTO_CREATE)
}
}
Your TimerService will be something like that:
class TimerService : Service() {
private val binder = Binder()
private var serviceLooper: Looper? = null
private var serviceHandler: ServiceHandler? = null
private var timer: CountDownTimer? = null
private val notificationUtil by lazy {
NotificationUtil(this)
}
override fun onCreate() {
HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
serviceLooper = looper
serviceHandler = ServiceHandler(looper)
}
}
override fun onBind(intent: Intent?): IBinder? = binder
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val timerRemaining = intent?.getLongExtra(EXTRA_REMAINING, 0) ?: 0L
if (timerRemaining != 0L) {
serviceHandler?.obtainMessage()?.also { msg ->
msg.arg1 = startId
msg.data.putLong(EXTRA_REMAINING, timerRemaining)
serviceHandler?.sendMessage(msg)
}
}
return START_STICKY // that's the second trick
}
override fun onDestroy() {
super.onDestroy()
timer?.cancel()
}
fun startTimer(secondsRemaining: Long, id: Long) {
Intent(this, TimerService::class.java).apply {
putExtra(EXTRA_REMAINING, secondsRemaining)
}.also {
onStartCommand(it, 0, id.toInt())
}
}
fun stopTimer() {
timer?.cancel()
}
fun updateNotification(secondsRemaining: Long){
val notification = NotificationCompat.Builder(this, NotificationUtil.CHANNEL_ID_TIMER)
.setSmallIcon(R.drawable.ic_timer)
.setAutoCancel(true)
.setDefaults(0)
.setContentTitle(secondsRemaining.formatSeconds())
.setContentText("Timer")
.setContentIntent(notificationUtil.getPendingIntentWithStack(this, TimerActivity::class.java))
.setOngoing(true)
.build()
startForeground(NotificationUtil.NOTIFICATION_ID, notification) // that's the last trick
}
private fun sendMessage(remaining: Long) {
Intent(TimerService::class.java.simpleName).apply {
putExtra(EXTRA_REMAINING, remaining)
}.also {
LocalBroadcastManager.getInstance(this).sendBroadcast(it)
}
}
private inner class ServiceHandler(looper: Looper) : Handler(looper) {
override fun handleMessage(msg: Message) {
val secondsRemaining = msg.data.getLong(EXTRA_REMAINING)
notificationUtil.showTimerStarted(secondsRemaining)
timer = object : CountDownTimer(secondsRemaining * 1000, 1000) {
override fun onTick(millisUntilFinished: Long) {
Log.i(this::class.java.simpleName, "tick ${(millisUntilFinished / 1000L).formatSeconds()}")
updateNotification(millisUntilFinished / 1000)
sendMessage(millisUntilFinished / 1000)
}
override fun onFinish() {
Log.i(this::class.java.simpleName, "finish")
notificationUtil.showTimerEnded()
sendMessage(0)
stopSelf()
}
}.start()
}
}
inner class Binder : android.os.Binder() {
// Return this instance of LocalService so clients can call public methods
fun getService(): TimerService = this#TimerService
}
companion object {
const val EXTRA_REMAINING = "EXTRA_REMAINING"
const val NOTIFICATION_ID = 1 // cannot be 0
fun Long.formatSeconds(): String {
val s = this % 60
val m = this / 60 % 60
val h = this / (60 * 60) % 24
return if (h > 0) String.format("%d:%02d:%02d", h, m, s)
else String.format("%02d:%02d", m, s)
}
}
}

Categories

Resources