Android Background Job Service on CoroutineScope and Dispatcher.IO - android

Context
I'm trying to update a couple of background Job in Android 6. The main goal is to make the work done by the background job light and non ui thread blocking. So I decided to some things:
GlobalScope may be a bad idea since a dying thread will terminate the others. So CoroutineScope will to the trick
Since I need I/O and Network stuff done, I need a thread pool designed for those tasks. So I'll use Dispatchers.IO
The work of the bound JobServices are designed for "fire and forget". The app doesn't care - so I'll just launch the job instead of waiting for the output of an async
Code
All together I designed the following JobService class as a template for others to be migrated or implemented. Since the job is shedulded the class will return false to the inherited functions.
import android.app.job.JobParameters
import android.app.job.JobService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class TestJobService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean {
CoroutineScope(Dispatchers.IO).launch {
// Fire and Forget
}
return false
}
override fun onStopJob(params: JobParameters?): Boolean {
return false
}
}
Questions
If you want to answer please use the number of the question.
Is CoroutineScope a good idea?
I'm getting errors from the main thread "I/art: Note: end time exceeds epoch:" - this is confusing and could indicate that the JobService does to much work on the UI thread. But I have no errors from the choreographer which should normally be the case if the UI is in trouble.
Is it a bad idea to return false in the JobService functions
Currently used Links
https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb
https://medium.com/androiddevelopers/coroutines-on-android-part-ii-getting-started-3bff117176dd
https://medium.com/androiddevelopers/coroutines-on-android-part-iii-real-work-2ba8a2ec2f45
https://developer.android.com/kotlin/coroutines/coroutines-adv?hl=de#coroutinescope
https://kotlinlang.org/docs/coroutines-basics.html#extract-function-refactoring

The only thing your usage of CoroutineScope does is provide the IO dispatcher. As it stands, if any of the work done in the coroutine throws an exception, the whole scope will be cancelled. That may be a problem if you are launching multiple coroutines from within it. If you do care about that you would also need to set it up with a SupervisorJob: CoroutineScope(Dispatchers.IO) + SupervisorJob().
It's hard to identify the meaning of that error without your actual code. From researching quickly, if you do something that should only occur on the main thread (like accessing certain Android components perhaps), that may cause that error. Perhaps inspect your code for those occurances and wrap it in a:
withContext(Dispatchers.Main) { ... }
But again this is just a guess without having any actual code.
The docs indicate for onStartJob:
The system holds a wakelock on behalf of your app as long as your job is executing. This wakelock is acquired before this method is invoked, and is not released until either you call jobFinished(android.app.job.JobParameters, boolean), or after the system invokes onStopJob(android.app.job.JobParameters) to notify your job that it is being shut down prematurely.
Returning false from this method means your job is already finished. The system's wakelock for the job will be released, and onStopJob(android.app.job.JobParameters) will not be invoked.
And according to the wakelock docs,
A wake lock is a mechanism to indicate that your application needs to have the device stay on.
So no, it does not seem correct to return false from onStartJob as the JobService will not properly maintain the wakelock. To remedy this, you should return true from this method, and upon completion of the coroutine, you should call jobFinished to indicate the job has completed all of its work.
For onStopJob, the return values:
true to indicate to the JobManager whether you'd like to reschedule this job based on the retry criteria provided at job creation-time; or false to end the job entirely. Regardless of the value returned, your job must stop executing.
For this case it's hard to tell what is the correct value based on your snippet. You should be able to figure out which is correct based on your use case. In this method you should be cancelling your previously created CoroutineScope. You will need to maintain a reference to the scope in order to do this.

Related

Why use suspend functions in kotlin coroutine?

