android 耗时任务 UI 安卓任务调度_android 耗时任务 UI

后面的文章开始写 Android 相关的东西了,这边就先插入一个小节奏,分享一个我平时自己写的工具类。git 链接: https://github.com/kuangzhongwen/AndroidCommonLibs/tree/master/src/main/java/waterhole/commonlibs/asyn

 

目录:

  1. 背景
  2. 代码实现
  3. 使用

 

 

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);