0

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.

saa
  • 1,538
  • 2
  • 17
  • 35

5 Answers5

3

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

HendraWD
  • 2,984
  • 2
  • 31
  • 45
2

Use your service in startForeground, using Notification you can keep your service alive..

Tanveer Bulsari
  • 213
  • 1
  • 10
  • it helps.. your answer not always helpful. i want to run this service continuously when my activity running and after destroyed. – saa Aug 01 '16 at 10:55
  • 1
    than you must start your service in foreground – Tanveer Bulsari Aug 01 '16 at 11:10
  • how to start service in foreground? i am calling liike this startService(intent); – saa Aug 01 '16 at 11:12
  • Your suggestion working for me. Please edit your answer i will mark it as correct answer. Thanks.. :D – saa Aug 01 '16 at 11:30
  • On android devices with lot of RAM, you're not required to put service foreground, but it'll make it more resilient. If service is using intents, `registerReciever` in separate ServiceHandler! ServiceHandler will prevent crashing your service when receiving intent coming after your Activity was destroyed by user/system. Example of such a service: https://github.com/mauron85/cordova-plugin-background-geolocation/blob/master/android/plugin/src/main/java/com/marianhello/bgloc/LocationService.java – mauron85 Sep 07 '16 at 11:37
1

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().

anoo_radha
  • 812
  • 1
  • 15
  • 34
0

return service.START_STICKY or service.START_REDELIVER_INTENT in onStartCommand

lorond
  • 3,856
  • 2
  • 37
  • 52
Shubham
  • 521
  • 4
  • 11
0

There is three important tricks:

  1. Call startForegroundService which creates a long running service not limited to the binded context and make a promise to call startForeground later.
  2. Return START_STICKY in onStartComand
  3. 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)
        }
    }

}
Allan Veloso
  • 5,823
  • 1
  • 38
  • 36