Skip to content

Instantly share code, notes, and snippets.

@chRyNaN
Last active March 31, 2016 20:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chRyNaN/9299df5c98ed02ed2eb6ed970d8bdf28 to your computer and use it in GitHub Desktop.
Save chRyNaN/9299df5c98ed02ed2eb6ed970d8bdf28 to your computer and use it in GitHub Desktop.
Android classes to simplify performing tasks of different types using a job queue.
import android.os.Looper;
import java.util.ArrayList;
import java.util.List;
/**
* Created by chRyNaN on 3/31/2016. An abstract class implementation of the Job interface to handle some boilerplate code.
*/
public abstract class AbstractJob implements Job {
private List<JobCompletedListener> listeners;
private int priority;
/**
* Static method used to determine if we are currently on the user interface thread.
* @return
*/
public static boolean isOnUIThread(){
return Looper.myLooper() == Looper.getMainLooper();
}
@Override
public void onPostExecute() {
alertJobCompleted();
}
@Override
public int getPriority() {
return priority;
}
/**
* Sets the priority level of this task.
* @param priority The priority level of this task.
*/
void setPriority(int priority){
priority = (priority < MIN_PRIORITY) ? MIN_PRIORITY : priority;
priority = (priority > MAX_PRIORITY) ? MAX_PRIORITY : priority;
this.priority = priority;
}
/**
* An interface listener for listening to when a job event is finished.
*/
public interface JobCompletedListener{
void onJobCompleted();
}
/**
* Adds a JobCompletedListener this Job.
* @param l The JobCompletedListener to be added.
*/
public void addJobCompletedListener(JobCompletedListener l){
if(listeners == null){
listeners = new ArrayList<>();
}
listeners.add(l);
}
/**
* Removes a JobCompletedListener from this Job. Returns a boolean stating whether or not the
* listener was removed; true if it was removed or false if the list was null or doesn't contain
* the listener.
* @param l The JobCompletedListener to be removed.
* @return boolean A boolean value determining whether the listener was removed or not.
*/
public boolean removeJobCompletedListener(JobCompletedListener l){
if(listeners != null){
return listeners.remove(l);
}
return false;
}
/**
* A private method used internally to alert all the listener objects that the Job has finished.
*/
private void alertJobCompleted(){
for(JobCompletedListener l : listeners){
l.onJobCompleted();
}
}
}
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import java.util.Arrays;
import java.util.Date;
/**
* Created by chRyNaN on 3/31/2016. An AsyncTask implementation of the Job interface. This class takes an AsyncTask as a
* constructor argument and performs that task when the execute() method is called. The execute() method of this class
* will start the the AsyncTask on the UI thread regardless of the thread the method is called on; this should prevent
* any thread exceptions that might occur from starting an AsyncTask object on a different thread. Parameters traditionally
* supplied to the AsyncTask's execute() method should either be supplied in the constructor or the setParams() method
* before calling this class' execute() method.
*/
public class AsyncTaskJob<T, P, R> extends AbstractJob {
private volatile AsyncTask<T, P, R> task;
private long createdTime;
private T[] params;
private Thread thread;
/**
* Default constructor.
* @param task The task to be performed.
*/
public AsyncTaskJob(AsyncTask<T, P, R> task){
if(task == null){
throw new IllegalArgumentException("AsyncTask parameter in AsyncTaskJob constructor must not be null.");
}
this.task = task;
this.createdTime = new Date().getTime();
setPriority(MIN_PRIORITY);
}
/**
* Constructor specifying the priority level of this task.
* @param task The task to be performed.
* @param priority The priority level of this task.
*/
public AsyncTaskJob(AsyncTask<T, P, R> task, int priority){
if(task == null){
throw new IllegalArgumentException("AsyncTask parameter in AsyncTaskJob constructor must not be null.");
}
this.task = task;
setPriority(priority);
this.createdTime = new Date().getTime();
}
/**
* Constructor specifying the priority level and parameters of this task.
* @param task The task to be performed.
* @param priority The priority level of this task.
* @param params The parameters needed to execute this task.
*/
public AsyncTaskJob(AsyncTask<T, P, R> task, int priority, T... params){
if(task == null){
throw new IllegalArgumentException("AsyncTask parameter in AsyncTaskJob constructor must not be null.");
}
this.task = task;
setPriority(priority);
this.params = params;
this.createdTime = new Date().getTime();
}
@Override
public long getCreatedTime() {
return createdTime;
}
@Override
public long getId() {
return this.hashCode();
}
@Override
public void cancel() {
if(thread != null && thread.isAlive() && !thread.isInterrupted()){
thread.interrupt();
thread = null;
}
if(task != null && !task.isCancelled()){
task.cancel(true);
}
}
@Override
public void setPriority(int priority){
super.setPriority(priority);
}
/**
* Sets the parameters needed in order to perform this task.
* Returns this class instance for method chaining.
* @param params The parameters needed to execute this task.
* @return AsyncTaskJob This class instance.
*/
public AsyncTaskJob<T, P, R> setParams(T... params){
this.params = params;
return this;
}
@Override
public void execute() {
if(isOnUIThread()){
if(params == null || params.length == 0){
task.execute();
}else{
task.execute(params);
}
//In order to know when the task has finished we have to wait on the get method
//to return. But we don't want to do this on the UI thread because what would be
//the point of the AsyncTask at all then? So, we spawn yet another thread to wait
//on this task to finish.
thread = new Thread(new Runnable() {
@Override
public void run() {
if(task.getStatus().equals(AsyncTask.Status.RUNNING)){
try {
task.get();
}catch(Exception e){
e.printStackTrace();
}
Handler h = new Handler(Looper.getMainLooper());
Runnable r = new Runnable() {
@Override
public void run() {
onPostExecute();
}
};
h.post(r);
}
}
});
thread.start();
}else{
Handler h = new Handler(Looper.getMainLooper());
Runnable r = new Runnable() {
@Override
public void run() {
execute();
}
};
h.post(r);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AsyncTaskJob<?, ?, ?> that = (AsyncTaskJob<?, ?, ?>) o;
if (createdTime != that.createdTime) return false;
if (getPriority() != that.getPriority()) return false;
if (task != null ? !task.equals(that.task) : that.task != null) return false;
// Probably incorrect - comparing Object[] arrays with Arrays.equals
return Arrays.equals(params, that.params);
}
@Override
public int hashCode() {
int result = task != null ? task.hashCode() : 0;
result = 31 * result + (int) (createdTime ^ (createdTime >>> 32));
result = 31 * result + getPriority();
result = 31 * result + (params != null ? Arrays.hashCode(params) : 0);
return result;
}
}
/**
* Created by chRyNaN on 3/31/2016. An interface representing a task that can be performed via the execute() method.
* Any parameters or fields needed to execute the task must be handled by the implementation. Usually, this task will
* be performed off of the UI thread. This interface differs from the java.util.concurrent.Executor interface in that
* it doesn't take a Runnable as a parameter in the execute() method so it can handle many different types of tasks by
* having different implementation classes.
*/
public interface Executor {
/**
* Performs the task.
*/
void execute();
}
/**
* Created by chRyNaN on 3/31/2016. An interface representing a task, or job, that needs to be performed.
* Usually, an implementation of this interface will perform the task off of the UI thread for efficiency.
* This interface extends the Executor interface where a call to the execute() method will perform the
* actual task. An implementation of this interface will often be given to an Executor Service that will
* perform all Jobs given to it according to its implemented precedence system. Not all methods will be
* regarded by every Executor Service but it is important to properly implement them nonetheless.
*/
public interface Job extends Executor {
/**
* Represents the lowest possible priority number.
*/
int MIN_PRIORITY = 1;
/**
* Represents the highest possible priority number.
*/
int MAX_PRIORITY = 10;
/**
* Retrieves the long representation of the time this Job was created.
* @return long The time this Job was created.
*/
long getCreatedTime();
/**
* Retrieves this Jobs unique ID.
* @return long The unique ID associated with this Job.
*/
long getId();
/**
* Retrieves the priority level of the Job. A priority level equal to or less than 1 is the lowest
* priority level, MIN_PRIORITY. And a priority level equal to or greater than 10 is the highest
* priority level, MAX_PRIORITY.
* @return
*/
int getPriority();
/**
* Executed after the task has been performed or has been cancelled. This method should be executed
* on the UI thread.
*/
void onPostExecute();
/**
* Cancels this Job if the Job has already begun and has not already finished.
* This doesn't always succeed in canceling a Job. For instance, if a Job implementation
* that supports a Runnable on a different Thread may not be able to properly stop the
* Thread (and therefore the Runnable) until the task has finished. This is due to the
* nature of Threads and the choice to deprecate methods associated with that task
* (stop, destroy, etc.) because of unsafe issues that doing so incurred. In such a
* scenario, the Threads interrupt() method should be called to attempt to stop the
* Thread, otherwise the task will continue until it has finished.
*/
void cancel();
}
import java.util.PriorityQueue;
import java.util.Queue;
/**
* Created by chRyNaN on 3/31/2016. A class that contains and executes Jobs in the order they come in. A Job is only
* executed when the previous Job has finished its tasks.
*/
public class JobQueue {
private static volatile JobQueue singleton = null;
private Queue<Job> q;
private volatile Job current;
private boolean shouldContinue = false;
/**
* Private constructor to allow only internal instantiation of this object.
*/
private JobQueue(){
q = new PriorityQueue<>();
}
/**
* Retrieves, creating if needed, the singleton instance of this class.
* @return JobQueue The singleton instance of this class.
*/
public static JobQueue getInstance(){
if(singleton == null){
singleton = new JobQueue();
}
return singleton;
}
/**
* Adds a Job to this queue to be performed. If this is the first Job added, the Job will be
* executed immediately.
* @param job The Job to be added to this queue.
*/
public void add(AbstractJob job){
q.add(job);
if(q.size() == 1){
shouldContinue = true;
performNextTask();
}
}
/**
* Private internal method that executes the next Job once the previous has finished.
*/
private void performNextTask(){
if(q.size() > 0 && shouldContinue){
AbstractJob j = (AbstractJob) q.remove();
j.addJobCompletedListener(new AbstractJob.JobCompletedListener() {
@Override
public void onJobCompleted() {
performNextTask();
}
});
j.execute();
current = j;
}
}
/**
* Retrieves the current Job being performed.
* @return
*/
public Job getCurrent(){
return current;
}
/**
* Cancels the current Job being performed.
*/
public void cancelCurrent(){
if(current != null){
current.cancel();
}
}
/**
* Cancels all Jobs from being performed and clears the queue.
*/
public void cancelAll(){
shouldContinue = false;
cancelCurrent();
q.clear();
singleton = null;
}
}
/**
* Created by chRyNaN on 3/31/2016. A simple implementation of a Runnable that allows the addition of a parameter field (type array).
*/
public abstract class ParamRunnable<P> implements Runnable {
private P[] params;
public ParamRunnable(P... params){
this.params = params;
}
public P[] getParams() {
return params;
}
public void setParams(P... params) {
this.params = params;
}
}
import android.os.Handler;
import android.os.Looper;
import java.util.Arrays;
import java.util.Date;
/**
* Created by chRyNaN on 3/31/2016. A Runnable implementation of the Job interface. This class takes a Runnable as a
* constructor argument and performs task off the UI thread when this class' execute() method is called.
*/
public class RunnableJob<P> extends AbstractJob {
private Runnable run;
private long createdTime;
private P[] params;
private Thread thread;
/**
* Default constructor.
* @param run The Runnable to be performed.
*/
public RunnableJob(Runnable run){
if(run == null){
throw new IllegalArgumentException("Runnable parameter in RunnableJob constructor must not be null.");
}
this.run = run;
this.createdTime = new Date().getTime();
setPriority(MIN_PRIORITY);
}
/**
* Constructor specifying the priority level of this Job.
* @param run The Runnable to be performed.
* @param priority The priority level of this Job.
*/
public RunnableJob(Runnable run, int priority){
if(run == null){
throw new IllegalArgumentException("Runnable parameter in RunnableJob constructor must not be null.");
}
this.run = run;
this.createdTime = new Date().getTime();
setPriority(priority);
}
/**
* Constructor specifying the priority level and parameters of this Job.
* The parameters are only used if the Runnable provided is an instance of a
* ParamRunnable object.
* @param run The Runnable to be performed.
* @param priority The priority level of this Job.
* @param params The parameters needed to perform this Job.
*/
public RunnableJob(Runnable run, int priority, P... params){
if(run == null){
throw new IllegalArgumentException("Runnable parameter in RunnableJob constructor must not be null.");
}
this.run = run;
this.createdTime = new Date().getTime();
setPriority(priority);
this.params = params;
}
@Override
public long getCreatedTime() {
return createdTime;
}
@Override
public long getId() {
return this.hashCode();
}
@Override
public void setPriority(int priority){
super.setPriority(priority);
}
@Override
public void cancel() {
if(thread != null && thread.isAlive() && !thread.isInterrupted()){
thread.interrupt();
thread = null;
onPostExecute();
}
}
/**
* Sets the parameters needed in order to perform this task.
* Returns this class instance for method chaining.
* @param params The parameters needed to execute this task.
* @return RunnableJob This class instance.
*/
public RunnableJob<P> setParams(P... params){
this.params = params;
return this;
}
@Override
public void execute() {
if(isOnUIThread()){
if((run instanceof ParamRunnable) && (params != null || params.length > 0)){
((ParamRunnable<P>) run).setParams(params);
}
cancel();
thread = new Thread(new Runnable() {
@Override
public void run() {
run.run();
onPostExecute();
}
});
thread.start();
}else{
Handler h = new Handler(Looper.getMainLooper());
Runnable r = new Runnable() {
@Override
public void run() {
execute();
}
};
h.post(r);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RunnableJob<?> that = (RunnableJob<?>) o;
if (createdTime != that.createdTime) return false;
if (getPriority() != that.getPriority()) return false;
if (run != null ? !run.equals(that.run) : that.run != null) return false;
// Probably incorrect - comparing Object[] arrays with Arrays.equals
return Arrays.equals(params, that.params);
}
@Override
public int hashCode() {
int result = run != null ? run.hashCode() : 0;
result = 31 * result + (int) (createdTime ^ (createdTime >>> 32));
result = 31 * result + getPriority();
result = 31 * result + (params != null ? Arrays.hashCode(params) : 0);
return result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment