Mbio

Technology & business for life & change

Using Runnables, Queues and Executors to Perform Tasks in Background Threads in Android

with one comment

This post is meant to be an extension of There’s life after Asynctasks in Android but can be perfectly applied out of this context in whatever situation fits better to your needs.
Note that in the end it is all about getting things done in a worker thread in a known order.

If ants, food, routes, lanes and winter are not familiar words to you, don’t panic. This time we’ll work away from analogies by simulating the whole thing for real. We are the queen of ants (our Activity), and we want to get some of our ants to go and get some food for the winter. We’ll enqueue them in a pool of threads so that they’ll do the job transparently for us, hence we can pay attention to other important stuff. After that and if we have some free time we’ll be notified with the amount of food brought by each ant and the total gathered so far.

Mmmm, I know it sounds weird, let’s clarify that a bit.

Worker ants = Requests. This is an an object which will implement Callable in our case (Runnable will do the job as well, but won’t say anything after completion) and Comparable (in case you need to prioritize between your tasks). Inside of it we’ll need to override the “call()” method (“run()” for Runnables) to set the operation/s we need to perform. You can complement it as much as you need (other methods, create other objects that inherit from this, create logs to control the status, etc). This will be our measurement unit for a task/s to be performed.

This is how the object AntRequest object looks like in our example:

/**
 * AntRequest.java
 *
 * @author	Jose Luis Ugia
 */

package ugia.ants.engine.core;

import java.util.concurrent.Callable;

import android.util.Log;

public class AntRequest implements Callable, Comparable {

    private static final int ANT_LOAD_WEIGHT_RATIO = 35;
    private static final int ANT_AVERAGE_SPEED_METERS_PER_HOUR = 300;

    private final double antWeight_Mg;
    private double foodWillCarry_Mg;
    private final int distance_M;
    private int timeTaken_H;

    private final int ant_no;

    public int priority;

    /**
     * Class constructor
     *
     * @param c
     *            Execution context
     */
    public AntRequest(double _weight, int _distance, int _ant_no) {
	antWeight_Mg = _weight;
	distance_M = _distance;
	ant_no = _ant_no;
    }

    /**
     * Method that executes the task
     *
     * @return Request status code
     */
    @Override
    public Double call() throws Exception {

	// Calculate the amount of food this ant will carry
	foodWillCarry_Mg = antWeight_Mg * ANT_LOAD_WEIGHT_RATIO;

	// Add +-25% error
	foodWillCarry_Mg += foodWillCarry_Mg * (Math.random() / 2 - 0.25);

	// Calculate the time taken to go back and forth and sleep the thread
	timeTaken_H = distance_M / ANT_AVERAGE_SPEED_METERS_PER_HOUR * 3600;

	// Add +-12.5% error
	timeTaken_H += timeTaken_H * (Math.random() / 4 - 0.125);

	// Seconds will be taken as milliseconds/10 for testing reasons
	Thread.sleep(timeTaken_H / 10);

	Log.v("Ant #" + (ant_no + 1) + " Extra food (mg)", Double.toString(foodWillCarry_Mg));

	return foodWillCarry_Mg;
    }

    /**
     * Compares requests priority
     *
     * @param another
     *            Comparing request
     * @return Relative order against another
     */
    @Override
    public int compareTo(AntRequest another) {
	if (priority < another.priority) 	    return -1; 	else if (priority > another.priority)
	    return 1;
	else
	    return 0;
    }

}

Done. Now…what is really our food?

Food = AnListenableFuture. If we trust on our worker ants we’ll expect them to bring some food among their mandibles. That’s our Future, which is nothing else but any kind of defined object expected as a result of our request at the point we enqueue it in our thread pool executor. Since we need to make this happen for Callables and eventually be able to prioritize among requests we’ll make our new object inherit from ListenableFuture (available as part of Google Guava libraries), adding the necessary logic for our case.
It might look something like this:

package ugia.ants.engine.object;

import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;

import com.google.common.util.concurrent.ExecutionList;
import com.google.common.util.concurrent.ListenableFuture;

