【android】AsyncTask的使用和工作原理

AsyncTask 的实现原理是基于异步消息处理机制,是一个抽象类。它适合来做耗时比较少的操作,这个类允许在没有操纵线程和 handler 的时候在 UI 线程上执行后台操作和发布结果。

AsyncTask 被设计成一个围绕 Thread 和 Handler 的帮助类,但不构成一个通用的线程框架。理想情况下,AsyncTask 应该用于短操作(最多几秒钟)。如果你需要保持线程运行很长时间,强烈建议你使用其他 API ,像java.util.concurrent 包提供的 Executor、ThreadPoolExecutor 和 FutureTask 。

异步任务被定义为在后台线程中运行,结果发表在 UI 线程。异步任务是由三个泛型类型,叫做 Params、Progress 和 Result,和四步叫做 onPreExecute、doInBackground、onProgressUpdate 和 onPostExecute 定义的。

1、AsyncTask 的使用

1.1、参数

  1. Params :在执行 AsyncTask 时需要传入的参数,可用于在后台任务中使用。
  2. Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
  3. Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。

因此,一个最简单的自定义 AsyncTask 就可以写成如下方式:

class DownloadTask extends AsyncTask<Void, Integer, Boolean>{
  ......
}

目前我们自定义的 DownloadTask 还是一个空任务,并不能进行任何实际的操作,还需要去重写 AsyncTask 中的几个方法才能完成对任务的定制。

1.2、常需要重写的四个方法

  1. onPreExecute():这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
  2. doInBackground(Params...):这个方法中所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。热没一旦执行完成就可以通过 return 语句来将任务的执行结果返回。注意:在这个方法中是不可以进行 UI 操作的,如果需要更新 UI 元素,可以调用 publishProgress(Progress...)方法来完成。
  3. onProgressUpdate(Progress...):当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就会很快被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对 UI 进行操作,利用参数中的数值就可以对界面元素进行相应地更新。
  4. onPostExecute(Result):当后台任务执行完毕并通过 return 语句进行返回时,这个方法就会很快被调用。返回地数据会作为参数传递到此方法中,可以利用返回地数据来进行一些 UI 操作,比如说提醒任务执行地结果,以及关闭掉进度条对话框等。

AsyncTask :在 doInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行 UI 操作,在 onPostExecute() 方法中执行一些任务的收尾工作。

启动任务:

new DownloadTask().execute();

1.3、取消

可以随时通过调用 cancel(boolean) 来取消一个任务。一个任务被取消后,系统会执行 onCancelled(Object) 作为结果回调接口,而不是 onPostExecute(Object)。 可以使用 isCancelled(Object) 方法判断一个任务是否被取消。如果一个任务可能被取消的话,就尽量在 doInBackground() 方法中定期的检查 onCancelled() 方法,当任务被取消可以提早结束任务,节约资源。

1.4、注意事项

  1. AsyncTask 类必须在 UI 线程加载。在 Build.VERSION_CODES.JELLY_BEAN(API 16)版本以后自动完成。
  2. 必须在 UI 线程上创建任务实例。
  3. execute(Params...) 必须在 UI 线程上调用。
  4. 不能手动调用 onPreExecute(), onPostExecute, doInBackground, onProgressUpdate四个方法。
  5. 一个任务实例只能执行一次。(第二次执行会被抛出异常)。

1.5、内存可观测性

AsyncTask 保证所有的回调在以下这种没有明确同步的安全的操作方式是同步的。

  1. 在构造函数中赋值或者在 onPreExecute方法中赋值,可以在 doInBackground中获取到。
  2. doInBackground方法中赋值,可以在 onProgressUpdate,onPostExecute,onCancel中获取该值。

1.6、执行顺序

AsyncTask 的执行顺序随系统版本有过巨大的改变。

  1. Android1.5时,它是在一个后台线程中顺序执行的,由调用顺序决定了任务的执行顺序。
  2. Android1.6 - Android2.3.2它是在一个线程池当中并发执行的。
  3. Android3.0- ~ 它又是在一个后台线程中顺序执行。但是,如果我们想让AsyncTask并发的执行,我们可以使用executeOnExecutor方法,为它指定一个Executor对象,控制它的执行顺序。

2、源码分析

2.1、Callable 和 FutureTask

在进行源码解释之前先来普及以下 Callable 和 FutureTask。

Callable 是类似于 Runnable 的接口,实现 Callable 和 Runnable 的类都是可被其他线程执行的任务。

Runnable 和 Callable 的区别是:

  1. 实现 Callable 接口需要重写的方法是 call() , Runnable 是 run()
  2. Callable 的任务执行后可返回值,Runnable 是不能的。
  3. call() 方法可以抛出异常,run() 方法是不可以的。
  4. 运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过 Future 对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

FutureTask 实际上是一个任务的操作类,它并不启动新线程,只是在自己所在线程上操作,任务具体的实现是构造 FutureTask 时提供的。

FutureTask 执行 Callable 任务,类似于 Thread 执行 Runnable 任务,执行完成调用 done() 方法。

2.2 AsyncTask 源码