Why we should use suspend?
Are they used only to restrict a function not to be used outside coroutine scope or other suspend functions?
Are they used only to restrict a function not to be used outside co routine scope or other suspend functions?
No. The main purpose of a suspend function is to suspend a coroutine it is called in to eliminate callback hell. Consider the following code with a callback:
fun interface Callback {
fun call()
}
fun executeRequest(c: Callback) {
// ... create new Thread to execute some request and call callback after the request is executed
c.call()
}
On the caller side there will be:
executeRequest {
// ... do sth after request is competed.
}
Imagine you need to make another request after that one:
executeRequest {
// make another request
executeRequest {
// and another one
executeRequest {
// and another one ...
executeRequest {
}
}
}
}
That is a Callback Hell. To avoid it we can get rid of a Callback code and use only suspend functions:
// withContext(Dispatchers.IO) switches a coroutine's context to background thread
suspend fun executeRequest() = withContext(Dispatchers.IO) {
// just execute request, don't create another Thread, it is already in the background Thread
// if there is some result, return it
}
And on the caller side if there are a couple of requests, that should be executed one by one, we can call them without the Callback Hell by launching a coroutine and calling those request in the coroutine:
viewModelScope.launch {
executeRequest()
executeRequest()
executeRequest()
}
If you wonder what it does, I guess you will find the answer in this near-duplicate question's answer.
It basically allows to use the syntax of synchronous calls to call an asynchronous function.
If the question is "Why use async programming?", then there are probably plenty of resources explaining this on the internet. It usually allows to use threads more effectively and avoids using too many resources.
Now as to why you would want to use suspend to do that instead of callbacks, there is probably several reasons. Probably the biggest of them is to avoid the infamous "callback hell" (well known in JS): using actual callbacks creates nesting in the code. It makes the language harder to manipulate because you can't use local variables or loops as easily as with regular sequential code. With suspend functions, the code reads sequentially even though some asynchronous mechanisms are used behind the scenes to resume executing a piece of code at a later point.
Another big reason to use suspend (and more precisely the coroutines library in general) is structured concurrency. It allows to organize your asynchronous work so you don't leak anything.
Under the hood, suspend functions provide the extra functionality (involving a hidden Continuation object) that allow their code to be stopped and resumed later from where it left off, instead of the typical limitation that all function calls in a function are called in sequence and block the thread the entire time. The keyword is there to make the syntax very simple for doing a long-running task in the background.
Three different benefits of this:
No need for callbacks for long-running tasks ("callback hell")
Without suspend functions, you would have to use callbacks to prevent a long-running task from blocking the thread, and this leads to complicated-looking code that is not written in sequential order, especially when you need to do a number of long-running tasks in sequence and even more-so if you need to do them in parallel.
Structured concurrency
Built-in features for automatically cancelling long-running tasks. Guarantees about shared state being correct whenever accessed despite thread switching.
No need for state machines for lazy iterators
A limited set of suspend functions can be used in lazy iterator functions like the iterator { } and sequence { } builders. These are a very different kind of coroutine than the ones launched with CoroutineScopes, because they don't do anything with thread switching. The yield() suspend function in these builders' lambdas allows the code to be paused until the next item is requested in iteration. No thread is being blocked, but not because it's doing some task in the background. It instead enables code to be written sequentially and concisely without a complicated state machine.

Kotlin Coroutine GlobalScope documentation clarity

I'm having a hard time understanding the usages and documentation for GlobalScope.
The docs states:
A global CoroutineScope not bound to any job.
Global scope is used to launch top-level coroutines which are
operating on the whole application lifetime and are not cancelled
prematurely. Another use of the global scope is operators running in
Dispatchers.Unconfined, which don’t have any job associated with them.
Application code usually should use an application-defined
CoroutineScope. Using async or launch on the instance of GlobalScope
is highly discouraged.
What does it mean when GlobalScope is not bound to any job? Because I can do
val job = GlobalScope.launch {
// some work
}
job.cancel()
It says they are not canceled prematurely. What does this mean? As you can see above I can cancel it.
Lastly it says, it operates on the whole application lifetime. So the scope stays alive until the application dies. How does this compare with CoroutineScope? When I exit an Android Activity in the middle of a running CoroutineScope, it'll still be alive and runs until completion. Does it just mean the CoroutineScope will get cleaned up w/ garbage collection after it completes and GlobalScope wont?
link to docs:
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/
What does it mean when GlobalScope is not bound to any job? Because I can do...
This refers to the lack of Job object associated to the CoroutineScope, not individual coroutine Jobs within the "Scope".
It says they are not canceled prematurely. What does this mean? As you can see above I can cancel it.
Calling GlobalScope.cancel() at any point will throw an IllegalArgumentException.
so this would produce the same error :
fun main(args: Array<String>) {
val myScope : CoroutineScope = object : CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.IO // no job added i.e + SupervisorJob()
}
val job = myScope.launch { longRunningTask() }
Thread.sleep(5_000)
job.cancel() // this is fine - individual coroutine cancelled
myScope.cancel() // this will throw an error, same as GlobalScope.cancel()
}
Lastly it says, it operates on the whole application lifetime. So the scope stays alive until the application dies. How does this compare with CoroutineScope? When I exit an Android Activity in the middle of a running CoroutineScope, it'll still be alive and runs until completion. Does it just mean the CoroutineScope will get cleaned up w/ garbage collection after it completes and GlobalScope wont?
GlobalScope is an object class - a singleton, thus lasts as long as the process it runs in. GlobalScope implements CoroutineScope its just a predefined scope that comes with the library. Coroutines can be launched from GlobalScope but the caller must take care to retain the Job object and cancel each coroutine via the Job. When using custom CoroutineScope you can have greater control on what happens when you call the extension function CoroutineScope.cancel() - for instance cancelling all child coroutines "running" in that scope.
I'm no authority on the subject but that's how I interpet the documentation and observed behaviours of the library.
If I'm honest I think most people struggle to explain coroutines simply and concisely - most articles on the subject are not easy reading and usually I come away more confused. Having delved into the library source code a bit after it usually demistifies things somewhat, but you can also see it is still very much in a state of continuing development.

