上一节讲了,这一节深入讲解如何深入使用AsyncTask。
asynctask本质上也是线程启动,只是它封装了一些内容,可以运行在后台,同时可以和UI线程交互。
asynctask最少要启动2个线程,最多四个。AsyncTask的状态
AsyncTask的状态共有三种,PENDING,RUNNING和FINISHED。这三种状态在AsyncTask的生命周期中之出现一次。
- PENDING,表示异步任务还没有开始运行。
- RUNNING,表示异步任务正在运行。
- FINISHED,表示onPostExecute已经执行完成。
安卓官方文档推荐我们编写一个子类继承自AsyncTask并且必须实现doinbackground方法。
我们编写一个简单的程序测试这三个状态的具体情况。private ProgressTask task = new ProgressTask();//自定义的一个内部类,继承自AsyncTaskprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash); ... Log.d("task", "before execute task's status is " + task.getStatus()); task.execute(); Log.d("task", "after execute task's status is " + task.getStatus());复制代码
在AsyncTask中我们实现以下几个方法
@Override protected void onPreExecute() { super.onPreExecute(); Log.d("task", "onPreExecute task's status is " + task.getStatus()); } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); Log.d("task", "onPostExecute task's status is " + task.getStatus()); handler.sendEmptyMessage(1); }复制代码
在handler中我们接收一个信息,用来打印此时task的状态
switch (msg.what) { case 1: Log.d("task", "finally task's status " + task.getStatus()); break; }复制代码
这样,我们的程序运行起来看一下日志
04-02 18:10:43.747 10433-10433/com.example.saka.materialdesignapplication D/task: before execute task's status is PENDING04-02 18:10:43.748 10433-10433/com.example.saka.materialdesignapplication D/task: onPreExecute task's status is RUNNING04-02 18:10:43.748 10433-10433/com.example.saka.materialdesignapplication D/task: after execute task's status is RUNNING04-02 18:11:17.724 10433-10433/com.example.saka.materialdesignapplication D/task: onPostExecute task's status is RUNNING04-02 18:11:17.724 10433-10433/com.example.saka.materialdesignapplication D/task: finally task's status FINISHED复制代码
可以看到,在整个任务周期中,task在execute之前是一直处于pennding状态,
在execute之后onPostExecute中的所有方法执行完成之前一直处于RUNNING状态,跳出onPostExecute方法之后所有的task任务相当于已经完成,这时候task的状态时FINISHED。通过观察源码我们可以看一下:当你新建一个AsyncTask对象以后,系统就会自动生成一个变量:private volatile Status mStatus = Status.PENDING;
这也就是说明当你new一个AsyncTask后,它的状态就被设置为了PENDING状态。直到execute或者executeOnExecutor方法执行之后。调用task.execute()之后,系统会调用executeOnExecutor()方法: @MainThread public final AsyncTaskexecute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }复制代码
传入的参数表明会启动默认的线程执行器,而现在系统默认的串行执行,也就是分先后次序执行。
executeOnExecutor()方法返回的同样是一个AsyncTask实例:@MainThread public final AsyncTaskexecuteOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; }复制代码
在这个方法中,首先判断异步任务的状态,
假如这个任务没有在等待状态,而是正在运行或者已经结束,会抛出异常。然后会将异步任务的状态设置为RUNNING,调用onPreExecute方法,并将参数传递给AsyncTask中的参数接收器,然后才开始执行exec.execute(mFuture)方法。所以在onPreExecute之前,状态已经设置为了RUNNING。再来研究以下何时会停止。调用exec.execute(mFuture)中传入的参数是一个接口类型的Runable:mWorker = new WorkerRunnable() { public Result call() throws Exception { mTaskInvoked.set(true); Result result = null; try { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked result = doInBackground(mParams); Binder.flushPendingCommands(); } catch (Throwable tr) { mCancelled.set(true); throw tr; } finally { postResult(result); } return result; } };复制代码
mFuture = new FutureTask<Result>(mWorker)
case MESSAGE_POST_RESULT: // There is only one result result.mTask.finish(result.mData[0]); break; ... private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }复制代码
在finish方法中设置了status为FINISHED,
注意此处时在执行完onCancelled或者onPostExecute之后才改变状态,这也就解释了为什么在onPostExecute方法中获取的状态仍然是RUNNING。Note:根据文档中的介绍,并没有讲当task被cancel后是否会执行FINISHED,实际是执行的。
取消任务
关于cancel方法官方的介绍比较简单,参看上篇文章。
首先看一下官方文档对这个API的解释:boolean cancel (boolean mayInterruptIfRunning)
尝试取消执行此任务。 如果任务已经完成,已经被取消或由于某种其他原因而无法取消,则此尝试将失败。
如果成功,并且在取消被调用时此任务尚未开始,则此任务不应该运行。 如果任务已经启动,那么mayInterruptIfRunning参数可以确定执行该任务的线程是否应该被中断,以试图停止该任务。 调用此方法将导致在doInBackground(Object [])返回后在UI线程上调用onCancelled(Object)。 调用此方法可以保证onPostExecute(Object)从不被调用。 调用此方法后,应该从doInBackground(Object [])定期检查isCancelled()返回的值,以尽早完成任务。
这是什么意思呢?
public final boolean cancel(boolean mayInterruptIfRunning) { mCancelled.set(true); return mFuture.cancel(mayInterruptIfRunning); }复制代码
调用asynctask的cancel方法其实就是调用futuretask的cancel方法,这个是java线程中的方法,
假如传入的参数为true的时候(并不推荐这种方式),会立即停止当前任务;当传入的参数为false的时候,会等到本次任务执行完成后,无论在哪种情况下,我们都应该清楚的认识到,此时获取当前任务的isCanceled都会返回为true。调用cancel方法影响到的会有doInBackground、onPostExecute和onCancelled方法,
当传入参数为ture的时候,doInBackGround方法会立即中断,进入onCancelled方法,而不执行onPostExecute方法;当传入的参数为false的时候,doInBackground方法会执行完毕后进入onCancelled方法,也不执行onPostExecute方法;始终不影响onPreExecute方法,它始终会执行。但是偶尔会出现doInbackground方法不执行而直接进入onCancelled方法。task启动直接cancel:
private void setCancelNow() { task.cancel(false); }复制代码
此时日志是:
04-02 21:44:24.692 8082-8082/com.example.saka.materialdesignapplication D/task: before execute task's status is PENDING04-02 21:44:24.692 8082-8082/com.example.saka.materialdesignapplication D/task: onPreExecute task's status is RUNNING04-02 21:44:24.692 8082-8082/com.example.saka.materialdesignapplication D/task: after execute task's status is RUNNING04-02 21:44:24.747 8082-8082/com.example.saka.materialdesignapplication D/task: onCancelled task's status:RUNNING复制代码
可见方法执行了onPreExecute和onCancelled方法,然而并没有执行doInBackground方法。
task启动后过一段时间在cancel:
private void setCancelDelay() { try { Thread.sleep(1000); task.cancel(false); Log.d("task", "task after cancel"); } catch (InterruptedException e) { e.printStackTrace(); } }复制代码
此时的输出日志是:
04-02 21:51:22.781 14525-14525/? D/task: before execute task's status is PENDING04-02 21:51:22.781 14525-14525/? D/task: onPreExecute task's status is RUNNING04-02 21:51:22.781 14525-14525/? D/task: after execute task's status is RUNNING04-02 21:51:22.782 14525-14540/? D/task: doInBackground04-02 21:51:23.113 14525-14540/? D/task: the progress value 004-02 21:51:23.452 14525-14540/? D/task: the progress value 104-02 21:51:23.782 14525-14525/com.example.saka.materialdesignapplication D/task: task after cancel...04-02 21:51:56.552 14525-14525/com.example.saka.materialdesignapplication D/task: onCancelled task's status:RUNNING04-02 21:51:56.552 14525-14525/com.example.saka.materialdesignapplication D/task: finally task's status FINISHED复制代码
可以看到此时执行了doInbackground方法,最后进入了onCancelled方法。
因为preExecute方法是在异步任务启动后而真正的线程启动前调用的,
而cancel方法调用的时futuretask的cancel方法,也就是真正启动线程之后才执行cancel的,所以preExecute一定会执行。doInBackground是在WorkerRunnable的call回调方法中执行的,mWorker = new WorkerRunnable() { public Result call() throws Exception { mTaskInvoked.set(true); ... try { ... result = doInBackground(mParams); Binder.flushPendingCommands(); } catch (Throwable tr) { mCancelled.set(true); throw tr; } finally { postResult(result); } return result; } };复制代码
假如WorkerRunnable不执行,也就不会调用onCancelled方法。
futuretask有一个回调方法是必须实现的,done()方法,当任务结束后会调用此方法。futuretask在done的回调方法中检测mTaskInvoked是否被调用,假如未被调用会执行postResultIfNotInvoked(get())
private void postResultIfNotInvoked(Result result) { final boolean wasTaskInvoked = mTaskInvoked.get(); if (!wasTaskInvoked) { postResult(result); } }复制代码
此时同样会调用postResult进而调用onCancelled方法。
所以无论何时都会调用onCancelled方法。
任务的串行和并行
首先看看execute方法:
AsyncTask execute (Params... params)使用指定的参数执行任务。 该任务返回自身(this),以便调用者可以保留对它的引用。
注意:此函数根据平台版本为队列中的任务排定一个后台线程或线程池。第一次被引入时,AsyncTasks在单个后台线程上串行执行。从DONUT开始,被更改为允许多个任务并行操作的线程池。HONEYCOMB开始,任务改为在单线程执行,以避免并行执行引起的常见应用程序错误。如果需要并行执行,可以使用THREAD_POOL_EXECUTOR的此方法的executeOnExecutor(Executor,Params ...)方法。这个是什么意思呢?
现在的安卓系统,基本都是大于3.0的,也就是默认情况下会按顺序执行每一个asynctask,比如,我们new出两个异步任务:
task = new ProgressTask(); Log.d("task", "before execute task's status is " + task.getStatus()); task.execute(10); ProgressTask task1=new ProgressTask(); task1.execute(90);复制代码
然后打印日志输出的是task先执行完毕才执行task1,这就证明了只能运行一个异步任务,
而其他异步任务会在队列中等待第一个执行完毕才执行。根据官方文档我们可以使用executeOnExecutor来并行执行两个任务,看一下API文档:
AsyncTask executeOnExecutor (Executor exec,Params... params)使用指定的参数执行任务。该任务返回自身(this),以便调用者可以保留对它的引用。此方法通常与THREAD_POOL_EXECUTOR一起使用,以允许多个任务在由AsyncTask管理的线程池上并行运行,但是也可以使用自己的Executor进行自定义行为。警告:允许多个任务从线程池并行运行通常不是想要的,因为它们的操作顺序没有定义。例如,如果这些任务用于修改任何共同的状态(例如由于按钮点击而编写文件),则不能保证修改的顺序。没有仔细的工作,在很少的情况下,较新版本的数据可能会被较旧的数据覆盖,从而导致数据丢失和稳定性问题。这些更改最好是连续执行;为了保证这样的工作是序列化的,无论平台版本如何,都可以使用SERIAL_EXECUTOR这个功能。必须在UI线程上调用此方法。简单验证一下
task = new ProgressTask(); Log.d("task", "before execute task's status is " + task.getStatus()); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,10); ProgressTask task1=new ProgressTask(); task1.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,90);复制代码
这样执行后task和task2会并行执行,交替输出值。
static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); threadPoolExecutor.allowCoreThreadTimeOut(true); THREAD_POOL_EXECUTOR = threadPoolExecutor; }复制代码
THREAD_POOL_EXECUTOR维护的是一个LinkedBlockingQueue线程队列。
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128);
这个阻塞队列的容量是128,则线程数超过128时要等旧的线程移除后才能加入队列,128是个大数目,我们就不验证了。则此阻塞队列可以同时并发128个异步任务。 而安卓系统默认的executor是SerialExecutor,就是个串行执行器。看一下实现方法:
private static class SerialExecutor implements Executor { final ArrayDequemTasks = new ArrayDeque (); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } }复制代码
SerialExecutor维护的是一个双向队列,当数组队列中有值不为空的时候,取出这个任务然后在线程池中执行。
发布进度
关于发布进度这个比较简单,早doInBackgroud方法中执行publishProgress (Progress... values)方法,
此方法会直接调用运行在UI线程中的onProgressUpdate(Progress...)方法,这时候我们就可以更新进度了。以上就是关于asynctask的一些我的理解。