public final class AntListenableFuture extends FutureTask implements ListenableFuture,
	Comparable<AntListenableFuture> {

    // The execution list to hold our listeners.
    private final ExecutionList executionList = new ExecutionList();

    private final Object object;

    /**
     * Creates a {@code ListenableFutureTask} that will upon running, execute
     * the given {@code Callable}.
     *
     * @param callable
     *            the callable task
     * @since 10.0
     */
    public static  AntListenableFuture create(Callable callable) {
	return new AntListenableFuture(callable);
    }

    /**
     * Creates a {@code ListenableFutureTask} that will upon running, execute
     * the given {@code Runnable}, and arrange that {@code get} will return the
     * given result on successful completion.
     *
     * @param runnable
     *            the runnable task
     * @param result
     *            the result to return on successful completion. If you don't
     *            need a particular result, consider using constructions of the
     *            form:
     *            {@code ListenableFuture<?> f = ListenableFutureTask.create(runnable,
     *     null)}
     * @since 10.0
     */
    public static  AntListenableFuture create(Runnable runnable, V result) {
	return new AntListenableFuture(runnable, result);
    }

    private AntListenableFuture(Callable callable) {
	super(callable);

	object = callable;
    }

    private AntListenableFuture(Runnable runnable, V result) {
	super(runnable, result);

	object = runnable;
    }

    @Override
    public void addListener(Runnable listener, Executor exec) {
	executionList.add(listener, exec);
    }

    /**
     * Internal implementation detail used to invoke the listeners.
     */
    @Override
    protected void done() {
	executionList.execute();
    }

    @Override
    @SuppressWarnings("unchecked")
    public int compareTo(AntListenableFuture o) {
	if (this == o)
	    return 0;
	if (o == null)
	    return -1; // high priority
	if (object != null && o.object != null) {
	    if (object.getClass().equals(o.object.getClass())) {
		if (object instanceof Comparable)
		    return ((Comparable</pre>

Route = AntServiceManager. Now let’s set route to get our food and decide the number of lanes we’ll enable for our ants to get to it. Remember we wanted to get this transparently to our activity or our app. So basically we just need a bound service that works in the background. For the purpose of this test we’ll set up three lanes by using a ListeningExecutorService (which is nothing but an ExecutorService which returns ListenableFutures instead of Futures. We’ll close the circle by attaching a LinkedBlockingQueue to our Executor:

package ugia.ants.engine.services;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import ugia.ants.application.AntsApplication;
import ugia.ants.engine.core.AntRequest;
import ugia.ants.engine.object.AntListenableFuture;
import ugia.ants.engine.object.AntUpdateCallback;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

public class AntServiceManager extends Service {

 private final String LOG_TAG = this.getClass().getSimpleName();

 private static final int ANT_AVERAGE_WEIGHT_IN_MG = 3;

 private double totalAmountOfFood;

 private final IBinder mBinder = new AntsBinder();

 private LinkedBlockingQueue mQueue;

 private volatile ListeningExecutorService mExecutorService;

 public class AntsBinder extends Binder {
 public AntServiceManager getService() {
 return AntServiceManager.this;
 }
 }

 /* METHODS FOR CLIENTS */
 public ListenableFuture enqueueRequest(final AntRequest request) {
 AntListenableFuture ftask = AntListenableFuture.create(request);
 mExecutorService.execute(ftask);

 return ftask;
 }

 public void startSendingAnts(Context _c, final int numberOfAnts, final int distanceToFood,
 final AntUpdateCallback updateCallback) {

 totalAmountOfFood = 0;
 stopSendingAnts();

 AntRequest request;
 ListenableFuture ftask;

 for (int i = 0; i < numberOfAnts; i++) {
 request = new AntRequest(ANT_AVERAGE_WEIGHT_IN_MG + (ANT_AVERAGE_WEIGHT_IN_MG
 * (Math.random() / 10 - 0.05)), distanceToFood, i);

 ftask = AntsApplication.antsService.enqueueRequest(request);
 Futures.addCallback(ftask, new FutureCallback() {

 @Override
 public void onFailure(Throwable arg0) {
 Log.w(LOG_TAG, "Unknown error");
 return;
 }

 @Override
 public void onSuccess(Double arg0) {

 updateCallback.update(totalAmountOfFood += arg0);
 }
 });
 }

 }

 public void stopSendingAnts() {
 if (mExecutorService != null) {
 mExecutorService.shutdownNow();
 mExecutorService = MoreExecutors.listeningDecorator(new ThreadPoolExecutor(3, 5, 15,
 TimeUnit.SECONDS, mQueue));
 }
 }

 @Override
 public void onCreate() {
 super.onCreate();

 mQueue = new LinkedBlockingQueue();
 mExecutorService = MoreExecutors.listeningDecorator(new ThreadPoolExecutor(3, 5, 15,
 TimeUnit.SECONDS, mQueue));
 }

 @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
 return super.onStartCommand(intent, flags, startId);
 }

 @Override
 public void onDestroy() {
 super.onDestroy();
 }

 @Override
 public IBinder onBind(Intent arg0) {
 return mBinder;
 }

}

The Queen = QueenAntActivity. Finally we need to give a form to our beloved queen, which will be represented by an Activity. As a proper member of the royalty she will decide the number of ants to be assigned to this campaign and the distance to the food source. Ultimately we’ll receive the updated information whenever the activity is available:

package ugia.ants;

import ugia.ants.application.AntsApplication;
import ugia.ants.engine.object.AntUpdateCallback;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class QueenAntActivity extends Activity {

    private EditText nAntsET;
    private EditText distanceET;
    private TextView totalFoodView;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);

	nAntsET = (EditText) findViewById(R.id.nAntsEditText);
	distanceET = (EditText) findViewById(R.id.distanceEditText);
	totalFoodView = (TextView) findViewById(R.id.totalFoodView);

    }

    public void startAnts(View v) {

	if (AntsApplication.bound) {

	    int nAnts = !nAntsET.getText().toString().equals("") ? Integer.parseInt(nAntsET
		    .getText().toString()) : 0;
	    int distance = !distanceET.getText().toString().equals("") ? Integer
		    .parseInt(distanceET.getText().toString()) : 0;

	    AntsApplication.antsService.startSendingAnts(this, nAnts, distance,
		    new AntUpdateCallback() {

			@Override
			public void update(final Double result) {
			    runOnUiThread(new Runnable() {

				@Override
				public void run() {
				    totalFoodView.setText("Total food gathered (mg): "
					    + Math.round(result));
				}
			    });
			}
		    });
	}
    }
}

The implementation for our callback and the Application to start the service as soon as our app gets launched are included in the source code.

Now it’s time for you to set up your own colony.
I hope the “Ants approach” enables you to take more control over certain operations or tasks you might need to perform in a (pool of) background thread(s) which can be ordered and prioritized as they are enqueued and return a result whenever it is obtained.

Now that you have the basis just go and make your app ant-friendly…
…because, yes! The winter is always hard.

Source

Written by joseluisugia

3, August.2012 at 09:43

One Response

Subscribe to comments with RSS.

  1. Great explanation! Thanks a lot for putting this up.. It cleared many doubts that I had.. šŸ™‚

    noder

    25, February.2014 at 18:15


Leave a comment