How to handle thread execution in coroutines? - android

I have a video playing application. I have implemented Vertical Viewpager2 with fragment. The fragment contains fullscreen exoplayer where I load the video and have a button to open the profile of the user who have posted the video. I make all the API calls in viewmodel as
viewmodelScope.launch(Dispatchers.IO){
... //making api call and updating the livedata
...
}
I am also using
lifecycleScope.launch(Dispatchers.IO){
... //start caching the video
}
The Problem is :
When I scroll like 40 - 45+ items and launch any coroutine on Dispatchers.IO, the launch block is never executed hence the api calls are never made and app stops working.
Debugging results :
I tried to debug why the launch block is not executing with debug break points. In the LimitingDispatcher class, there is variable called inFlightTasks which is used to schedule the task if the parallelism limit is reached (which is 64 by default). So, my current task is added to ConcurrentLinkedQueue<Runnable>. I waited for an hour but after scheduling the task it never resumed the task again. I checked the queue for the tasks and at the time of testing it had around 150-170 tasks but none of them resumed.
Any idea how to solve this problem.
Note : I cannot remove the caching of video(which is am using in onStart() of the fragment).
EDIT
I tried with lifecycleScope.launch{ } and withContext(Dispatchers.IO){ }, but I am still getting the same issue.
In viewModelScope
viewModelScope.launch(Dispatchers.IO){
val response = repository.call()
when(repository){
is Resource.Sucess -> livedata.postValue(response.data)
is Resource.Error -> livedata.postValue(response.error)
}
}
In lifecycleScope
lifecycleScope.launch(Dispatcher.IO){
try{
startVideoCaching()
}catch(e : Exception){
e.printStackTrace()
}
}
And both the functions in launch are purely suspend functions.
Edit 2
I am adding the screen shots of my functions and the Dispatchers.IO's queue data.
Debug sscreenshot.
The functions named in the debug screenshot.
In the sendAnalytics I am using lifecycleScope only
cacheVideo() is called from onStart().
markVideoView() is called from onResume()
I tried with yield() but still no change in the behavior.
I am using all this methods in Fragment which is a part of ViewPager2 items.

If it reached the parallelism limit, and the other ones are put in the queue, the only
logical reason they're not resuming and executing is that those previously called
are not suspending/returning to free up the threads for the queued ones to run.
What you should do is make your functions cancellable (cooperatively cancellable using
isActive) and those coroutines that are fired for the items that are no longer visible
on the screen (scrolled off the screen) should be cancelled, since there is no point of
calculating something not visible on the screen. By cancelling those coroutines you
should always have enough free threads to run those coroutines for the items currently visible on the screen.
In case you don't want to cancel coroutines fired for the items not currently on the screen, you can call yield() inside your functions to deliberately yield thread to other coroutines that run on the same thread pool.

Related

How to cancel all background operations when move from 1 activity to another?

I am facing issue that app gets freezed after using it for 3 mins. I am using lifecycleScope.launch {...} to get data from API. I want to know whether i am doing in a right way or not. To do network operations i am using Coroutine lifecycle. Below is my code
lifecycleScope.launch {newAllChatModel.getAllArchivedAPICount()}
Whether i have to cancel all Coroutines on activity onStop if yes then how should i do
viewModelScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {//getting data from server}}

Question about launchWhenX and repeatOnLifecycle

I have the following code and a few questions that need to be answered:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launchWhenResumed {
delay(2000)
Log.d("LifeCycleAware", "launchWhenStarted: before calling")
val result = differentDispatcher()
Log.d("LifeCycleAware", "launchWhenStarted: after calling $result")
}
}
private suspend fun differentDispatcher(): Int =
withContext(Dispatchers.Default) {
for (i in 1..5) {
delay(2000)
Log.d("LifeCycleAware", "Inside different Dispatcher")
}
return#withContext 9
}
override fun onStart() {
super.onStart()
Log.d("LifeCycleAware", "onStart")
}
override fun onStop() {
super.onStop()
Log.d("LifeCycleAware", "onStop")
}
As far as I understand, the method (or any of them) launchWhenResumed, is called when the Lifecycle has reached the RESUMED state, and I also know that when I move the app to the background, the corrutine will stop, but it will not stop if it has child corrutines running in another Dispatcher, so far so good.
So in this code, we determine that if I, in the middle of the loop that is in the differentDispatcher method, send the app to second, it will continue to run but when it finishes, the parent corrutine launched with launchWhenResumed, will not resume until it takes this RESUMED state again.
My first doubt is... if when the corrutine is finished running, I go to the background and return to the foreground, why is it not launched again, if I have returned to the RESUMED state?
I also know about the existence of the repeatOnLifecycle method, where, if I pass the Lifecycle.State.RESUMED state as parameter, it is executed every time, moreover, I know that in this case if I go to the background, the execution of the corrutine is completely suspended and when I go back to the foreground it starts from the beginning, but, why when I run with launchWhenResumed and the corrutine finishes it does not start again, but with repeatOnLifecycle it does? What does it do differently internally?
I guess the answer is because when I switch from background to foreground, the onCreate method is not called again, and I've checked that:
override fun onResume() {
super.onResume()
lifecycleScope.launchWhenResumed {
delay(2000)
Log.d("LifeCycleAware", "launchWhenStarted: before calling")
val result = differentDispatcher()
Log.d("LifeCycleAware", "launchWhenStarted: after calling $result")
}
}
This way it does re-launch because the onResume method does call again when I switch from background to foreground but then.... what kind of magic does the repeatOnLifecycle method do?
The key to understanding launchWhenResumed is to break it down into the two parts that is actually is: a launch and a whenResumed block. Looking at the source code, you'll see it is actually exactly that:
public fun launchWhenResumed(
block: suspend CoroutineScope.() -> Unit
): Job = launch { // It does a regular launch
lifecycle.whenResumed(block) // Then passes your block to whenResumed
}
A launch is a one time operation - a launch done in onCreate() will only run exactly once for each call to onCreate(). This is also why calling launch in onResume() will launch every time you hit onResume.
A call to launch finishes in one of two ways: all of the calls within that block complete normally or the CoroutineScope is cancelled. For lifecycleScope, that cancellation happens when the Lifecycle reaches DESTROYED.
So in a regular launch, work starts immediately, runs until everything completes (or the scope is cancelled), and that's it. It never runs again or restarts at all.
Instead, the whenResumed is an example of Suspend Lifecycle-aware coroutines:
Even though the CoroutineScope provides a proper way to cancel long-running operations automatically, you might have other cases where you want to suspend execution of a code block unless the Lifecycle is in a certain state.
Any coroutine run inside these blocks is suspended if the Lifecycle isn't at least in the minimal desired state.
So what whenResumed does is just pause the coroutine code when you fall below the resumed state, essentially meaning that if your Lifecycle stops being RESUMED, instead of your val result = differentDispatcher() actually resuming execution immediately and your result being delivered back to your code, that result is simply waiting for your Lifecycle to again reach RESUMED.
So whenResumed doesn't have any 'restarting' functionality - just like other coroutine code, it just runs the code you've given it and then completes normally.
You're right that repeatOnLifecycle is a very different pattern. As per the Restartable Lifecycle-aware coroutines, repeatOnLifecycle doesn't have any of the 'pausing' behavior at all:
Even though the lifecycleScope provides a proper way to cancel long-running operations automatically when the Lifecycle is DESTROYED, you might have other cases where you want to start the execution of a code block when the Lifecycle is in a certain state, and cancel when it is in another state.
So in the repeatOnLifecycle call, every time your Lifecycle reaches RESUMED (or what Lifecycle.State you want), the block is ran. When you fall below that state, the whole block is completely cancelled (very similar to when your LifecycleOwner reaches DESTROYED - that level of cancelling the whole coroutine scope).
You'll not the dire warning at the end of the page that talks about both of these APIs:
Warning: Prefer collecting flows using the repeatOnLifecycle API instead of collecting inside the launchWhenX APIs. As the latter APIs suspend the coroutine instead of cancelling it when the Lifecycle is STOPPED, upstream flows are kept active in the background, potentially emitting new items and wasting resources.
The fact that your differentDispatcher code continues to run in the background, despite being inside a whenResumed block is considered a bad thing - if that code was doing more expensive operations, like checking for the user's location (keeping GPS on), it would continue to use up system resources and the user's battery the whole time, even when you aren't RESUMED.

How to make synchronous network call in android?

