Main
(main thread). In it, we will extract the image from the IO
stream and send this image back to Main
for processing. launch(Dispatchers.Main) { val image = withContext(Dispatchers.IO) { getImage() } // IO imageView.setImageBitmap(image) // }
getImage
is executed in a dedicated pool of IO
threads, the main thread is free and can take on any other task! The withContext function suspends the current coruntine while its action is running ( getImage()
). As soon as getImage()
returns and the looper
from the main thread becomes available, the coroutine will resume work in the main thread and call imageView.setImageBitmap(image)
. val job = launch(Dispatchers.Main) { val deferred1 = async(Dispatchers.Default) { getFirstValue() } val deferred2 = async(Dispatchers.IO) { getSecondValue() } useValues(deferred1.await(), deferred2.await()) } job.join() // ,
async
is similar to launch
, but returns deferred
(Kotlin entity equivalent to Future
), so its result can be obtained using await()
. When called without parameters, it works in the default context for the current scope.launch
function returns a Job
, which can be used to wait until the operation completes — this is done using the join()
function. It works like in any other language, with the proviso that it simply suspends coruntine, and does not block the flow .Main
looks in java, that is, runOnUiThread: public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); // } else { action.run(); // } }
Main
context for Android is a Handler
based dispatcher. So this is really a very suitable implementation: launch(Dispatchers.Main) { ... } vs launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) { ... } // kotlinx 0.26: launch(Dispatchers.Main.immediate) { ... }
launch(Dispatchers.Main)
sends Runnable
to Handler
, so its code is not executed immediately.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED)
will immediately execute its lambda expression in the current thread.Dispatchers.Main
ensures that when the corutin resumes work, it will be sent to the main stream ; In addition, Handler is used here as the native Android implementation for sending app events to the loop. val Main: HandlerDispatcher = HandlerContext(mainHandler, "Main")
val job = Job() val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> whatever(throwable) } launch(Disaptchers.Default+exceptionHandler+job) { ... }
job.cancel()
will cancel all coroutines whose parent is job
. A exceptionHandler will receive all the exceptions thrown in these quiches.coroutineScope
interface simplifies error handling:async
example, if the value could not be retrieved, another task continued to work - we have a damaged state, and something needs to be done about it.coroutineScope
, the useValues
function will be called only if the retrieval of both values ​​was successful. Also, if deferred2
fails, deferred1
will be canceled. coroutineScope { val deferred1 = async(Dispatchers.Default) { getFirstValue() } val deferred2 = async(Dispatchers.IO) { getSecondValue() } useValues(deferred1.await(), deferred2.await()) }
CoroutineContext
context and use it.CoroutineScope
interface: open class ScopedViewModel : ViewModel(), CoroutineScope { protected val job = Job() override val coroutineContext = Dispatchers.Main+job override fun onCleared() { super.onCleared() job.cancel() } }
CoroutineScope
:launch
or async
default dispatcher now becomes the dispatcher of the current scope. launch { val foo = withContext(Dispatchers.IO) { … } // - CoroutineContext … } launch(Dispatchers.Default) { // - … }
GlobalScope.launch(Dispatchers.Main) { // - . … }
Main
Manager: object AppScope : CoroutineScope by GlobalScope { override val coroutineContext = Dispatchers.Main.immediate }
Dispatchers.Default
(and Main
...) - Dispatchers.IO is designed for thisDispatchers.Default
based on ForkJoinPool
, which appeared in Android 5+Channel
conceptually very similar toBlockingQueue
. The key difference is that it does not block the put operation, it provides a suspendingsend
(or nonblockingoffer
), and instead of blocking a take operation it provides a suspendingreceive
.
Actor
.Actor
, again, is very similar to Handler
: we define the context of the korutina (that is, the stream in which we are going to perform the actions) and work with it in a sequential order.actor
will redirect any command to the cortina channel. It guarantees the execution of a command and restricts operations in its context . This approach perfectly helps to get rid of synchronize
calls and keep all streams free! protected val updateActor by lazy { actor<Update>(capacity = Channel.UNLIMITED) { for (update in channel) when (update) { Refresh -> updateList() is Filter -> filter.filter(update.query) is MediaUpdate -> updateItems(update.mediaList as List<T>) is MediaAddition -> addMedia(update.media as T) is MediaListAddition -> addMedia(update.mediaList as List<T>) is MediaRemoval -> removeMedia(update.media as T) } } } // fun filter(query: String?) = updateActor.offer(Filter(query)) // suspend fun filter(query: String?) = updateActor.send(Filter(query))
sealed class Update object Refresh : Update() class Filter(val query: String?) : Update() class MediaAddition(val media: Media) : Update()
job.cancel()
when destroying activity. class MyActivity : AppCompatActivity(), CoroutineScope { protected val job = SupervisorJob() // Job override val coroutineContext = Dispatchers.Main.immediate+job override fun onDestroy() { super.onDestroy() job.cancel() // } }
SupervisorJob
class is similar to the normal Job
with the only exception that cancellation only extends in the downstream direction.Activity
, when one of them refuses.CoroutineContext
from any View
in CoroutineScope
. val View.coroutineContext: CoroutineContext? get() = (context as? CoroutineScope)?.coroutineContext
setOnClick
function creates a combined actor to control its onClick
actions. In the case of multiple clicks, intermediate actions will be ignored, thus eliminating ANR errors (the application is not responding), and these actions will be performed within the scope of the Activity
. Therefore, if you destroy the activity, all this will be canceled. fun View.setOnClick(action: suspend () -> Unit) { // val scope = (context as? CoroutineScope)?: AppScope val eventActor = scope.actor<Unit>(capacity = Channel.CONFLATED) { for (event in channel) action() } // setOnClickListener { eventActor.offer(Unit) } }
Channel
to be Conflated
, so that it ignores part of the events if there are too many of them. You can replace it with Channel.UNLIMITED
if you prefer to queue events without losing any of them, but you still want to protect the application from ANR errors. val LifecycleOwner.untilDestroy: Job get() { val job = Job() lifecycle.addObserver(object: LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun onDestroy() { job.cancel() } }) return job } // GlobalScope.launch(Dispatchers.Main, parent = untilDestroy) { /* ! */ }
Channel
.requestBrowsing(url, listener)
initiates a parsing of the folder located at url
.listener
receives onMediaAdded(media: Media)
for any media file found in this folder.listener.onBrowseEnd()
is called upon completion of the folder parsingrefresh
function in the content provider for the VLC browser: private val refreshList = mutableListOf<Media>() fun refresh() = requestBrowsing(url, refreshListener) private val refreshListener = object : EventListener{ override fun onMediaAdded(media: Media) { refreshList.add(media)) } override fun onBrowseEnd() { val list = refreshList.toMutableList() refreshList.clear() launch { dataset.value = list parseSubDirectories() } } }
refresh
. Now browser callbacks will only send media to this channel, and then close it.refresh
function has become clearer. It creates a channel, calls the VLC browser, then builds a list of media files and processes it.select
or consumeEach
you can use for
to wait for the media, and this cycle will break as soon as the browserChannel
channel closes. private lateinit var browserChannel : Channel<Media> override fun onMediaAdded(media: Media) { browserChannel.offer(media) } override fun onBrowseEnd() { browserChannel.close() } suspend fun refresh() { browserChannel = Channel(Channel.UNLIMITED) val refreshList = mutableListOf<Media>() requestBrowsing(url) // for (media in browserChannel) refreshList.add(media) // dataset.value = refreshList parseSubDirectories() }
retrofitSuspendCall
function wraps the request for a Retrofit Call
to make it a suspend
function.suspendCoroutine
we call the Call.enqueue
method and suspend the coruntine. The callback provided in this way will apply to continuation.resume(response)
to resume the quortenine response from the server as soon as it is received.retrofitSuspendCall
, in order to use them to return query results. suspend inline fun <reified T> retrofitSuspendCall(request: () -> Call <T> ) : Response <T> = suspendCoroutine { continuation -> request.invoke().enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) { continuation.resume(response) } override fun onFailure(call: Call<T>, t: Throwable) { continuation.resumeWithException(t) } }) } suspend fun browse(path: String?) = retrofitSuspendCall { ApiClient.browse(path) } // ( Main) livedata.value = Repo.browse(path)
Channel
can be used in many other ways; Look at BroadcastChannel for more powerful implementations that may be useful to you.Channel
or, for example, via Actor
.Source: https://habr.com/ru/post/457224/
All Articles