QUESTION:
I have an AsyncTask with a public ArrayList and I wanna know if I can dynamically update this ArrayList without stop the Task.
The thing is that my task load information about the elements in his internal array, at the same time my activity can load more elements so I would like to know if I can push theses new elements into the task's array instead of creating a new task.
SOLUTION:
MY TASK:
public class TaskGetMatchesDetails extends AsyncTask<Void, MatchDetails, Void> {
private FragmentHistory fragmentHistory;
//Dynamic Data, Array where we have to add and remove elements.
private ArrayList<Match> matchesArrayList;
//Constructor
public TaskGetMatchesDetails(FragmentHistory f) {
this.fragmentHistory = f;
this.matchesArrayList = new ArrayList<>();
}
//SYNCHRONIZED METHODS
public synchronized void addMatch(Match match) {
if (this.matchesArrayList != null) {
this.matchesArrayList.add(match);
Log.d("TASK DETAILS", "ADDED MATCH: " + match.getMatchId());
}
}
public synchronized Match getFirsMatchFromArrayList() {
if (matchesArrayList.size() > 0) {
return matchesArrayList.get(0);
}
return null;
}
public synchronized void removeMatchFromArrayList(Match match) {
if (this.matchesArrayList != null) {
this.matchesArrayList.remove(match);
Log.d("TASK DETAILS", "REMOVED MATCH: " + match.getMatchId());
}
}
#Override
protected Void doInBackground(Void... params) {
Match match;
MatchDetails matchDetails;
while (!isCancelled()) {
//If we have not work to do continue
if (matchesArrayList.size() <= 0) {
continue;
}
//Get the work for this iteration
Match m = getFirsMatchFromArrayList();
//If we have already calculated this data we just jump to other cycle
if (fragmentHistory.getMatchDetails(m.getMatchId()) != null) {
removeMatchFromArrayList(m);
continue;
}
matchDetails = new MatchDetails();
//TODO: Here we have to proccess the data.
publishProgress(matchDetails);
removeMatchFromArrayList(m);
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
fragmentHistory.setTaskGetMatchesDetails(null);
cancel(true);
}
#Override
protected void onProgressUpdate(MatchDetails... matches) {
super.onProgressUpdate(matches);
//We save the data calculated in this fragment
fragmentHistory.addMatchDetails(matches[0]);
}
#Override
protected void onCancelled() {
super.onCancelled();
}
}
CREATE THE TASK IN THE FRAGMENT onCreate method:
taskGetMatchesDetails = new TaskGetMatchesDetails(this);
taskGetMatchesDetails.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
TO ADD A NEW ELEMENT:
taskGetMatchesDetails.addMatch(m);
TO CLOSE THE TASK YOU JUST HAVE TO:
taskGetMatchesDetails.cancel(true);
The answers are No and Very Carefully. No- you can't do this with a list without additional work. You'd need to either protect access to the data with a semaphore or used a synchronized list. Otherwise you could concurrently access the list leading to incorrect partial state. This is bad, especially if both are updating the list at once, that can lead to memory access errors and even crashes.
If you use a synchronized list or semaphore, you can access it but you need to write your algorithm carefully to avoid problems if items are removed/added in midstream. But answering how to do that is awfully broad, you'd need to give us a more concrete algorithm to do so.
Related
i tried to return list from the url that i get with retrofit. it works and i get the data but it wont return.
this is my code
public List<MovieResponse> loadCourses() {
ArrayList<MovieResponse> list = new ArrayList<>();
ApiServices apiService =
NetworkClient.getRetrofitClient().create(ApiServices.class);
Call<MovieResult> call = apiService.getMovies();
call.enqueue(new Callback<MovieResult>() {
#Override
public void onResponse(Call<MovieResult> call, Response<MovieResult> response) {
if (response.body() != null) {
ArrayList<MovieResponse> movies = new ArrayList<>();
movies = response.body().getResults();
Log.d("",""+movies);
list.addAll(movies);
Log.d("",""+list);
}
}
#Override
public void onFailure(Call<MovieResult> call, Throwable t) {
// Log error here since request failed
Log.e("error", t.toString());
}
});
return list;
}
when i print list inside onResponse it works and there are the data. but when i return it or trying to print list outside onResponse for example below ArrayList<MovieResponse> list = new ArrayList<>(); it not show the data.
please help what is actually wrong with it. i really appreciate it.
The simplest way is to define your movies list directly inside activity or fragment(in other words, a field member of the class).
It's not a good idea to return data from an asynchronous method.
Change the return type of the loadCourses method to void and instantiate the filed movies inside onResponse().
public class SomeActivity extends AppCompatActivity {
private ArrayList<MovieResponse> movies = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_some);
}
public void loadCourses() {
ApiServices apiService =
NetworkClient.getRetrofitClient().create(ApiServices.class);
Call<MovieResult> call = apiService.getMovies();
call.enqueue(new Callback<MovieResult>() {
#Override
public void onResponse(Call<MovieResult> call, Response<MovieResult> response) {
if (response.body() != null) {
movies = response.body().getResults();
...
}
}
#Override
public void onFailure(Call<MovieResult> call, Throwable t) {
...
}
});
}
}
It is because you are making asynchronous call which is being handled by a separate thread. So after call.enqueue(), the main thread directly jumps to return statement without waiting for API response, that's why you are getting empty list.
Assuming your API takes 1 sec to respond,
just for an experiment, you can try adding a sleep() for 3 sec right before your return statement, it should return all the movies.
If you must return from the method then go for retrofit synchronous call.
To make a sync call create a new thread in main thread and make call from there, it is not allowed to make network call from main thread because it blocks the thread.
I am using AWS Android SDK to uploading image files to an S3 bucket. I do cropping operation in a thread which starts transfer of image file after cropping is done. I save all TransferObserver instances in a Map as defined and initialized below:
private Map<String, TransferObserver> transferObservers;
A() {
transferObservers = Collections.synchronizedMap(new LinkedHashMap<String, TransferObserver>());
}
public void add(Image image) {
handler.post(new Runnable() {
#Override
public void run() {
// Cropping
...
TransferObserver uploadObserver = transferUtility.upload(key, new File(localFilePath));
uploadObserver.setTransferListener(new TransferListener() {
photoTransferObservers.put(image.getPath(), uploadObserver);
}
}
}
The image can be deleted so thus its entry in the map:
public void deleteTransferRecord(String key) {
transferObservers.remove(key);
}
There is also another function that returns overall process of uploads:
private void notifyListeners() {
int completedUploadCount = 0;
for (TransferObserver transferObserver : transferObservers.values()) {
if (transferObserver.getState() == TransferState.COMPLETED) {
completedUploadCount++;
}
}
...
}
I got an exception -which I didn't take note- about removal attempt during iteration on transferObservers. How should I update my code to prevent any concurrency issues?
You could synchronize over transferObservers wherever you access it (whether addition, removal, or read). At that point you don't need the Collections.synchronizedMap.
Or, a simpler solution may be to simply copy the values before iterating in notifyListeners. That way removals or adds to the transfer observers won't cause a ConcurrentModificationException while iterating.
private void notifyListeners() {
int completedUploadCount = 0;
List<TransferObserver> observers = new ArrayList<>(transferObservers.values());
for (TransferObserver transferObserver : observers) {
if (transferObserver.getState() == TransferState.COMPLETED) {
completedUploadCount++;
}
}
...
}
I'm using a recursive method which implements the use of the SwingWorker class to do a research in one folder and all its subfolders - in the local hard drive.
Basically works fine but I'm stuck when I want to stop the SwingWorker method: when the user change the 'source folder' (I'm using a JTree - JAVAFX - to show all the folders in the local hard drive), I want to stop the current 'SwingWorker research' in that folder and start a new one, with the newest 'source path' results choosed from the user.
All the results of the research are stored in a private ObservableList - and updated everytime in the done() method, just by filling one TableView - JavaFX: so, when the user change the 'source path' I have to clean the results of the previous research.
Start method:
private static ObservableList<msg> data = FXCollections.observableArrayList();
private static SwingWorker<Void, Void> worker;
private static String currentFolder;
#Override
public void start(Stage primaryStage) throws Exception {
// TODO Auto-generated method stub
stage = primaryStage;
primaryStage.setScene(new Scene(createContent()));
styleControls();
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setMaximized(true);
primaryStage.setFullScreen(false);
primaryStage.show();
msgp = new MsgParser();
}
createContent() method- recursive function its called here:
public Parent createContent() {
tree.getSelectionModel().selectedItemProperty().addListener( new ChangeListener<Object>() {
#Override
public void changed(ObservableValue observable, Object oldValue,
Object newValue) {
TreeItem<File> selectedItem = (TreeItem<File>) newValue;
currentFolder = selectedItem.getValue().getAbsolutePath();
// I want to stop here the previous SwingWorker call : the tree
// ChangeListener event is called when the user change the
// source folder of the research, by selecting one TreeItem on it.
if(worker!= null)
worker.cancel(true);
//Here I clean previous results
data.clear();
TV.setItems(data);
//And I call again the method with the new source Folder
ListMail(new File(currentFolder));
}
});
}
ListMail() method: [recursive SwingWorker]
private void ListMail(File dir) {
worker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
File[] directoryListing = dir.listFiles();
if (directoryListing != null) {
for (File child : directoryListing) {
if(!worker.isCancelled()) {
if(child != null){
if(!child.isDirectory()) {
if(child.getAbsolutePath().substring(child.getAbsolutePath().lastIndexOf('.')+1).equals("msg")) {
Message message = msgp.parseMsg(child.getPath());
String percorsoMail = child.getAbsolutePath().toUpperCase();
if(message != null) {
String fromEmail = message.getFromEmail();
String fromName = message.getFromName();
String subject = message.getSubject();
String received = message.getDate().toString();
String name;
if(fromEmail != null)
name = fromName + "(" + fromEmail + ")";
else name = fromName;
msg Message = new msg(name, subject, received);
if(!data.contains(Message))
data.add(Message);
//I use the Platform.runLater to
// take count of the number of results found
//It updates the GUI - works fine
Platform.runLater(new Runnable() {
#Override public void run() {
if(data != null && data.size() > 0)
setStatusLabel(data.size());
else
setStatusLabel(0);
}
});
}
}
} else {
/**
* Recursive call here : I do the research
* for the subfolders
*/
ListMail(child);
}
} else {
}
}
}
}
return null;
}
// Update GUI Here
protected void done() {
// I refresh here the TableView: works fine on-the-fly added results
TableView.setItems(data);
TableView.refresh();
}
};
//This doesn't do anything
if(!worker.isCancelled())
worker.execute();
}
Basically, the issue is that the SwingWorker thread never stop, I'm thinking because of the recursive calls which creates new pid process at every run or something ?
Also by using a dedicated external button, which I prefer to avoid, gives no results:
refreshBtn.setOnAction(e -> {
//Handle clicks on refreshBtn button
worker.cancel(true);
});
After I click on TreeItem to change source-folder, it just delete all the ObservableList elements created at that moment, but the previous research don't stop.
Everything works fine instead if I wait the research its finished - but this can works only when I'm in a deep-level folder, while I can't obviously wait when the research start with the "C:\" folder.
Ok so that's here how I managed this by using javafx.concurrent.
Just to point my experience with this, it seems using a recursive background Task for potentially long computations, such as scanning the Whole local drive like in my example, it's very memory consuming - also because I stored some results of this background computation in static local variables to access them faster: the result was a data-structure (ObservableList) with over 5000+ instances of a custom class to represent that specific data computed and then the OutOfMemoryError message or the background thread just going like in 'stand-by' without any advice after running for long time (waiting for garbage collection?).
Anyway here's the code that sum up how I solved: the threads are correctly closed. By the way, sometimes, there's a little 'GUI delay' due to cleaning the GUI on the isCancelled() method check: the GUI swing between clear/not clear, because in my opinion it keeps get filled by the results of the previous tasks in the recursion.
private static BackgroundTask backgroundTask;
private static Thread thread;
tree.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Object>() {
#Override
public void changed(final ObservableValue observable, final Object oldValue, final Object newValue) {
//I close previous running background tasks if there's any
if (backgroundTask != null) {
while (backgroundTask.isRunning()) {
backgroundTask.cancel(true);
// reset GUI nodes here used to show results of the previous thread
}
}
backgroundTask = new BackGoundTask();
thread= new Thread(backgroundTask);
thread.setDaemon(true);
thread.start();
//This will be called only when latest recursion is finished, not at every run
backgroundTask.setOnSucceeded(e -> {});
}
});
BackgroundTask class:
public static class BackgroundTask extends Task<Object> {
// .. variables used by the task here
//constructor: initialize variables at every run of the Task
public BackgroundTask() {
}
#Override
protected Object call() throws Exception {
if (!isCancelled()) {
// ... Do all background work here
Platform.runLater(new Runnable() {
#Override
public void run() {
// GUI progress can goes here
}
});
//recursion here
if(something) {
//...
} else {
call();
}
} else {
//user want to cancel task: clean GUI nodes
}
return null;
}
}
Long story short, i want to upload multiple images to my server using Retrofit 2. i want to loop the process of sending single image based on the size of List image but asynchronously, so second upload only run if the first upload is succeeded. Some people tell me i should send an Array of File to my server instead and parse the array there, but i want to know if there is an error while uploading in client side or not. that way if there is an error (network problem) on first loop, second loop will stop running.
I really don't have clear idea as for how to do the task above, but here is a start.
public class UploadAllImages extends AsyncTask<Void, Void ,Void>{
#Override
protected Void doInBackground(Void... params) {
doSingleUpload(image);
return;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if(upload == succeeded){
new UploadAllImage().execute();
}
}
}
I would probably solve it like this:
public class UploadImages extends AsyncTask<Image, Integer, Boolean> {
#Override
protected Boolean doInBackground(Image... images) {
List<Image> remainingImages = new ArrayList<>(Arrays.asList(images));
while (!remainingImages.isEmpty()) {
boolean success = doSingleUpload(remainingImages.remove(0));
if (!success) {
return false;
}
}
return true;
}
#Override
protected void onPostExecute(Boolean success) {
// Handle the result of all uploads
}
}
I have written an Asynctask that loads 5 feeds from different URLs, writes all to the same file (via the WriteFeed method shown below), and then loads an activity only based on the first feed.
However, I am getting a android.os.TransactionTooLargeException: data parcel size 1052800 bytes, even though all five feeds together only have 70 feed items overall. Please note that I am launching the next activity onPostExecute only with the parsed first feed, and yet I get this Exception during the AsyncTask. How to run these feeds parallelly? Please help.
private class AsyncLoadXMLFeed extends AsyncTask<String, Void, Void> {
#Override
protected Void doInBackground(String... params) {
// Obtain feed
String feedlink1, feedlink2, feedlink3, feedlink4, feedlink5;
feedlink1=params[0];
feedlink2=params[1];
feedlink3=params[2];
feedlink4=params[3];
feedlink5=params[4];
Log.e("MY LINK",feedlink1);
try {
DOMParser myParser = new DOMParser();
feed = myParser.parseXml(feedlink1);
feed2 = myParser.parseXml(feedlink2);
feed3 = myParser.parseXml(feedlink3);
feed4 = myParser.parseXml(feedlink4);
feed5 = myParser.parseXml(feedlink5);
if (feed != null && feed2 != null && feed3 != null && feed4 != null && feed5 != null) {
WriteFeed(feed);
WriteFeed(feed2);
WriteFeed(feed3);
WriteFeed(feed4);
WriteFeed(feed5);
} else {
Log.e("FEED", "IS NULL");
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
startNextActivity(feed);
}
}
I think this is occurring because you are requesting 5 request at a time.
You can make some delay with every request like below:
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
feed = myParser.parseXml(feedlink1);
if(feed!=null)
WriteFeed(feed);
}
}, 1000);
Please let me know the result.
This is not caused by the parsing, it should be that the next activity is called with its intent data exceeding 1 MB size. The feed object which you are passing to startNextActivity() should be the main culprit.
This might be a bit slower to the end user but should help resolve the error. Instead of calling AsyncLoadXMLFeed on the calling activity, call it in the onCreate() of the called activity and modify the async as follows.
private class AsyncLoadXMLFeed extends AsyncTask<String, Void, Void> {
FeedListener fl;
interface FeedListener{
void onFeedParsed(Feed feed); //use appropriate class name
}
AsyncLoadXMLFeed(FeedListener fl){
this.fl=fl;
}
#Override
protected Void doInBackground(String... params) {
//No changes here
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
fl.onFeedParsed(feed); //the same feed object which was passed in startNewActivity.
}
on the activity which you are calling this async, you will get the feed object in onFeedParsed() then do the awesome stuff you plan to do with it.