I am trying to make an application that constantly hits the google API to fetch the distance between two points and then process the distance and add it in db inside a loop.
I was previously using the volley but it does not waits and my function proceeds with the rest of lines of code. I need help so that the line of code waits for that method to return only if some result is received once I hit google API, then use that result in next line of code.
I'd recommend looking into coroutines. More specifically take a look at the async launcher which should return the value to an awaiting variable. Your code runs asynchronously (starting a new thread so your main UI thread doesn't block and the user can freely do as they wish) and when the call returns your code is called once again at the point where the variable will be awaiting. You could set a timer right before starting the async call and stop it after await.
You could also use withContext{} to await for a result. The code will be continued in the calling function when the coroutine returns a result.
startTimer()
val job = coroutineScope{
async{
networkCall()
}
}
job.await()
stopTimer()
or
startTimer()
coroutineScope{
launch{
withContext{
networkCall()
}
//code will continue when networkCall() has returned a result
stopTimer() //be careful, this may be called through a background thread.
}
}
Coroutines are thread-safe and lightweight so you can start as many as you want without any problems. Also, by using coroutineScope you will avoid lifecycle problems like your ui being rotated which would normally cause a network call to be resent. With coroutineScope your call will have the life span of the calling activity/fragment thus it will be cancelled or killed if your app unexpectedly decides to stop and not care for the result any longer.
Hope this helped, Panos.
If you are beginner use retrofit library to make API calls in there is option enque will call API background for you.
https://www.journaldev.com/13639/retrofit-android-example-tutorial
If you are pro go for Rxjava with Retrofit.
https://medium.com/3xplore/handling-api-calls-using-retrofit-2-and-rxjava-2-1871c891b6ae
You cannot do synchronous calls on the main thread. Blocking the UI thread on a network call for more than a specified period of time would trigger an ANR.
A couple of options would be to use an AysncTask or AsyncTaskLoader. Blocking the main thread is definitely not recommended.
AsyncTasks create a separate thread of execution while the UI thread can continue with its work.
Android applications usually start with a single thread of execution so if you block this thread then an ANR would result.
Take a look here
If you don't mind the ANRs which will not be acceptable to a user then go with using a simple HttpURLConnection but this is not recommended.
If you do not prefer the AsyncTask approach you can create a Runnable to do the background processing and then update the UI thread.
More here
You can implement a jobservice to get distance in an interval.You can view the implementation .here

RxJava, What happened if I don't call dispose?

My Android app needs to support to upload a big file, but I do not want a user wait until the upload is completed.
Usually, when I use Observable, I call dispose() for it when the view is destroyed.
But in uploading case, I can not dispose it in any case until it finished.
So I was thinking to try to like this,
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
fun upload() {
val disposable = Observable.just(true).delay(20, TimeUnit.SECONDS).subscribe({
Log.d("=>", "Upload finished")
disposeUploader()
})
compositeDisposable.add(disposable)
}
fun disposeUploader() {
compositeDisposable.clear()
compositeDisposable.dispose()
}
But the problem is the upload() could be called multiple times, so the first uploader will dispose of all other processing calls.
What happened if I don't call dispose? or is there any way dispose of itself when it is completed?
The idea of disposing an Observable serves two purposes:
1) Avoid memory leaks - detach view references that might be held by ongoing request module.
2) Release resources - stop ongoing background task to free unneeded resources, when the user exit your activity for instance, a request/processing might not be relevant anymore, so no point to keep it running.
In your case you want your background task (uploading file) to resume, avoiding (2), while having your view detaching from it (1).
Your solution of disposing after some period of time miss the point as Observable will dispose itself at completion. moreover, you can't assume a specific duration for the upload (at most it is timeout value).
The solution with RxJava is multicasting your Observable using operator like publish and share, in your case:
val multicastedObservable = uploadObservable.publish()
.autoConnect()
.subscriber(//your view related subscriber);
This way, uploadObservable will start execute at first subscribe but will not stop when dispose is called, but rather will detach the reference to view.
Having said all that, it's worth noting that your upload scenario cannot be done reliably without foreground service in Android.

Android : runOnUiThread not working, working if user touches screen

When I do some processing in the background thread and wants to update the UI using runOnUiThread(), the code inside the function is running successfully without any error, but the UI gets rendered only if the user touches the screen. Otherwise, it's rendering consistently.
Why it's rendering only after touching the screen ?
It is possible if your screen gets overlapped by another screen. This causing Activity to move to paused state (.onPause()) method. when you touch it again, it become foreground again so can receive UI update events
It's possible that an operation is blocking (for example notifying an adapter too often) your ui thread and gets unblocked with an interrupt occurs. Touching the screen is an interrupt in your scenario.
If you paste your code maybe we can find an exact solution.
Edit: Debounce example code with RxJava
yourSearchObservable
.throttleLast(100, TimeUnit.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
searchResults -> {
// Your success
} ,
() -> {
// Error
}
));
Try using callbacks or android.os.Handler.post() to interact with the Main Thread

Categories

Resources