preface

Personally, I think the use of queue can be used in many places in project development, so I will record the process of how to encapsulate a queue. On the whole, it is not difficult, but after all, the ability is limited. If you have good suggestions or opinions, you are welcome to put them forward.

Demand background

In project development, will often encounter some need to perform the function of the queue, such as hair dynamic upload more pictures, need to upload one, such as animation studio needs to send or receive messages needs to show, need a an animation to show and so on scene, this time will be easy to think of use queue to achieve, But I think a lot of people will just make a list of tasks to perform, and then recursively iterate through the list, like the following code:

List<String> List = new ArrayList<>(); // Execute the task. Private voiddoTask() {
    if (list.size() > 0) {
        String task = list.get(0);
        doSoming(task);
        list.remove(0);
        doTask(); }}Copy the code

First of all, this way of implementation is able to complete the required functions, in the face of some simple scenarios are relatively easy to think of and simple implementation. However, facing a more complex scenario has many disadvantages, especially in some ancestral projects, who can never tell how messy the code is, then it is very difficult to maintain (I have experienced this in real projects). So encapsulating a queue to accomplish this function is a more practical and necessary means.

First take a look at what functionality the queue to encapsulate needs:

  1. In practice, there are probably two types of tasks that we can perform. One has a specific execution time, such as displaying 10 animations in a row for 5 seconds each. One is that there is no specific execution time, such as uploading 10 images in a row, and the completion time of each upload task is not known until the successful upload callback. So the first thing about queuing is that each task is compatible with both, and of course one is executed before the next, queued for execution.
  2. Since the queue execution, of course, there will be a priority, so each task can be set to the priority, the queue can be queued to execute the task according to the priority.

As for which queue to choose, I’ve chosen PriorityBlockingQueue, and the thing about this queue is that it has to store objects that implement Comparable, and it’s a blocking queue, and the rest of it, or those of you who don’t know anything about it, can figure it out, and then it’s all encapsulated.

1. Define an enumeration class TaskPriority that defines the priority of tasks.

