/** * Copyright (c) 2013, Paul Daniels * <p/> * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * <p/> * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * <p/> * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * <p/> * Copyright (C) 2010-2013 eBusiness Information, Excilys Group * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed To in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * <p> * Copyright (C) 2010-2013 eBusiness Information, Excilys Group * <p> * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed To in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * <p> * Copyright (C) 2010-2013 eBusiness Information, Excilys Group * <p> * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed To in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ /** * Copyright (C) 2010-2013 eBusiness Information, Excilys Group * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed To in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.gzsll.hupu.components.jockeyjs.util; import android.util.Log; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class BackgroundExecutor { private static final String TAG = "BackgroundExecutor"; private static Executor executor = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors()); private static final List<Task> tasks = new ArrayList<Task>(); /** * Execute a runnable after the given delay. * * @param runnable the task to execute * @param delay the time from now to delay execution, in milliseconds * @return Future associated to the running task * @throws IllegalArgumentException if <code>delay</code> is strictly positive and the current * executor does not support scheduling (if * {@link #setExecutor(Executor)} has been called with such an * executor) */ private static Future<?> directExecute(Runnable runnable, int delay) { Future<?> future = null; if (delay > 0) { /* no serial, but a delay: schedule the task */ if (!(executor instanceof ScheduledExecutorService)) { throw new IllegalArgumentException("The executor set does not support scheduling"); } ScheduledExecutorService scheduledExecutorService = (ScheduledExecutorService) executor; future = scheduledExecutorService.schedule(runnable, delay, TimeUnit.MILLISECONDS); } else { if (executor instanceof ExecutorService) { ExecutorService executorService = (ExecutorService) executor; future = executorService.submit(runnable); } else { /* non-cancellable task */ executor.execute(runnable); } } return future; } /** * Execute a task after (at least) its delay <strong>and</strong> after all * tasks added with the same non-null <code>serial</code> (if any) have * completed execution. * * @param task the task to execute * @throws IllegalArgumentException if <code>task.delay</code> is strictly positive and the * current executor does not support scheduling (if * {@link #setExecutor(Executor)} has been called with such an * executor) */ public static synchronized void execute(Task task) { Future<?> future = null; if (task.serial == null || !hasSerialRunning(task.serial)) { task.executionAsked = true; future = directExecute(task, task.remainingDelay); } if (task.id != null || task.serial != null) { /* keep task */ task.future = future; tasks.add(task); } } /** * Execute a task. * * @param runnable the task to execute * @param id identifier used for task cancellation * @param delay the time from now to delay execution, in milliseconds * @param serial the serial queue (<code>null</code> or <code>""</code> for no * serial execution) * @throws IllegalArgumentException if <code>delay</code> is strictly positive and the current * executor does not support scheduling (if * {@link #setExecutor(Executor)} has been called with such an * executor) */ public static void execute(final Runnable runnable, String id, int delay, String serial) { execute(new Task(id, delay, serial) { @Override public void execute() { runnable.run(); } }); } /** * Execute a task after the given delay. * * @param runnable the task to execute * @param delay the time from now to delay execution, in milliseconds * @throws IllegalArgumentException if <code>delay</code> is strictly positive and the current * executor does not support scheduling (if * {@link #setExecutor(Executor)} has been called with such an * executor) */ public static void execute(Runnable runnable, int delay) { directExecute(runnable, delay); } /** * Execute a task. * * @param runnable the task to execute */ public static void execute(Runnable runnable) { directExecute(runnable, 0); } /** * Execute a task after all tasks added with the same non-null * <code>serial</code> (if any) have completed execution. * <p> * Equivalent to {@link #execute(Runnable, String, int, String) * execute(runnable, id, 0, serial)}. * * @param runnable the task to execute * @param id identifier used for task cancellation * @param serial the serial queue to use (<code>null</code> or <code>""</code> * for no serial execution) */ public static void execute(Runnable runnable, String id, String serial) { execute(runnable, id, 0, serial); } /** * Change the executor. * * Note that if the given executor is not a {@link ScheduledExecutorService} * then executing a task after a delay will not be supported anymore. If it * is not even a {@link ExecutorService} then tasks will not be cancellable * anymore. * * @param executor * the new executor */ public static void setExecutor(Executor executor) { BackgroundExecutor.executor = executor; } /** * Cancel all tasks having the specified <code>id</code>. * * @param id * the cancellation identifier * @param mayInterruptIfRunning * <code>true</code> if the thread executing this task should be * interrupted; otherwise, in-progress tasks are allowed to * complete */ public static synchronized void cancelAll(String id, boolean mayInterruptIfRunning) { for (int i = tasks.size() - 1; i >= 0; i--) { Task task = tasks.get(i); if (id.equals(task.id)) { if (task.future != null) { task.future.cancel(mayInterruptIfRunning); if (!task.managed.getAndSet(true)) { /* * the task has been submitted to the executor, but its * execution has not started yet, so that its run() * method will never call postExecute() */ task.postExecute(); } } else if (task.executionAsked) { Log.w(TAG, "A task with id " + task.id + " cannot be cancelled (the executor set does not support it)"); } else { /* this task has not been submitted to the executor */ tasks.remove(i); } } } } /** * Indicates whether a task with the specified <code>serial</code> has been * submitted to the executor. * * @param serial * the serial queue * @return <code>true</code> if such a task has been submitted, * <code>false</code> otherwise */ private static boolean hasSerialRunning(String serial) { for (Task task : tasks) { if (task.executionAsked && serial.equals(task.serial)) { return true; } } return false; } /** * Retrieve and remove the first task having the specified * <code>serial</code> (if any). * * @param serial * the serial queue * @return task if found, <code>null</code> otherwise */ private static Task take(String serial) { int len = tasks.size(); for (int i = 0; i < len; i++) { if (serial.equals(tasks.get(i).serial)) { return tasks.remove(i); } } return null; } public static abstract class Task implements Runnable { private String id; private int remainingDelay; private long targetTimeMillis; /* since epoch */ private String serial; private boolean executionAsked; private Future<?> future; /* * A task can be cancelled after it has been submitted to the executor * but before its run() method is called. In that case, run() will never * be called, hence neither will postExecute(): the tasks with the same * serial identifier (if any) will never be submitted. * * Therefore, cancelAll() *must* call postExecute() if run() is not * started. * * This flag guarantees that either cancelAll() or run() manages this * task post execution, but not both. */ private AtomicBoolean managed = new AtomicBoolean(); public Task(String id, int delay, String serial) { if (!"".equals(id)) { this.id = id; } if (delay > 0) { remainingDelay = delay; targetTimeMillis = System.currentTimeMillis() + delay; } if (!"".equals(serial)) { this.serial = serial; } } @Override public void run() { if (managed.getAndSet(true)) { /* cancelled and postExecute() already called */ return; } try { execute(); } finally { /* handle next tasks */ postExecute(); } } public abstract void execute(); private void postExecute() { if (id == null && serial == null) { /* nothing to do */ return; } synchronized (BackgroundExecutor.class) { /* execution complete */ tasks.remove(this); if (serial != null) { Task next = take(serial); if (next != null) { if (next.remainingDelay != 0) { /* the delay may not have elapsed yet */ next.remainingDelay = Math.max(0, (int) (targetTimeMillis - System.currentTimeMillis())); } /* a task having the same serial was queued, execute it */ BackgroundExecutor.execute(next); } } } } } }