AsyncTask 的使用方式和版本演进

1.前言

AsyncTask 类位于 android.os 包下。它使得开发者能够合适而容易地使用 UI 线程。具体来说,AsyncTask 允许执行后台任务并把执行结果发布到 UI 线程,无需开发者管理线程和 Handler。AsyncTask 适合用于执行短时(最多几秒钟)的后台任务。如果执行长时间的后台任务,强烈建议使用 java.util.concurrent 包下提供的 API,如 ExecutorThreadPoolExecutorFutureTask
本篇基于 Android5.0, 来介绍 AsyncTask 的使用方式和版本演进。

2.使用方式

首先,看一下 AsyncTask 类的声明:

public abstract class AsyncTask<Params, Progress, Result>

这是一个抽象类,说明不允许直接使用 AsyncTask;同时,它也是一个泛型类,拥有三个泛型,即 ParamsProgressResult。三个泛型的含义:

  • Params,当任务执行时发送任务的参数类型;
  • Progress,在后台任务执行过程中发布的进度单元的类型;
  • Result,后台任务返回的结果的类型。

注意
一个异步任务并不总是使用所有的泛型。使用 Void,来标明不使用某个泛型。

AsyncTask 提供了 4 个核心 API。当任务执行时,这些方法就会被调用:

  • onPreExecute():在任务执行前,在主线程调用。一般用来为任务执行做一些准备工作,比如在界面上显示一个等待加载的进度圈。
  • doInBackground(Params... params):在 onPreExecute() 完成后马上在后台线程调用。这个方法用来执行耗时的后台任务。这个方法会传入后台任务需要的参数。这个方法的返回值必须是后台任务的结果,并且返回值会作为 onPostExecute(Result result) 的参数。在这个方法里,可以调用 publishProgress(Progress... values) 来发布后台任务的执行进度到 UI 线程。这个方法是在实例化 AsyncTask 时必须覆写的方法。
  • onProgressUpdate(Progress... values):在调用 publishProgress(Progress... values) 后在主线程被调用。这个方法的执行时机是未定义的。用于后台任务进行过程中,在主线程展示进度信息。例如,展示一个进度条或者显示变动的进度数值。
  • onPostExecute(Result result):在后台任务结束后,在主线程被调用。参数就是后台任务的执行结果,就是 doInBackground(Params... params) 方法的返回值。

那么如何取消一个任务呢?

通过调用 AsyncTask 类的 cancel(boolean mayInterruptIfRunning) 方法,就可以在任何时候取消一个任务。调用这个方法之后,上面 4 个核心方法的执行序列会发生一些变化。具体而言,调用了 cancel 方法会引起后续调用的 isCancelled() 方法返回 true。在 cancel 方法调用后,在 doInBackground(Params... params) 结束后,将会调用 onCancelled(Result result) 方法而非 onPostExecute(Result result) 方法。onCancelled(Result result) 方法也是在主线程被调用的。需要注意的是,为尽快确保一个任务是否已被取消,在 doInBackground(Params... params) 方法中需要判断 isCancelled() 方法的返回值。

下面通过一实例的例子,展示 AsyncTask 的使用方式。

相关代码如下:

public class AsyncTaskActivity extends Activity {
    private static final String TAG = AsyncTaskActivity.class.getSimpleName();
    private static final String url = "https://github.com/jhwsx/android-art-research/raw/"
            + "f6257e9f1e46848400f7ff2635991fd5a850d4f8/chapter_11/app-debug.apk";
    private TextView mTvProgress;
    private TextView mTvLength;
    private DownloadTask mDownloadTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_asynctask);
        Button btnStartDownload = (Button) findViewById(R.id.btn_start_download);
        Button btnCancelDownload = (Button) findViewById(R.id.btn_cancel_download);
        mTvProgress = (TextView) findViewById(R.id.tv_download_progress);
        mTvLength = (TextView) findViewById(R.id.tv_download_length);
        btnStartDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDownloadTask = new DownloadTask();
                mDownloadTask.execute(url);
            }
        });
        btnCancelDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDownloadTask.cancel(false);
            }
        });
    }

    public class DownloadTask extends AsyncTask<String, Integer, Long> {

        private ProgressDialog mProgressDialog;

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            mProgressDialog = new ProgressDialog(AsyncTaskActivity.this);
            mProgressDialog.setTitle("下载");
            mProgressDialog.setMessage("获取数据中...");
            mProgressDialog.show();
            Log.d(TAG, "onPreExecute: threadName=" + Thread.currentThread().getName());
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            if (mProgressDialog != null && mProgressDialog.isShowing()) {
                mProgressDialog.dismiss();
                mProgressDialog = null;
            }
            Log.d(TAG, "onProgressUpdate: threadName=" + Thread.currentThread().getName());
            mTvProgress.setText("下载进度: " + values[0] + "%");
        }

        @Override
        protected Long doInBackground(String... urls) {
            Log.d(TAG, "doInBackground: threadName=" + Thread.currentThread().getName());
            HttpURLConnection connection = null;
            InputStream inputStream = null;
            OutputStream outputStream = null;
            long contentLength = -1;
            try {
                URL url = new URL(urls[0]);
                connection = (HttpURLConnection) url.openConnection();
                connection.connect();
                if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                    return -1L;
                }
                contentLength = connection.getContentLength();
                inputStream = connection.getInputStream();
                outputStream = new FileOutputStream(new File(getExternalFilesDir(null), "download.apk"));
                byte[] buffer = new byte[4 * 1024];
                int total = 0;
                int length;
                while ((length = inputStream.read(buffer)) != -1) {
                    total += length;
                    final int progress = (int) (total * 100f / contentLength);
                    if (isCancelled()) {
                        break;
                    }
                    publishProgress(progress);
                    outputStream.write(buffer, 0, length);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                CloseUtils.closeIOQuietly(inputStream, outputStream);
                if (connection != null) {
                    connection.disconnect();
                }
            }
            return contentLength;
        }

        @Override
        protected void onPostExecute(Long aLong) {
            super.onPostExecute(aLong);
            Log.d(TAG, "onPostExecute: threadName=" + Thread.currentThread().getName());
            mTvLength.setText("下载字节数: " + aLong);
            mTvLength.setVisibility(View.VISIBLE);
        }

        @Override
        protected void onCancelled() {
            super.onCancelled();
            Log.d(TAG, "onCancelled: " + Thread.currentThread().getName());
            mTvLength.setVisibility(View.VISIBLE);
            mTvLength.setText("任务已取消");
        }
    }
}