我们直接从 AsyncTask 的 execute(Params...params) 方法说起,在这个方法里会调用 executeOnExecutor() 方法,在这个方法中会先检查当前任务的状态是否是 Pending,如果不是,就会抛出异常,这也就意味着,一个任务不能被多次执行,如果是 Pending ,就将当前任务的状态变为 Running 状态,然后在 onPreExecute() 方法中执行一些任务的初始化操作,之后将执行时传入的参数复制给 Callable 的对象 mWorker,这里的 Callable 对象和 FutureTask 对象是在创建 AsyncTask 时初始化的,然后调用 exec 的 execute() 方法。

    /**
     * Executes the task with the specified parameters. The task returns
     * itself (this) so that the caller can keep a reference to it.
     * 
     * <p>This method must be invoked on the UI thread.
     */
    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

    /**
     * Executes the task with the specified parameters. The task returns
     * itself (this) so that the caller can keep a reference to it.
     * 
     * <p>This method must be invoked on the UI thread.
     */
    @MainThread
    public final AsyncTask<Params, Progress, Result> 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;
    }

这个 exec 就是传入的 sDefaultExecutor 参数,注意这个 mFuture 哦,这个参数是怎么来的呢?

    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    //...
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

执行器在执行任务时,会先将任务加入到任务队列的队尾,然后判断 mActive 是否为空,第一次运行是 null ,接下来就会执行 scheduleNext() 方法,从任务队列的头部取出一个事物在线程池中执行并且将该任务在消息队列中删除,直到消息队列为空时,执行结束。

    private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        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);
            }
        }
    }

在执行时,是在r.run()里,这个 r 是什么呢?就是之前传入的 mFuture,所以是要调用FutureTask 中的 run() 方法。

    private final FutureTask<Result> mFuture;
    public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

这里调用了 c.call() 方法,c 是什么呢?其实就是在初始化 mFuture 对象时传入的 mWorker 随想,此时调用的 call() 方法,也就是一开始在 AsyncTask的构造函数中指定的。

/**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

会先调用 doInBackground() 方法,并将任务结果通过 postResult() 方法。

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

借助 Handler ,将结果发送到 UI 线程中。

    private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }

这里的 InternalHandler 是什么呢?在这个里面会执行任务的 finish() 方法。

    private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

在这个方法中根据任务是否已经被取消,决定是回调 onCancelled() 还是 onPostExecute(),执行完回调方法后,将该任务的状态置为 Finished。

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

注意上面的 InternalHandler 还有一种 MESSAGE_POST_PROGRESS 的消息类型,这种消息是用于当前进度的,调用的正是一个 onProgressUpdate(result.mData) 方法,那么什么时候才会发出这样一条消息呢?查看publishProgress()方法的源码,如下所示:

    @WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }

正因如此,在 doInBackground()方法中调用 publishProgress() 方法才可以从子线程切换到 UI 线程,从而完成对 UI 元素的更新操作。

3、AsyncTask 的线程池

刚刚的在分析 SerialExecutor 时,只关注了它会调用 mFuture 中的 run()方法,至于什么时候调用,我们并不清楚。

    private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        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);
            }
        }
    }

可以看到,这里是使用 ArrayDeque 这个队列来管理 Runnable 对象的,如果我们一次性启动了很多歌任务,首先会在第一次运行 execute() 方法的时候,会调用 ArrayDeque 的 offer()方法将传入的 Runnable 对象添加到队列的尾部,然后判断mActive对象是不是等于null,第一次运行当然是等于null了,于是会调用scheduleNext()方法。在这个方法中会从队列的头部取值,并赋值给mActive对象,然后调用THREAD_POOL_EXECUTOR去执行取出的取出的Runnable对象。之后如何又有新的任务被执行,同样还会调用offer()方法将传入的Runnable添加到队列的尾部,但是再去给mActive对象做非空检查的时候就会发现mActive对象已经不再是null了,于是就不会再调用scheduleNext()方法。

那么后面添加的任务岂不是永远得不到处理了?当然不是,看一看offer()方法里传入的Runnable匿名类,这里使用了一个try finally代码块,并在finally中调用了scheduleNext()方法,保证无论发生什么情况,这个方法都会被调用。也就是说,每次当一个任务执行完毕后,下一个任务才会得到执行,SerialExecutor模仿的是单一线程池的效果,如果我们快速地启动了很多任务,同一时刻只会有一个线程正在执行,其余的均处于等待状态。

不过你可能还不知道,在Android 3.0之前是并没有SerialExecutor这个类的,那个时候是直接在AsyncTask中构建了一个sExecutor常量,并对线程池总大小,同一时刻能够运行的线程数做了规定,代码如下所示:

private static final int CORE_POOL_SIZE = 5;  
private static final int MAXIMUM_POOL_SIZE = 128;  
private static final int KEEP_ALIVE = 10;  
……  
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,  
        MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);  

可以看到,这里规定同一时刻能够运行的线程数为5个,线程池总大小为128。也就是说当我们启动了10个任务时,只有5个任务能够立刻执行,另外的5个任务则需要等待,当有一个任务执行完毕后,第6个任务才会启动,以此类推。而线程池中最大能存放的线程数是128个,当我们尝试去添加第129个任务时,程序就会崩溃。

因此在3.0版本中AsyncTask的改动还是挺大的,在3.0之前的AsyncTask可以同时有5个任务在执行,而3.0之后的AsyncTask同时只能有1个任务在执行。为什么升级之后可以同时执行的任务数反而变少了呢?这是因为更新后的AsyncTask已变得更加灵活,如果不想使用默认的线程池,还可以自由地进行配置。比如使用如下的代码来启动任务,下面的代码是在 static 代码块中执行的,所以所有的 AsyncTask 是共享线程池的

Executor exec = new ThreadPoolExecutor(15, 200, 10,  
        TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());  
new DownloadTask().executeOnExecutor(exec);  

这样就可以使用我们自定义的一个Executor来执行任务,而不是使用SerialExecutor。上述代码的效果允许在同一时刻有15个任务正在执行,并且最多能够存储200个任务。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值