Public enum TaskPriority {LOW, // LOW DEFAULT,// common HIGH, // HIGH}Copy the code

There are three priorities, as shown in the comment, and their relationship is: LOW<DEFAULT<HIGH

2. Implementation policies for the uncertain and definite execution time of queue tasks

  1. In this case, the execution time of the task is determined. It is relatively simple to set a time duration for the task. When the task starts to execute, it will block and wait, and when the time reaches, it will release the next task.
  2. In case the task execution time is uncertain, we need to manually release the task in the completion callback, so we are going to use a PriorityBlockingQueue, because it has this characteristic: If the queue is empty, it will always block when its take() method is called, and it will not block when the list is not empty. So when the task starts executing, we call the take() method, wait until our completion callback comes, then add a value to it manually, release the block, and proceed to the next task.

3. After determining the implementation policy for the two cases of tasks, define an interface to define what each task needs to do

Public Interface ITask extends Comparable<ITask> {void enQueue (); public interface ITask extends Comparable<ITask> {// Insert the task into the queue void enqueue(); // Perform the specific task method voiddoTask(); // Void finishTask(); // Set the task priority ITasksetPriority(TaskPriority mTaskPriority); TaskPriority getPriority(); // This method is used to mark the insertion order when the priorities are the same: first in, first outsetSequence(int mSequence); Int getSequence(); Boolean getStatus(); Boolean getStatus(); // Set the execution time of each task. This method is used in the case of task execution time determination ITasksetDuration(int duration); // Get the execution time of each task int getDuration(); Void blockTask() throws Exception; // blockTask execution. Void blockTask() throws Exception; Void unLockBlock(); // Unblock the task. Void unLockBlock(); }Copy the code

The PriorityBlockingQueue interface basically defines some essential methods, and because of the PriorityBlockingQueue feature, the interface inherits from Comparable to implement priority queuing. See the comments for specific method functions.

4. Encapsulate the basic functionality of PriorityBlockingQueue

public class BlockTaskQueue {
    private String TAG = "BlockTaskQueue"; private AtomicInteger mAtomicInteger = new AtomicInteger(); Private final BlockingQueue<ITask> mTaskQueue = new PriorityBlockingQueue<>(); privateBlockTaskQueue() {} private static class BlockTaskQueueHolder {private final static BlockTaskQueue INSTANCE = new BlockTaskQueue(); } public static BlockTaskQueuegetInstance() {
        returnBlockTaskQueueHolder.INSTANCE; } /** * The queue will be inserted in order of priority as defined by the Task's compare() method because each Task implements the Comparable interface ** ** Public <T extends ITask> int add(T task) {if(! mTaskQueue.contains(task)) { task.setSequence(mAtomicInteger.incrementAndGet()); mTaskQueue.add(task); Log.d(TAG,"\n add task " + task.toString());
        }
        return mTaskQueue.size();
    }

    public <T extends ITask> void remove(T task) {
        if (mTaskQueue.contains(task)) {
            Log.d(TAG, "\n" + "task has been finished. remove it from task queue");
            mTaskQueue.remove(task);
        }
        if (mTaskQueue.size() == 0) {
            mAtomicInteger.set(0);
        }
    }

    public ITask poll() {
        return mTaskQueue.poll();
    }

    public ITask take() throws InterruptedException {
        return mTaskQueue.take();
    }

    public void clear() {
        mTaskQueue.clear();
    }

    public int size() {
        returnmTaskQueue.size(); }}Copy the code

Here is a simple encapsulation, see the comments for an explanation.

5. Write a class to record the information about the current task

public class CurrentRunningTask {
    private static ITask sCurrentShowingTask;

    public static void setCurrentShowingTask(ITask task) {
        sCurrentShowingTask = task;
    }

    public static void removeCurrentShowingTask() {
        sCurrentShowingTask = null;
    }

    public static ITask getCurrentShowingTask() {
        return sCurrentShowingTask;
    }

    public static boolean getCurrentShowingStatus() {
        return sCurrentShowingTask != null && sCurrentShowingTask.getStatus();
    }
}
Copy the code

Sometimes you need to get information about the tasks that are being performed, so here we make a class to store the tasks that are being performed.

6. With all the basic requirements written, it’s time to encapsulate a basic task class

public class BaseTask implements ITask { private final String TAG = getClass().getSimpleName(); private TaskPriority mTaskPriority = TaskPriority.DEFAULT; Private int mSequence; Private Boolean mTaskStatus =false; Protected WeakReference<BlockTaskQueue> taskQueue; // Protected WeakReference<BlockTaskQueue> taskQueue; // Block queue protected int duration = 0; Private PriorityBlockingQueue<Integer> blockQueue; private PriorityBlockingQueue<Integer> blockQueue; // The constructor publicBaseTask() { taskQueue = new WeakReference<>(BlockTaskQueue.getInstance()); blockQueue = new PriorityBlockingQueue<>(); } @override public voidenqueue() { TaskScheduler.getInstance().enqueue(this); } // Execute the task method, when the flag is set totrue@override public voiddoTask() {
        mTaskStatus = true; CurrentRunningTask.setCurrentShowingTask(this); } / / mission complete, change the tag and remove the task in the queue, and the record cleared @ Override public voidfinishTask() {
        this.mTaskStatus = false;
        this.taskQueue.get().remove(this);
        CurrentRunningTask.removeCurrentShowingTask();
        Log.d(TAG, taskQueue.get().size() + ""); } // Override public ITasksetPriority(TaskPriority mTaskPriority) {
        this.mTaskPriority = mTaskPriority;
        returnthis; } // Set the task execution time public ITasksetDuration(int duration) {
        this.duration = duration;
        returnthis; } @override public TaskPrioritygetPriority() {
        returnmTaskPriority; } // Override public intgetDuration() {
        returnduration; } // Set the sequence of tasks @override public voidsetSequence(int mSequence) { this.mSequence = mSequence; } // Override public intgetSequence() {
        returnmSequence; } Obtain the task status. @override public BooleangetStatus() {
        returnmTaskStatus; } // blockTask execution @override public void blockTask() throws Exception {blockqueue.take (); } // Unblock @override public voidunLockBlock() { blockQueue.add(1); // Add any data to the queue and the block will be unblocked} /** * * Override public int compareTo(ITask) */ @override public int compareTo(ITask)  another) { final TaskPriority me = this.getPriority(); final TaskPriority it = another.getPriority();returnme == it ? this.getSequence() - another.getSequence() : it.ordinal() - me.ordinal(); } // Output some information @override public StringtoString() {
        return "task name : " + getClass().getSimpleName() + " sequence : " + mSequence + " TaskPriority : "+ mTaskPriority; }}Copy the code

The code is not too hard, so you can read the comments, but the enqueue method, it’s not added directly to the taskQueue, it’s queued through the enqueue method of the TaskScheduler, which is a TaskScheduler class, It encapsulates some of the functions of enqueuing and queueing. Let’s see how it is implemented.

7. TaskScheduler

public class TaskScheduler {
    private final String TAG = "TaskScheduler";
    private BlockTaskQueue mTaskQueue = BlockTaskQueue.getInstance();
    private ShowTaskExecutor mExecutor;

    private static class ShowDurationHolder {
        private final static TaskScheduler INSTANCE = new TaskScheduler();
    }

    private TaskScheduler() {
        initExecutor();
    }

    private void initExecutor() {
        mExecutor = new ShowTaskExecutor(mTaskQueue);
        mExecutor.start();
    }

    public static TaskScheduler getInstance() {
        returnShowDurationHolder.INSTANCE; } public void enqueue(ITask task) {// TaskScheduler is written as a singletonfalseIf you don't judge it, it will always befalse
        if(! mExecutor.isRunning()) { mExecutor.startRunning(); } mtaskQueue.add (task); } public voidresetExecutor() {
        mExecutor.resetExecutor();
    }

    public void clearExecutor() { mExecutor.clearExecutor(); }}Copy the code

ShowTaskExecutor is a task queued executor, which is basically an infinite loop, constantly fetching tasks from the queue and executing them. Let’s look at its implementation.

8.ShowTaskExecutor

public class ShowTaskExecutor {
    private final String TAG = "ShowTaskExecutor";
    private BlockTaskQueue taskQueue;
    private TaskHandler mTaskHandler;
    private boolean isRunning = true; private static final int MSG_EVENT_DO = 0; private static final int MSG_EVENT_FINISH = 1; public ShowTaskExecutor(BlockTaskQueue taskQueue) { this.taskQueue = taskQueue; mTaskHandler = new TaskHandler(); } // Start traversing the task queue public voidstart() {
        AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    while(isRunning) {// ITask ITask; iTask = taskQueue.take(); / / take the taskif(iTask ! = null) {// Execute the task TaskEventdoEvent = new TaskEvent();
                            doEvent.setTask(iTask);
                            doEvent.setEventType(TaskEvent.EventType.DO);
                            mTaskHandler.obtainMessage(MSG_EVENT_DO, doEvent).sendToTarget(); // Block until the task is finishedif(iTask.getDuration()! =0) { TimeUnit.MICROSECONDS.sleep(iTask.getDuration()); }else{ iTask.blockTask(); } // Finish the task TaskEvent finishEvent = new TaskEvent(); finishEvent.setTask(iTask); finishEvent.setEventType(TaskEvent.EventType.FINISH); mTaskHandler.obtainMessage(MSG_EVENT_FINISH, finishEvent).sendToTarget(); } } } catch (Exception ex) { ex.printStackTrace(); }}}); } // Call back different methods depending on the message. private static class TaskHandler extends Handler {TaskHandler() {
            super(Looper.getMainLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            TaskEvent taskEvent = (TaskEvent) msg.obj;
            if (msg.what == MSG_EVENT_DO && taskEvent.getEventType() == TaskEvent.EventType.DO) {
                taskEvent.getTask().doTask();
            }
            if (msg.what == MSG_EVENT_FINISH && taskEvent.getEventType() == TaskEvent.EventType.FINISH) {
                taskEvent.getTask().finishTask();
            }
        }
    }

    public void startRunning() {
        isRunning = true;
    }

    public void pauseRunning() {
        isRunning = false;
    }

    public boolean isRunning() {
        return isRunning;
    }

    public void resetExecutor() {
        isRunning = true;
        taskQueue.clear();
    }

    public void clearExecutor() { pauseRunning(); taskQueue.clear(); }}Copy the code
public class TaskEvent {
    private WeakReference<ITask> mTask;
    int mEventType;

    public ITask getTask() {
        return mTask.get();
    }

    public void setTask(ITask mTask) {
        this.mTask = new WeakReference<>(mTask);
    }

    public int getEventType() {
        return mEventType;
    }

    public void setEventType(int mEventType) { this.mEventType = mEventType; } public static class EventType { public static final int DO = 0X00; public static final int FINISH = 0X01; }}Copy the code

Now that the entire queue is encapsulated, let’s see how to use it:

Method of use

  1. Define a Task that inherits from BaseTask and implements the corresponding methods
public class LogTask extends BaseTask { String name; public LogTask(String name) { this.name = name; } // Override public void. // Override public voiddoTask() {
        super.doTask();
        Log.i("LogTask"."--doTask-"+ name); // If the Task execution time is undefined, such as uploading an image, then you need to manually call //unLockBlock to unblock after the upload, such as uploadImage(new UploadListener{void)onSuccess(){ unLockBlock(); }}); } // Call back to the task where you can do something like release resources or bury them @override public voidfinishTask() {
        super.finishTask();
        Log.i("LogTask"."--finishTask-"+ name); }}Copy the code
  1. And then in turn into the line to use
findViewById(R.id.btn1).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new LogTask("DEFAULT").setduration (5000) // Sets the time, which means the task time is fixed. If not, do not set. SetPriority (TaskPriority. // Join the team}}); findViewById(R.id.btn2).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new LogTask("LOW") .setDuration(4000) .setPriority(TaskPriority.LOW) .enqueue(); }}); findViewById(R.id.btn3).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new LogTask("HIGH") .setDuration(3000) .setPriority(TaskPriority.HIGH) .enqueue(); }});Copy the code

There you go, isn’t it easy, just click the button one after another, you will find that the tasks are queued according to the priority.

To obtain the current running task:

LogTask task = (LogTask) CurrentRunningTask.getCurrentShowingTask();
Copy the code