运行日志:
当点击开始下载直至下载完成时,

D/AsyncTaskActivity: onPreExecute: threadName=main
D/AsyncTaskActivity: doInBackground: threadName=AsyncTask #1
D/AsyncTaskActivity: onProgressUpdate: progress=0, threadName=main
...
D/AsyncTaskActivity: onProgressUpdate: progress=99, threadName=main
D/AsyncTaskActivity: onProgressUpdate: progress=100, threadName=mai
D/AsyncTaskActivity: onPostExecute: threadName=main

从日志中可以看到,

  • 4 个核心方法的调用次序:onPreExecute 最先执行,接着是 doInBackground,然后是多个 onProgressUpdate,最后是 onPostExecute
  • 4 个核心方法的执行线程:只有 doInBackground 在后台线程中执行,其他都是在主线程执行。

当点击开始下载后,在获取下载进度后,再点击取消下载时:

D/AsyncTaskActivity: onPreExecute: threadName=main
D/AsyncTaskActivity: doInBackground: threadName=AsyncTask #2
D/AsyncTaskActivity: onProgressUpdate: progress=0, threadName=main
...
D/AsyncTaskActivity: onProgressUpdate: progress=86, threadName=main
D/AsyncTaskActivity: onCancelled: main

从日志中看以看到:

  • 在取消任务时,就不会再调用 onPostExecute 方法,而是调用 onCancelled 方法;
  • onCancelled 方法是在主线程被调用的。

使用 AsyncTask 需要注意的地方:

  • AsyncTask 的类必须在主线程中加载。在 Android4.1 之前,这需要开发者自己完成;在 Android4.1 之后,这已经被系统自动完成,具体是在 ActivityThread 类的 main 方法中调用了 AsyncTaskinit 方法;在 Android5.1 之后,是在 AsyncTask 中使用 Looper.getMainLooper() 类构造 InternalHandler 静态成员变量,而不再使用 AsyncTaskinit 方法了。
  • AsyncTask 的对象必须在主线程中创建。
  • AsyncTaskexecute 方法必须在 UI 线程调用。
  • 不要手动调用 onPreExecuteonPostExecutedoInBackgroundonProgressUpdate 方法。
  • 一个 AsyncTask 对象只能执行一次(再次执行,会抛出异常),这一点可以在 executeOnExecutor 方法中看出来。
  • 要记得在合适的地方清理或者说撤销 AsyncTask,否则会引起内存泄露,或者引起 UI 更新问题。可以选择在 onStop 或者 onDestroy 方法中调用 AsyncTaskcancel 方法。 cancel 方法有两种工作模式:温和的和粗暴的,需要选择使用。它们的区别是:cancel(false) 方法,会温和地设置 isCancelled() 的状态为 true。随后,AsyncTask 会检查 doInBackground 方法中的 isCanclled() 方法的状态,然后选择提前结束运行。而 cancel(true) 方法,则会简单粗暴地结束 doInBackground 方法所在的线程。

3.版本演进

当 Android 初次引入 AsyncTask 时,AsyncTask 是以串行方式执行在单线程上的。从 Android1.6 开始,AsyncTask 转而使用线程池,允许多个任务并行执行。从 Android3.0 开始,AsyncTask 是执行在单线程上,这是为了避免并发执行引起的程序错误。不过,如果开发者确实需要并行执行任务,那么可以调用 executeOnExecutor 方法,使用 THREAD_POOL_EXECUTOR 作为参数。

4.总结

本文先介绍了 AsyncTask 的 泛型,核心 API;接着引入了一个实际的例子,验证了一些描述;最后简单介绍了 AsyncTask 的版本演进。希望能加深对于 AsyncTask 类的认识与理解。

参考

Android开发艺术探索
Android编程权威指南
[Android]AsyncTask 解析

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

willwaywang6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值