巧用AsyncTask的onProgressUpdate回调

AsyncTask简介

AsyncTask相信大家已经特别熟悉了,它是Android提供的一个轻量级的异步处理类。它简单易用,可以很容易地执行后台任务,也可以很方便地将后台任务执行的进度与结果发送到UI线程。

爱与恨

由于Android的单线程模型,异步处理在Android开发中显得尤为重要,因此线程中的交互不可避免。假如我们利用Android消息机制自己实现这个过程,则较为复杂且有着发生各种各样错误的风险。使用AsyncTask则较为方便,它为我们封装好了工作线程的执行与交互的过程,我们仅需要派生一个AsyncTask的子类,实现几个回调,然后在UI线程启动这个task即可,整个使用过程相当简单。
但世界上没有十全十美的东西,AsyncTask也一样,虽然它轻便易用,但也有一些坑,让人又爱又恨。
如AsyncTask跟系统版本的关系,AsyncTask在API 4之前是串行执行的,在API 4~12则是并行执行的,等到了API 13+,它又改回了串行执行,另外AsyncTask.executeOnExecutor()则是在API 11才开始支持的,因此如果想让各版本实现一致的用户体验,就不得不注意这些陷阱。

AsyncTask虽然简单,但也有一些场景并非最佳选择,如一个后台任务,并不需要传入参数也不需要其返回数据,这种场景完全可以使用一个Thread来替代。还有假如某个后台任务需要Looper,则使用HandlerThread可能是更好的选择。

由于本文主题为“巧用onProgressUpdate回调”,因此以上只是简单概括一下AsyncTask的优缺点,真要写的话,就成了一篇很长的文章了,相关的文章网上也有很多。如果有不清楚的也可留言讨论,另外建议看一下AsyncTask的源码,下面切入主题。

巧用onProgressUpdate回调

先来看下AsyncTask的几个主要回调:

  • onPreExecute 此回调是在主线程执行的,用来在后台任务执行前做一些预处理的工作。
  • doInBackground 此回调是最重要的一个,用来处理后台任务,因此它必须在子线程运行。
  • onProgressUpdate 此回调会将后台任务的执行进度发到UI线程,因此它是在UI线程中运行的,而且它是与doInBackground同时运行的。
  • onPostExecute 顾名思义,此回调表示当后台任务执行完了,会通过它将结果返回给UI线程,它是在UI线程运行的。
  • onCancelled 当取消一个任务时,会走到这个回调。

创建一个AsyncTask子类的方法一般如下所示:

private static class ImageDownloadTask extends AsyncTask<String, int, Object> {}

值得注意的是AsyncTask的实现类需要定义为静态内部类或者独立类,否则会持有外部类的引用,可能造成内存泄露。

上面的三个参数,第一个表示输入的参数,可以从UI线程传到子线程;第二个参数一般表示任务的执行进度,所以最常见的就是int类型;第三个参数则表示子线程执行完任务,返回给UI线程的数据类型,可为任意对象。

因此对于回调的使用,我们通常是这样的:doInBackground执行后台任务;onProgressUpdate用来更新后台任务的执行进度,最常见的如下载进度;执行完任务后,使用onPostExecute将结果返回给UI线程。

这里来展示一种巧用onProgressUpdate回调的场景,假设一个任务需要下载多张图片。可以让onProgressUpdate回调里返回下载好的图片,这样就可以在下载过程中,下载好一张图片展示一张,而不必等到多张图片全部下载完成后再通过onPostExecute回调将结果返回,实时渲染的用户体验显然更好,至于onPostExecute,则完全可以不用了。如果你想问如何确保onProgressUpdate在下载好每一张图片后会被调用?那么还记得publishProgress方法吗?它在源代码里的定义如下:

/**
 * This method can be invoked from {@link #doInBackground} to
 * publish updates on the UI thread while the background computation is
 * still running. Each call to this method will trigger the execution of
 * {@link #onProgressUpdate} on the UI thread.
 *
 * {@link #onProgressUpdate} will not be called if the task has been
 * canceled.
 *
 * @param values The progress values to update the UI with.
 *
 * @see #onProgressUpdate
 * @see #doInBackground
 */
@WorkerThread
protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}

