AsyncTask的使用方法和原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/whsdu929/article/details/52505439

AsyncTask的本质实际仍是传统的线程,它封装了Thread和Handler,底层用到了线程池。它在线程池中执行后台任务,支持把执行的进度和最终结果传递给主线程并在主线程中更新UI。但是AsyncTask不适合执行特别耗时的后台任务,对于特别耗时的任务,建议直接使用线程池(原因请参考下面的工作原理分析)。

使用方法

AsyncTask是一个抽象的泛型类,它的泛型参数有三个:

public abstract class AsyncTask<Params, Progress, Result>

Params:doInBackground方法接收的参数类型,即执行这个AsyncTask时传入的参数,比如需要执行下载任务,这里可能就是URL类型。

Progress:onProgressUpdate方法接收的参数类型,即后台任务执行进度的类型,比如可以设置为Integer。

Result:onPostExecute方法接收的参数类型,即后台任务执行完毕后返回的结果类型,比如后台请求了网络接口,服务端返回了一个Json格式串,这里就可以设为Json类型。
如果不需要设置某个参数,可以将其写为Void。


AsyncTask提供了5个核心的方法供子类去复写,其中doInBackground必须要实现,其他可酌情而定:

onPreExecute()在主线程中执行,在异步任务执行之前,此方法会被调用,一般用于做一些准备工作。

doInBackground(Params[] params)在线程池中执行,params表示异步任务的输入参数。在这个方法中可以调用publishProgress方法来更新任务进度,调用后onProgressUpdate方法就会被回调。另外,此方法的返回结果会传递给onPostExecute。

onProgressUpdate(Progress[] values)在主线程中执行,当后台异步任务进度发生改变时,可通过在doInBackground里调用publishProgress方法使该方法被调用,可以在这里面更新进度条UI。

onPostExecute(Result result)在主线程中执行,在后台异步任务执行完毕之后,此方法会被调用,可以在这里面执行一些收尾工作。

onCancelled()在主线程中执行,在后台异步任务被取消时(开发者调用AsyncTask的cancel()方法),该方法会被回调。可以在doInBackground里通过if(isCancelled())来判断任务是否已被取消。

这几个方法的执行顺序和流程是:

Created with Raphaël 2.1.0startonPreExecutedoInBackgroundpublishProgressonProgressUpdateisCancelledonCancelledendonPostExecuteyesnoyesno

demo

public class MainActivity extends ActionBarActivity {

    private URL url;
    private Context mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;

        try {
            url = new URL("...");
            DownloadTask downloadTask = new DownloadTask();
            downloadTask.execute(url);

        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

    class DownloadTask extends AsyncTask<URL, Integer, String>{

        @Override
        protected void onPreExecute() {
            Toast.makeText(mContext, "开始下载", Toast.LENGTH_SHORT).show();
        }

        @Override
        protected String doInBackground(URL... params) {
            //此处省略了具体的下载代码逻辑,返回值是下载文件的存储路径
            return Downloader.download(params[0]);
        }

        @Override
        protected void onPostExecute(String path) {
            if(TextUtils.isEmpty(path)){
                Toast.makeText(mContext, "文件下载失败", Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(mContext, "文件下载完成,存储位置:" + path, Toast.LENGTH_LONG).show();
            }
        }
    }
}

条件限制

(原因请参考下面的工作原理分析)

  1. AsyncTask的对象必须在主线程中创建

  2. execute方法必须在主线程中调用

  3. 不要手动去调用onPreExecute、doInBackground、onProgressUpdate、onPostExecute、onCancelled

  4. 一个AsyncTask只能execute一次,否则会报运行时异常

  5. Android 1.6之前,AsyncTask串行执行任务;Android 1.6 - 3.0前,AsyncTask并行执行任务;从Android 3.0开始,AsyncTask又恢复串行执行任务(如果希望它可以并行执行任务,需调用executeOnExecutor方法)

工作原理分析

一个AsyncTask的execute()方法的源码如下:

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}

这里的sDefaultExecutor是一个串行的线程池,一个进程中所有的AsyncTask会在这个串行的线程池里排队执行。
接着跟踪源码:

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

这里就看出了为什么AsyncTask不能重复execute的原因,会抛出异常。这里会首先执行onPreExecute,然后就会调用线程池的execute方法了。

下面通过SerialExecutor来分析一下线程池的的执行过程:

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

首先系统会把AsyncTask的Params参数封装为FutureTask对象,然后由SerialExecutor 的execute方法去处理,即我们之前看到的exec.execute(mFuture); 这行代码。FutureTask是一个并发类,相当于是一个Runnable。SerialExecutor 的execute方法接收到以后,会把它插入任务队列mTasks里,如果这个时候没有正在活动的AsyncTask,那么就会调用scheduleNext方法执行下一个AsyncTask。从这里可以看出,AsyncTask是串行执行的。

AsyncTask有两个线程池:SerialExecutor 和 THREAD_POOL_EXECUTOR,还有一个Handler:InternalHandler,其中SerialExecutor用于处理任务的排队,而THREAD_POOL_EXECUTOR真正的去执行任务,InternalHandler负责将执行环境切换到主线程。

FutureTask的run方法会调到mWorker的call方法,而mWorker的call方法会在线程池中执行:

public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);

            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //noinspection unchecked
            return postResult(doInBackground(mParams));
        }
    };
    ......

这里可以看出就会调用doInBackground方法了,然后返回结果传给postResult方法:

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

sHandler(InternalHandler对象)会发送MESSAGE_POST_RESULT消息。为了能使执行环境切换到主线程,InternalHandler对象必须在主线程中创建,所以也就变相要求AsyncTask的实例也必须在主线程中创建。

AsyncTask不适合执行特别耗时的后台任务,也是由于它的不可重复性和不可断点续传性。

阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页