Do suspend functions execute in another thread and come back with the result?

I've looked at plenty of articles online but I'm still a bit confused as to what happens specifically, step by step, when suspend functions are suspended in coroutines.
I know that a suspend function, under the hood, is just a regular function with a Continuation parameter that allows it to resume, but my confusion is regarding where that suspend function or coroutine goes and where it comes back once resumed.
I've heard a few people saying "they don't necessarily come back to the same thread" and I don't get it, can someone please explain this to me step by step?
TLDR;
There is no guarantee, it may or may not,
it really depends that on the following points:
Is the dispatcher is multi-threaded?
Is there any dispatcher override in between?
LONG Answer
A coroutine has a CoroutineContext that specify how it behaves, where it run.
A CoroutineContext is mainly build up with four elements: Job, CoroutineName, CoroutineExceptionHandler and Dispatcher.
Its responsibility of the dispatcher to dispatch the coroutine. A dispatcher can be paused to stop coroutines to even run (this is useful in unit testing) mentioned here in the android conference talk, it may be a single-threaded dispatcher just like Dispatchers.Main, it has an event-loop like javascript has.
So, it really depends that on the following points:
Is the dispatcher is multi-threaded?
For example: This will run on single thread.
suspend fun main() {
val dispatcherScope = CoroutineScope(Executors.newSingleThreadExecutor().asCoroutineDispatcher())
val job = dispatcherScope.launch {
repeat(10) {
launch {
println("I'm working in thread ${Thread.currentThread().name}")
// every coroutine on same thread
}
}
}
job.join()
}
Run it here, other single threaded dispatchers: Dispatchers.Main
Is there any dispatcher override in between?
With the same context, if we override the dispatcher before launch, it will change the thread even if original context is based on single-threaded event-loop, each coroutine will run on different thread creating 10 different thread:
dispatcherScope.launch {
repeat(10) {
launch(Dispatchers.IO) {
println("I'm working in thread ${Thread.currentThread().name}")
// every coroutine on same thread
}
}
}
Run it here, other multi-threaded Dispatchers: Dispatchers.Default, Executor based dispatcher, Dispatchers.Unconfined (this launch coroutine in any free thread).
The short answer is: they execute somewhere and come back with the result to somewhere.
The long(er) explanation for the short answer:
"Somewhere" might be the same thread, it might be a different thread - it depends on the dispatcher and in many cases, the current state of the dispatcher. For instance, the contents of a SequenceScope will (by default) run on the same thread.
Another case where a suspend function might run on the same thread is if it's using the same dispatcher as the calling function.
Dispatchers can also share threads between them to save on thread creation (and just keep a count of the maximum number of parallel operations for their own tasks), so even switching between different dispatchers that each use thread pools may not result in using a different thread.
As far as people saying "they don't necessarily come back to the same thread," this is correct, for similar reasons. Keep in mind that your dispatcher may have many threads, and the one that you were using before you got suspended might be occupied with a different function right now, so the dispatcher will simply pick a different thread to run your code on.
When the coroutine suspends, the underlying Java method returns a special COROUTINE_SUSPENDED value. If the calling function is also suspendable, it also returns the object, and so the execution returns to the innermost plain, non-suspendable function. This function typically runs an event loop, where event handlers are calls to continuation.resume(). So now it is ready to take the next handler from the queue and resume another coroutine.
When you call continuation.resume(), the continuation itself knows about the dispatcher in charge of the coroutine and delegates to it. If the current thread isn't owned by that dispatcher, it dispatches an event to another event loop, the one served by the dispatcher's thread pool. This way the dispatcher controls the thread where the corutine resumes.

