Last active
March 31, 2016 20:29
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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