Issue
I have the following scenario (view code below):
- Get an
ImageProxy
from the loop of theImageAnalyzer
use case of CameraX - Analyze the image (takes about 20 ms) and return some information,
close()
theImageProxy
- Analyze the extracted information via AsyncTask (takes about 500 to 3000 ms)
- If successful, show results
I have some fragments which are dependent on how successful Task 1. and 2. are (in particular a PreviewView fragment and some result page). Currently they get notified by the ViewModel
. When the image gets analyzed by either Task 1. or 2. I set a state with viewModel.analyzerState.postValue(<newState>)
. This is of course not ideal, since I need to listen to that state in my Tasks as well in case the previous image produced a result that I can show to the user.
My problem is now the following:
When a result is ready, Task 1. or 2. might still be running and produce another result which will then trigger the UI to update twice. I do not want that, but I still want the Tasks to run in parallel in case the previous task failed (which happens quite often). When I'm sure I have a result, I want the Tasks cancelled (I'm already calling cancel()
on the AsyncTask, but that's not working). I think my problem is using the ViewModel
as a messaging tool for inter thread communication, since I set the state in Task 1. as well as in Task 2. and listen to it in the loop.
The only information I could find was using Kotlin coroutines which apparently are made for working with the ViewModel
, but I am using Java.
What is the standard approach for this kind of problem? How do I communicate between the various threads and fragments safely?
Code:
void analyzeImage(ImageProxy imageProxy) { // loop called by CameraX
if (viewModel.analyzerState.getValue() == 'done') {
imageProxy.close();
return;
}
viewModel.analyzerState.postValue('extracting');
Data data = extractData(imageProxy);
imageProxy.close();
if (data.isValid()) {
viewModel.analyzerState.postValue('analyzing');
new DataAnalyzeTask.execute(data);
} else {
viewModel.analyzerState.postValue('idle');
}
}
// UI listens with
viewModel.analyzerState.observe(getViewLifecycleOwner(), state -> { /*...*/ } );
Solution
It took me some time and a lot of changes, but now I have a solution that is working for my purposes. So instead of using AsyncTask
and the ViewModel
for communication, I implemented my own Task
class which is basically just a Runnable
with some callbacks. These callbacks allow me to communicate with the UI. The UI then propagates data changes with the ViewModel
(on the main thread). To ensure I'm on the main thread, I execute the callbacks in the main thread handler:
Handler mainThreadHandler = HandlerCompat.createAsync(Looper.getMainLooper());
// in Task class:
mainThreadHandler.post(new Runnable() {
@Override
public void run() {
callback.onSomethingHappend(result);
}
});
My custom Task
s are executed in a thread pool via an ExecutorService
:
ExecutorService executor = Executors.newFixedThreadPool(4); // should be called only once when the application launches
executor.execute(new Task(data, callback));
I think there might be a simpler solution using ListenableFuture
(since I more or less reimplemented those with my Task
and TaskCallback
class), but I did not want to add Guava to my project for now.
To ensure I can cancel my tasks, I used AtomicBoolean
and set the value to false once I had some results:
atomicBoolean.compareAndSet(/*expectedValue=*/false, /*newValue=*/true);
Here are some useful resources:
https://developer.android.com/guide/background/asynchronous/java-threads https://developer.android.com/guide/background/asynchronous/listenablefuture
Answered By - mo5ch
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.