简体   繁体   中英

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

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

在startForeground中使用您的服务,通过Notification可以使您的服务保持活动状态。

return service.START_STICKY onStartCommandservice.START_REDELIVER_INTENT onStartCommand中的onStartCommand

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

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM