后面的文章开始写 Android 相关的东西了,这边就先插入一个小节奏,分享一个我平时自己写的工具类。git 链接: https://github.com/kuangzhongwen/AndroidCommonLibs/tree/master/src/main/java/waterhole/commonlibs/asyn
目录:
- 背景
- 代码实现
- 使用
1. 背景
app 启动可能会有很多任务需要异步线程执行,为了减少对 app 启动速度的影响(减小启动过程中 cpu 消耗),一种方案是采用延时方式。但延时方式有个弊端,不同性能的设备延时多少也不合适。另外第一次安装,有引导界面,第一次启动速度也慢,导致延时时间相对较短,在app还没有初始化完毕,这些异步线程在预期之前执行,过多占用 cpu。
为此设计该类,app 初始化完毕后才开始执行这些异步任务,而不用考虑到底延时多少的问题。app 需要在合适的地方调用 {@link #appReady(boolean)},此时会安排队列中的任务开始顺序执行。
2. 代码实现
任务执行用的是 AsyncTask,同时包装了一个 AsyncTaskAssistant 类,可以方便的串行或者并行执行任务。LanuchTaskExecutor 为随app启动一起执行的异步任务调度器。
AsyncTaskAssistant:
/**
* {@link AsyncTask} 类的辅助工具类,方便直接执行 {@link Runnable} 任务。<br>
* 并且支持延时 {@link Runnable} 任务,内部通过 {@link Handler#postDelayed(Runnable, long)} 实现,
* 参考 {@link #execute(Runnable, long)} 函数,执行前由于用到 {@link Handler}对象,所以需要提前在UI线程
* 调用 {@link #init()}函数,进行初始化操作。<br><br>
* <p>
* 从{@link Build.VERSION_CODES#HONEYCOMB} 开始,{@link AsyncTask#execute(Object...)} 函数
* 执行的任务按顺序执行,也就是一个任务执行完了才会执行下一个任务。如果想同时在线程池执行,需要调用
* {@link AsyncTask#executeOnExecutor(Executor, Object...)} 函数。<br><br>
* <p>
* 为了隐藏这些细节,这个类的 {@link #execute(Runnable)} 函数也是按顺序执行,只不过和android版本没有关系。<br>
* 如果想在线程池执行,请调用 {@link #executeOnThreadPool(Runnable)} 相关函数。
*
* @author kuang on 2017/07/01.
*/
public final class AsyncTaskAssistant {
/**
* 需要在UI线程初始化,调用{@link #init()}函数。
*/
private static Handler sHandler;
/**
* 一个 {@link Executor} ,按照顺序执行任务,一个任务执行完以后才执行下一个。
* 一个进程中共用一个。
*/
private static final Executor SERIAL_EXECUTOR = new SerialExecutor();
/**
* 默认的 {@link Executor},默认为 {@link #SERIAL_EXECUTOR}
*/
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
/**
* 内部使用参数类。用作执行任务的参数。
*/
private static class Task {
/**
* 所具体执行的任务。
*/
Runnable runnable;
}
/**
* @see {@link AsyncTaskAssistant#SERIAL_EXECUTOR}
*/
private static class SerialExecutor implements Executor {
/**
* 任务队列。
*/
final LinkedList<Runnable> mTasks = new LinkedList<>();
/**
* 当前正在执行的任务。
*/
Runnable mActive;
@Override
public synchronized void execute(final Runnable r) {
mTasks.add(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
/**
* 执行下一个任务。
*/
synchronized void scheduleNext() {
mActive = mTasks.poll();
if (mActive != null) {
executeOnThreadPool(mActive);
}
}
}
/**
* 用来实际执行任务的 {@link AsyncTask}.
*/
private static class WorkerAsyncTask extends AsyncTask<Task, Object, Object> {
@Override
protected Object doInBackground(Task... params) {
if (params[0] != null && params[0].runnable != null) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Runnable task = params[0].runnable;
task.run();
}
return null;
}
}
/**
* Utility class use a private constructor.
*/
private AsyncTaskAssistant() {
}
/**
* 调用延时任务{@link #execute(Runnable, long)}之前,需要在UI线程中调用此函数,进行初始化操作。
*/
public static void init() {
if (sHandler == null) {
sHandler = new Handler();
}
}
/**
* 一个在 {@link AsyncTask} 中执行 {@link Runnable} 任务的快捷方式。
* 任务按顺序执行,一个任务执行完后,才执行下一个任务。
*
* @param runnable 要执行的任务
* @see #executeOnThreadPool(Runnable)
*/
public static void execute(Runnable runnable) {
sDefaultExecutor.execute(runnable);
}
/**
* 延时执行{@link Runnable} 任务. 调用之前需要在UI线程调用 {@link #init()}
*
* @param runnable 要执行的任务。
* @param delayTimeMS 需要延时的时间,单位毫秒。
* @see #execute(Runnable)
*/
public static void execute(final Runnable runnable, long delayTimeMS) {
if (sHandler == null) {
init();
}
Runnable r = new Runnable() {
@Override
public void run() {
execute(runnable);
}
};
sHandler.postDelayed(r, delayTimeMS);
}
private static void executeOnThreadPoolInternal(Runnable runnable) {
Task task = new Task();
task.runnable = runnable;
/*
* 从 apilevel 11 开始,默认为顺序执行,之前是在线程池执行。当然对刚开始只有一个线程执行。从1.6开始
* 使用线程池执行。
*/
WorkerAsyncTask asyncTask = new WorkerAsyncTask();
if (APIUtils.hasHoneycomb()) {
asyncTask.executeOnExecutor(WorkerAsyncTask.THREAD_POOL_EXECUTOR, task);
} else {
asyncTask.execute(task);
}
}
/**
* 一个在 {@link AsyncTask} 中执行 {@link Runnable} 任务的快捷方式。
* 执行顺序无法保证,在线程池执行。
*
* @param runnable 要执行的任务
* @see AsyncTask#executeOnExecutor(Executor, Object...)
*/
public static void executeOnThreadPool(Runnable runnable) {
executeOnThreadPool(runnable, 0);
}
/**
* 一个延时在 {@link AsyncTask} 中执行 {@link Runnable} 任务的快捷方式。
* 执行顺序无法保证,在线程池执行。
* 调用之前需要在UI线程调用 {@link #init()}
*
* @param runnable 要执行的任务
* @param delayTimeMS 需要延时的时间,单位毫秒。
* @see AsyncTask#executeOnExecutor(Executor, Object...)
*/
public static void executeOnThreadPool(final Runnable runnable, long delayTimeMS) {
if (sHandler == null) {
init();
}
Runnable r = new Runnable() {
@Override
public void run() {
executeOnThreadPoolInternal(runnable);
}
};
sHandler.postDelayed(r, delayTimeMS);
}
}
LanuchTaskExecutor:
/**
* 随app启动一起执行的异步任务调度器。<br>
* <p>
* app启动可能会有很多任务需要异步线程执行。为了减少对app启动速度的影响(减小启动过程中cpu消耗),
* 一种方案是采用延时方式。但延时方式有个弊端,不同性能的设备延时多少也不合适。
* 另外第一次安装,有引导界面,第一次启动速度也慢,导致延时时间相对较短,在app还没有初始化完毕,这些异步线程
* 在预期之前执行,过多占用cpu。
* <p>
* 为此设计该类,app初始化完毕后才开始执行这些异步任务,而不用考虑到底延时多少的问题。<br>
* <p>
* app 需要在合适的地方调用 {@link #appReady(boolean)},此时会安排队列中的任务开始顺序执行。
*
* @author kuang on 2017/07/01
*/
public final class LaunchTaskExecutor {
/**
* 排队等候需要执行的消息队列。
*/
private static LinkedList<Task> sQueue = new LinkedList<>();
/**
* app 是否已经初始化完毕,完毕后才安排执行 {@link #sQueue} 中的任务。
*/
private static boolean sAppReady = false;
/**
* 工具类,私有化构造函数。
*/
private LaunchTaskExecutor() {
}
/**
* 内部使用参数类。用作执行任务的参数。
*/
private static class Task {
/**
* 所具体执行的任务。
*/
Runnable runnable;
/**
* 任务名字表示
*/
String name;
/**
* 任务延时
*/
long delay = 0L;
}
/**
* 标记app初始化完毕,能够在不影响自身启动(抢占cpu资源)的前提下执行异步任务。
* 但是还有一个情况。当app只是退出Activity,但是进程没有杀死。
* 等一下次Activity进入的时候静态变量标记为已经ready,任务就会立刻执行。所以还是有问题的。
* <p>
* 这时候需要一个 notready,把状态给位 notready。 随后等初始化完毕后再次标记为 ready。
* <p>
* 建议在 Activity onCreate最开始 标记为 notready。
*
* @param readyOrNot 参考函数说明。
*/
public static synchronized void appReady(boolean readyOrNot) {
if (!readyOrNot) {
sAppReady = false;
return;
}
if (sAppReady) {
return;
}
sAppReady = true;
// 执行等待队列中的任务。
while (true) {
Task task = sQueue.poll();
if (task != null && task.runnable != null) {
// 将 执行任务放到 AsyncTaskAssistant 的执行队列中,按顺序执行。
if (task.delay > 0L) {
AsyncTaskAssistant.execute(task.runnable, task.delay);
} else {
AsyncTaskAssistant.execute(task.runnable);
}
} else {
// 队列为空,中断循环。
break;
}
}
}
/**
* 获取初始化完毕标记值
*
* @see #appReady(boolean)
*/
public static synchronized boolean isAppReady() {
return sAppReady;
}
/**
* 执行随 app 启动的异步任务,如果app还没有初始化完毕{@link #appReady(boolean)},任务暂时直接放到等待队列。
* 如果app已经初始化完毕,则直接安排通过 {@link AsyncTaskAssistant#execute(Runnable)} 执行,顺序执行。
*
* @param runnable 需要执行的 runnable 任务
* @param taskName 任务名字,目前只用来调试,log输出作用。
*/
public static synchronized void execute(Runnable runnable, String taskName) {
execute(runnable, taskName, 0L);
}
/**
* 执行随 app 启动的异步任务,如果app还没有初始化完毕{@link #appReady(boolean)},任务暂时直接放到等待队列。
* 如果app已经初始化完毕,则直接安排通过 {@link AsyncTaskAssistant#execute(Runnable)} 执行,顺序执行。
*
* @param runnable 需要执行的 runnable 任务
* @param taskName 任务名字,目前只用来调试,log输出作用。
* @param delay 任务延迟时间(毫秒)
*/
public static synchronized void execute(Runnable runnable, String taskName, long delay) {
if (sAppReady) {
if (delay > 0L) {
AsyncTaskAssistant.execute(runnable, delay);
} else {
AsyncTaskAssistant.execute(runnable);
}
} else {
Task task = new Task();
task.runnable = runnable;
task.name = taskName;
task.delay = delay;
sQueue.add(task);
}
}
}
3. 使用
LaunchTaskExecutor 的使用:
public final class MyApplication extends Application {
@Override
public void onCreate() {
// 先初始化 AsyncTaskAssistant
AsyncTaskAssistant.init();
// app 启动后才会开始执行该任务
LaunchTaskExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("launchTask-0 execute");
}
}, "launchTask-0");
// app 启动后才会开始执行该任务 (延时3000)
LaunchTaskExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("launchTask-0 execute");
}
}, "launchTask-0", 3000L);
}
}
在 app 启动完成后 (如 MainActivity 中),调用:
LaunchTaskExecutor.appReady(true);
当然,除了启动任务,你也可以使用 AsyncTaskAssistant 这个类在你的 app 中执行耗时任务:
AsyncTaskAssistant.execute(new Runnable() {
@Override
public void run() {
System.out.println("task0 execute");
}
});
AsyncTaskAssistant.execute(new Runnable() {
@Override
public void run() {
System.out.println("task1 execute");
}
}, 3000L);
AsyncTaskAssistant.executeOnThreadPool(new Runnable() {
@Override
public void run() {
System.out.println("task2 execute");
}
});
AsyncTaskAssistant.executeOnThreadPool(new Runnable() {
@Override
public void run() {
System.out.println("task3 execute");
}
}, 3000L);