Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Waiting for multiple callbacks in Android

In Java threads, you could have some number of threads in a list, start them off, and have a main thread join one, then another, going through and waiting for all processes to complete before moving on.

In other models, I'm not sure how you would do that. Take the RootTools 3.0 Command class for example. You create a Command which has three methods, commandOutput, commandFinished, commandTerminated, and while you can use a callback to do something at the end of a process, I don't know how you would wait for multiple processes (For example, going through listing of several directories and summing the file-sizes).

I believe the Android Asynctask would have a similar problem - you can easily make a callback, but there is no way to wait for several tasks. Unless I'm missing something?

like image 493
NoBugs Avatar asked Jul 02 '13 05:07

NoBugs


1 Answers

##Introduction

I had gone through this topic for one of my previous project and found different solutions to the problem .I finally used the first method for the project because it was the best suited one for that specific project.

Let ImageDownloader be a class to download an image from a URL asynchronously and it has the following properties.

  • An interface - ImageDownloadCallback to get the callback when the task is completed. It has two methods
  • void onSuccess(String imagePath) : called when the task is completed successfully.
  • void onFailure() : called when the task is failed to complete.
  • A method - download(String url, ImageDownloadCallback callback) to start the download task

PauseModeCallbackHandler, ChainModeCallbackHandler and ParallelModeCallbackHandler are wrapper classes of the callbacks for the three methods respectively. You can customize them according to what task you want to do.




##Method 1: Execute the tasks one by one by pausing the starter thread.

Illustration of solution 1

Pros
Gets the results in the original thread

Cons
Need to make the thread waiting


###ThreadLockedTask You can use this class to make the thread waiting until the result is obtained.
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author Ahamad Anees P.A
 * @version 1.0
 * @param <T> type
 */
public class ThreadLockedTask<T> {

    private AtomicReference<ResultWrapper<T>> mReference;

    public ThreadLockedTask() {
        mReference = new AtomicReference<>(new ResultWrapper<T>());
    }

    public T execute(Runnable runnable) {
        runnable.run();
        if (!mReference.get().mIsSet)
            lockUntilSet();
        return mReference.get().mResult;
    }

    private void lockUntilSet() {
        synchronized (this) {
            while (!mReference.get().isSet()) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public void setResult(T result) {
        synchronized (this) {
            ResultWrapper<T> wrapper = mReference.get();
            wrapper.setResult(result);
            wrapper.setIsSet(true);
            notify();
        }
    }

    public static class ResultWrapper<T> {
        private boolean mIsSet;
        private T mResult;

        public boolean isSet() {
            return mIsSet;
        }

        public T getResult() {
            return mResult;
        }

        void setIsSet(boolean isCompleted) {
            this.mIsSet = isCompleted;
        }

        void setResult(T result) {
            this.mResult = result;
        }
    }

}

###Sample

import java.util.ArrayList;
import java.util.List;

public class PauseModeCallbackHandler {

    // List of results
    private static List<String> results;

    public static void start(final List<String> urls, final ImageDownloader.ProgressUpdateListener listener) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                results = new ArrayList<>();

                // Do tasks one by one
                for (final String url :
                        urls) {

                    //Here the result is a String. Change "String" in the following two lines for other datatypes.
                    final ThreadLockedTask<String> task = new ThreadLockedTask<>();
                    final String imagePath = task.execute(new Runnable() {
                        @Override
                        public void run() {
                            //Start the task here
                            ImageDownloader.getInstance(listener).download(url,
                                    new ImageDownloader.ImageDownloadCallback() {
                                        @Override
                                        public void onSuccess(String imagePath) {
                                            //Set the result on success
                                            task.setResult(imagePath);
                                        }

                                        @Override
                                        public void onFailure() {
                                            //Set result as null on failure
                                            task.setResult(null);
                                        }
                                    });
                        }
                    });

                    if (imagePath!=null)
                        results.add(imagePath);

                }

                afterCallbacks();
            }
        }).start();
    }

    private PauseModeCallbackHandler() {}

    private static void afterCallbacks() {
        // All tasks completed. List "results" now holds the result

        DemoActivity.isTasksInProgress = false;
    }
}




##Method 2: Execute tasks from the callback of the previous one like a chain reaction.

Illustration of solution 2


Sample

import java.util.ArrayList;
import java.util.List;

public class ChainModeCallbackHandler implements ImageDownloader.ImageDownloadCallback {

    // List of args to start the task. Use pojo classes if your task has multiple args
    private static List<String> urls;

    // List of results
    private static List<String> results;

    // Optional.
    private static ImageDownloader.ProgressUpdateListener progressUpdateListener;

    // Leave it as it is
    private int index;

    public static void start(List<String> urls, ImageDownloader.ProgressUpdateListener listener) {
        ChainModeCallbackHandler.urls = urls;
        results = new ArrayList<>();
        progressUpdateListener = listener;

        //Start with the first task
        ImageDownloader.getInstance(listener).download(urls.get(0), new ChainModeCallbackHandler(0));
    }

    private ChainModeCallbackHandler(int index) {
        this.index = index;
    }

    @Override
    public void onSuccess(String imagePath) {
        results.add(imagePath);
        afterCallback();
    }

    @Override
    public void onFailure() {
        afterCallback();
    }

    private void afterCallback() {
        int nextIndex = index+1;
        if (nextIndex<urls.size()) {
            //Tasks are not completed yet. Do next task
            ImageDownloader.getInstance(progressUpdateListener).download(urls.get(nextIndex),
                    new ChainModeCallbackHandler(nextIndex));
        } else {
            // All tasks completed. List "results" now holds the result

            DemoActivity.isTasksInProgress = false;
        }
    }
}




##Method 3: Execute tasks in parallel.

Illustration of solution 3

Pros
Parallel execution helps to save time sometimes


Sample

import java.util.ArrayList;
import java.util.List;

public class ParallelModeCallbackHandler {

    // List of args to start the task. Use pojo classes if your task has multiple args
    private static List<String> urls;

    // List of results
    private static List<String> results;

    // Leave it as it is
    private static int count;

    public static void start(List<String> urls, ImageDownloader.ProgressUpdateListener listener) {
        ParallelModeCallbackHandler.urls = urls;
        results = new ArrayList<>();
        count = 0;

        // Start all tasks
        for (String url :
                urls) {
            //Replace with your task and its callback
            ImageDownloader.getInstance(listener).download(url, new ImageDownloader.ImageDownloadCallback() {
                @Override
                public void onSuccess(String imagePath) {
                    results.add(imagePath);
                    afterCallback();
                }

                @Override
                public void onFailure() {
                    afterCallback();
                }
            });
        }
    }

    private ParallelModeCallbackHandler() {}

    private static void afterCallback() {
        if (++count==urls.size()) {
            // All tasks completed. List "results" now holds the result

            DemoActivity.isTasksInProgress = false;
        }
    }
}
like image 170
Bertram Gilfoyle Avatar answered Sep 25 '22 15:09

Bertram Gilfoyle