Created
August 6, 2017 11:30
-
-
Save cbeyls/e7f874a480934d2a802873f8f8d91549 to your computer and use it in GitHub Desktop.
An Intent Service processing work in order from a Kotlin coroutine running on the main thread.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package be.digitalia.common.services | |
import android.app.Service | |
import android.content.Intent | |
import android.os.Message | |
import kotlinx.coroutines.experimental.android.UI | |
import kotlinx.coroutines.experimental.channels.Channel | |
import kotlinx.coroutines.experimental.channels.LinkedListChannel | |
import kotlinx.coroutines.experimental.launch | |
/** | |
* An Intent Service processing work in order from a coroutine running on the main thread. | |
* | |
* @author Christophe Beyls | |
*/ | |
abstract class SuspendIntentService : Service() { | |
private val channel: Channel<Message> = LinkedListChannel() | |
/** | |
* Sets intent redelivery preferences. Usually called from the constructor | |
* with your preferred semantics. | |
* | |
* <p>If enabled is true, | |
* {@link #onStartCommand(Intent, int, int)} will return | |
* {@link Service#START_REDELIVER_INTENT}, so if this process dies before | |
* {@link #onHandleIntent(Intent)} returns, the process will be restarted | |
* and the intent redelivered. If multiple Intents have been sent, only | |
* the most recent one is guaranteed to be redelivered. | |
* | |
* <p>If enabled is false (the default), | |
* {@link #onStartCommand(Intent, int, int)} will return | |
* {@link Service#START_NOT_STICKY}, and if the process dies, the Intent | |
* dies along with it. | |
*/ | |
var intentRedelivery: Boolean = false | |
override fun onCreate() { | |
super.onCreate() | |
launch(UI) { | |
for (msg in channel) { | |
try { | |
onHandleIntent(msg.obj as Intent) | |
stopSelf(msg.arg1) | |
} finally { | |
msg.recycle() | |
} | |
} | |
} | |
} | |
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { | |
val msg = Message.obtain().apply { | |
arg1 = startId | |
obj = intent | |
} | |
channel.offer(msg) | |
return if (intentRedelivery) Service.START_REDELIVER_INTENT | |
else Service.START_NOT_STICKY | |
} | |
override fun onDestroy() { | |
// Remove remaining messages | |
var msg = channel.poll() | |
while (msg != null) { | |
msg.recycle() | |
msg = channel.poll() | |
} | |
channel.close() | |
} | |
override fun onBind(intent: Intent) = null | |
/** | |
* This will be called from a coroutine running on the main thread. | |
* Execution may be suspended and resumed in order to wait for long-running operations to complete. | |
*/ | |
protected abstract suspend fun onHandleIntent(intent: Intent) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@LouisCAD This was mainly a proof-of-concept for Kotlin coroutines to be used as a replacement for
IntentService
when you don't need background threads. When jobs are not always launched by a foreground app, you should useJobIntentService
instead which handles all the complexity for you and I don't see the benefit of reimplementing it using coroutines and having to maintain this code as the support library implementation will certainly be improved over time.I use
LinkedListChannel
because it's the only channel that never suspends the sender. You can't suspend the sender because the onStartCommand() method must return immediately, it's not a suspending function. This is similar to the framework'sIntentService
which uses aLinkedList
to queue the incoming Intents. A circular array likeArrayDeque
would have been a bit more efficient but there is no equivalent for channels.