How can CoroutineScope(job+Dispatchers.Main) run on the main/UI thread?

If the operations inside CoroutineScope(job+Dispatchers.Main){...} run on the main thread then how come it does not violate Android's requirement that slow (blocking) operations (Networking etc.) are not allowed to run on the main/UI thread? I can run blocking operations with this scope and the UI does not freeze at all.
I would be grateful if someone could explain what is happening under the hood. My guess is that it is similar to how JavaScript manages blocking operations with the event loop, but I struggle to find any relevant materials.
My guess is that it is similar to how JavaScript manages blocking operations with the event loop
Yes, this is correct, the event loop is essential to making coroutines work. Basically, when you write this:
uiScope.launch {
delay(1000)
println("A second has passed")
}
it compiles into code that has the same effect as this:
Handler(Looper.mainLooper()).postDelayed(1000) { println("A second has passed") }
The main concept is the continuation, an object that implements a state machine that corresponds to the sequential code you wrote in a suspendable function. When you call delay or any other suspendable function, the continuation's entry-point method returns a special COROUTINE_SUSPENDED value. Later on, when some outside code comes up with the return value of the suspendable function, it must call continuation.resume(result). This call will be intercepted by the dispatcher in charge, which will post this call as an event on the GUI event loop. When the event handler is dequeued and executed, you are back inside the state machine which figures out where to resume the execution.
You can review this answer for a more fleshed-out example of using the Continuation API.
Running blocking operations and running suspending operations on CoroutineScope(Dispatchers.Main) are two different things.
delay() is a suspending function and it is non blocking
CoroutineScope(Dispatchers.Main){
delay(6000)
}
While Thread.sleep() is blocking and calling code below will cause ANR
CoroutineScope(Dispatchers.Main){
Thread.sleep(6000)
}
I suggest you checking Kotlin coroutines talk by Roman Elizarov on Kotlinconf 2017, especially the part where he runs 100,000 delay()

Android: How to unit test "IllegalStateException: Can not perform this action after onSaveInstanceState"

I'm having a crash in my app where sometimes a dialog.show is called after the activity's lifetime. I know where this happens and would like to unit test every bug that occurred in the app to avoid it to appear again. But how can something like this be (unit?) tested?
It's difficult to unit test the exception because the occurrence is tightly bound to the Activity lifecycle as the exception message suggests - the isolation of the occurrence is practically impossible .
You could employ Robolectric and try to verify whether the dialog.show() method is invoked before onSaveInstanceState call but I would not approach the problem this way. And the tests using Robolectric are no longer unit tests.
I met with a few solutions that eliminated the exception occurrence:
You could instantiated an internal queue storing functions deferring the FragmentTransaction-related methods execution and recognize whether the activity has called onSaveInstanceState at the time the show() method is attempted to be executed.
If the activity is in the created/started/resumed state you could execute show() immediately. If not, store the function deferring the show() execution and execute them
A few lines of pseudocode below:
if (isCreatedOrStartedOrResumed) {
dialog.show()
} else {
internalQueue.add {
dialog.show()
}
}
Has the activity returned to the resumed state, execute all pending functions
fun onResume() {
super.onResume()
while(internalQueue.isNotEmpty()) {
internalQueue.poll().invoke()
}
}
This approach is not immune to configuration change though, we lose the deferred invocations once the activity gets rotated.
Alternatively, you could use ViewModel which is designed to retain the activity state across configuration change such as the rotation and store the deferred executions queue from the 1st approach inside the view model. Make sure the functions storing deferred dialog.show() executions are not anonymous classes - you may end up with memory leak introduced.
Testing:
The way I would test the dialog displaying gracefully would be the Espresso instrumentation tests.
I would also unit test view model storing/executing deferred executions.
If we consider structuring code using MVP or MVVM architectural pattern we could store the deferred executions queue within one of the architecture class members and unit test them too.
I would also include LeakCanary as a safety net against memory leaks.
Optionally, we could still use robolectric and develop integration test verifying:
the internal queue deferring dialog.show() executions after onSaveInstanceState gets called
the internal queue executing pending dialog.show() executions stored after activity has called onSaveInstanceState and returned to the resumed state again.
the dialog.show() executed immediately in case the activity is in created/started/resumed state.
That's all I have at the moment, hope you will figure out the satisfactory tests suite verifying correct dialog displaying based on the approaches suggested.

Categories

Resources