注释已经写的很明确了,只需要在doInBackground里的合适地方调用publishProgress就可以触发onProgressUpdate回调了。

代码示例

这里就是上述巧用场景的具体实现,一个Activity界面,一个下载按钮,点击后就会启动一个AsyncTask去下载4张图片,同时显示一个进度条,下载完成后,进度条消失。

界面布局

该示例的界面布局如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="me.geed.cleverasynctask.MainActivity"
tools:showIn="@layout/activity_main">

<ProgressBar
    android:id="@+id/progress"
    style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true"
    android:visibility="gone"/>

<Button
    android:id="@+id/btn_start"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_below="@+id/progress"
    android:text="开始下载"/>

<ScrollView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_below="@+id/btn_start">

    <LinearLayout
        android:id="@+id/lL_image_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    </LinearLayout>
</ScrollView>
</RelativeLayout>
相关控件及界面的初始化

相关控件及变量的定义如下:

private static final String[] IMAGE_URLS = {
        "http://img0w.pconline.com.cn/pconline/1401/15/4172339_touxiang/spcgroup/width_640,qua_30/23.jpg",
        "http://img0w.pconline.com.cn/pconline/1401/15/4172339_touxiang/spcgroup/width_640,qua_30/22.jpg",
        "http://img0w.pconline.com.cn/pconline/1401/15/4172339_touxiang/spcgroup/width_640,qua_30/24.jpg",
        "http://img0w.pconline.com.cn/pconline/1401/15/4172339_touxiang/spcgroup/width_640,qua_30/26.jpg"
};

private boolean hasExecuted = false;
ImageDownloadTask mImageDownloadTask;
ProgressBar mProgressBar;
LinearLayout mLinearLayout;
Button mButton;

初始化如下:

mLinearLayout = (LinearLayout) findViewById(R.id.lL_image_container);
mProgressBar = (ProgressBar) findViewById(R.id.progress);
mProgressBar.setMax(IMAGE_URLS.length);
mButton = (Button) findViewById(R.id.btn_start);
mImageDownloadTask = new ImageDownloadTask(this);
AsyncTask的定义

AsyncTask定义为一个内部类,具体如下:

/**
 * AsyncTask的实现类需要定义为静态内部类或者独立类,否则会持有外部类的引用,可能造成内存泄露
 */
private static class ImageDownloadTask extends AsyncTask<String, Bitmap, Void> {

    private MainActivity mActivity;
    private int mCount = 0;

    public ImageDownloadTask(MainActivity activity) {
        this.mActivity = activity;
    }

    public void setActivity(MainActivity activity) {
        this.mActivity = null;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mActivity.mProgressBar.setVisibility(View.VISIBLE);
        mActivity.mProgressBar.setProgress(0);
    }

    @Override
    protected Void doInBackground(String... urls) {
        for (String url : urls) {
            if (!isCancelled()) {
                Bitmap bitmap = downloadImage(url);
                publishProgress(bitmap);
            }
        }
        return null;
    }

    @Override
    protected void onProgressUpdate(Bitmap... values) {
        super.onProgressUpdate(values);
        if (mActivity != null) {
            mActivity.mProgressBar.setProgress(++mCount);
            ImageView iv = new ImageView(mActivity);
            iv.setImageBitmap(values[0]);
            mActivity.mLinearLayout.addView(iv);
        }
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        if (mActivity != null) {
            mActivity.mProgressBar.setVisibility(View.GONE);
        }
    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
        if (mActivity != null) {
            mActivity.mProgressBar.setVisibility(View.GONE);
        }
    }

    /**
     * 图片下载,真正app中的实现可能并非这样
     *
     * @param imageUrl
     * @return
     */
    private Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeStream((InputStream) new URL(imageUrl).getContent());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return bitmap;
    }

}
AsyncTask的启动执行

启动一个任务,注意每个AsyncTask只能执行一次,否则会报错。

mButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (!hasExecuted && mImageDownloadTask.getStatus() != AsyncTask.Status.RUNNING) {
                mImageDownloadTask.execute(IMAGE_URLS);
                hasExecuted = true;
            } else {
                Toast.makeText(MainActivity.this, "下载已完成,且每个Task只能执行一次", Toast.LENGTH_SHORT).show();
            }
        }
    });
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值