博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入理解安卓异步任务AsyncTask
阅读量:6587 次
发布时间:2019-06-24

本文共 11899 字,大约阅读时间需要 39 分钟。

上一节讲了,这一节深入讲解如何深入使用AsyncTask。

asynctask本质上也是线程启动,只是它封装了一些内容,可以运行在后台,同时可以和UI线程交互。

asynctask最少要启动2个线程,最多四个。

AsyncTask的状态

AsyncTask的状态共有三种,PENDING,RUNNING和FINISHED。这三种状态在AsyncTask的生命周期中之出现一次。

  1. PENDING,表示异步任务还没有开始运行。
  2. RUNNING,表示异步任务正在运行。
  3. 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 AsyncTask
execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }复制代码

传入的参数表明会启动默认的线程执行器,而现在系统默认的串行执行,也就是分先后次序执行。

executeOnExecutor()方法返回的同样是一个AsyncTask实例:

@MainThread    public final AsyncTask
executeOnExecutor(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)

上面两步都是在new一个AsyncTask实例的时候执行的,try块中时执行doInbackGround方法,
在finally中执行的是结束动作。在postResult()方法中,定义了一个message,
调用该方法的时候会使用AsyncTask中的handler发送消息,handler接收到消息后执行如下代码:

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方法。

  1. 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方法。

  2. 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 ArrayDeque
mTasks = 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的一些我的理解。

转载地址:http://trhno.baihongyu.com/

你可能感兴趣的文章
《Windows Server 2012 Hyper-V虚拟化管理实践》一3.3 远程管理Hyper-V主机
查看>>
《c++语言导学》——1.3 Hello,World!
查看>>
租用服务器怎么免去后顾之忧?
查看>>
阿里云服务器创建历史功能介绍 快速创建云服务器
查看>>
开源人工智能技术将改变一切
查看>>
送17届学弟学妹的礼物——学生包、学生优惠合集
查看>>
OpenBSD 现已支持 USB 3.0
查看>>
《CUDA C编程权威指南》——2.4节设备管理
查看>>
《Arduino开发实战指南:机器人卷》一2.2 模拟I/O口的操作函数
查看>>
红帽专家谈 Ceph 与 Gluster 开源存储路线
查看>>
2015 上半年 JavaScript 使用统计数据
查看>>
《PaaS程序设计》一1.2 云能为创新做什么
查看>>
《OpenGL ES 3.x游戏开发(上卷)》一2.4 文件I/O
查看>>
《写给程序员的数据挖掘实践指南》——5.2. 10折交叉验证的例子
查看>>
DevOps:软件架构师行动指南2.2 云的特性
查看>>
JVM性能优化, Part 5:Java的伸缩性
查看>>
《Python算法教程》——1.6 如果您感兴趣
查看>>
《正则表达式经典实例(第2版)》——2.18 向正则表达式中添加注释
查看>>
lolcat :一个在 Linux 终端中输出彩虹特效的命令行工具
查看>>
ROS机器人程序设计(原书第2版)3.9 3D可视化
查看>>