异步任务之 AsyncTask 使用初解

在之前的博文中,我们使用 Handler 在子线程当中更新 UI,但是 Handler 机制不仅庞大,又需要处理它和线程之间的关系,这样不仅代码量庞大,也会容易出错。

Android 为我们提供了 AsyncTask 来帮助我们处理工作线程和主线程之间的关系,它在内部为我们处理了 Thread 和 Handler 之间的关系,使得我们不需要再关注 Thread 和 Handler,只需要关注我们的业务逻辑即可。

按照官方文档来说,AsyncTask 应该用于较短时间的操作(最多几秒钟),如果是长时间的线程运行,还是使用线程池等等。

AsyncTask 通常由 3 个参数 4 个回调方法构成:

3 个参数:

  • Params:用于 AsyncTask 执行任务的参数的类型
  • Progress:表示在后台线程处理的过程中,可以阶段性的发布结果的数据类型
  • Result:表示任务全部完成后所返回的数据类型

4 个回调方法:

  • onPreExecute:该方法在任务执行之前会在 UI 线程当中调用。通常我们在该方法当中设置任务,例如显示一个进度条等等。
  • doInBackground:在上一个方法执行之后立即在工作线程当中执行该方法。该方法用于执行需要耗时的操作。异步任务的参数传递给该方法,最后得到的结果也由该方法传出。可以在该方法中调用 publishProgress(Progress...) 方法来更新任务进度,该方法最终会调用 onProgressUpdate 方法。
  • onProgressUpdate:在调用了 publishProgress(Progress...) 方法之后会调用该方法,该方法运行在 UI 线程当中,可以用于显示任务进度或者提示信息。
  • onPostExecute:该方法运行在 UI 线程当中,参数为任务的最终结果,可以用来最后的更新。

示例

例如我们要实现一个功能,点击按钮,下载一张图片,下载完成后将图片设置给 ImageView,在下载的同时实时更新 Progress 的进度。

3 个参数,4 个回调该怎么设置呢?

  • Params:执行任务参数,自然是图片的 URL,所以可以是 String。
  • Progress:用于实时更新 ProgressBar 的进度,自然就是 Integre。
  • Result:下载完成后,可以返回一个 Bitmap 来设置 Image。
  • onPreExecute:弹出一个 Toast,用于提示开始下载。
  • doInBackground:在该方法中连接网络,下载图片,并实时调用 publishProgress 方法发布进度。
  • onProgressUpdate:实时更新 ProgressBar,用来显示下载进度。
  • onPostExecute:任务最终完成,更新 UI。

所以代码大致可以这样写:

public class MainActivity extends Activity {

    private ProgressBar progress;
    private Button button;
    private ImageView image;

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

        progress = findViewById(R.id.progress);
        button = findViewById(R.id.button);
        image = findViewById(R.id.image);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyAsyncTask task = new MyAsyncTask();
                task.execute("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502073520254&di=425cf90affda434dbd79ebccf0e773a0&imgtype=0&src=http%3A%2F%2Fn.sinaimg.cn%2Fgames%2Ftransform%2F20160201%2F9B6b-fxnzanh0502003.jpg");
            }
        });
    }

    class MyAsyncTask extends AsyncTask<String, Float, Bitmap> {
        Bitmap bitmap = null;

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

        @Override
        protected Bitmap doInBackground(String... params) {
            try {
                URL url = new URL(params[0]);
                URLConnection connection = url.openConnection();
                InputStream is = connection.getInputStream();
                FileOutputStream fos = openFileOutput("sex.jpg", Context.MODE_PRIVATE);
                int length = connection.getContentLength();
                int len = 0;
                byte[] buf = new byte[1024];
                float downloadSize = 0;
                while ((len = is.read(buf)) != -1) {
                    fos.write(buf, 0, len);
                    fos.flush();
                    downloadSize += len;
                    publishProgress((downloadSize / length) * 100);
                    Log.d("TTT", "" + downloadSize / length);
                }
                is.close();
                fos.close();
                bitmap = BitmapFactory.decodeFile(getApplicationContext().getFilesDir() + "/sex.jpg");
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            image.setImageBitmap(bitmap);
        }

        @Override
        protected void onProgressUpdate(Float... values) {
            super.onProgressUpdate(values);
            progress.setProgress(Math.round(values[0]));
        }
    }
}

取消任务

任务时候都可以调用 AsyncTask 的 cancel() 方法来取消任务,在调用了该方法之后,并不会停止后台线程,也就是说,doInBackground 方法还会继续执行,只是在执行完之后,不会调用 pnPostExecute 方法,而是会调用 onCancelled(Result result),不过我们可以在 doInBackground 方法当中调用 isCancelled() 判断是否已经取消任务。

注意

  • AsyncTask 对象的创建必须是在 UI 线程当中
  • 调用 AsyncTask 对象的 execute 方法必须在 UI 线程当中
  • 我们不能手动调用 AsyncTask 的四个回调方法,必须由系统自动调取
  • execute 方法只能调用一次,重复调用同一个 AsyncTask 对象的 execute 会抛出异常

例子

最后附上一个稍微全一些的例子:

public class MainActivity extends Activity {

    private ProgressBar progress;
    private Button start, cancel, stop;
    private MyAsyncTask myAsyncTask;
    private Object asyncObject = new Object();
    private boolean isWait = false;

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

        progress = findViewById(R.id.progress);
        start = findViewById(R.id.start);
        cancel = findViewById(R.id.cancel);
        stop = findViewById(R.id.stop);

        //点击按钮,开始任务
        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myAsyncTask = new MyAsyncTask();
                myAsyncTask.execute();
            }
        });

        //点击按钮,取消任务
        cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myAsyncTask.cancel(true);
            }
        });

        //点击按钮,休眠工作线程,再点击,唤醒...
        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                isWait = !isWait; 
                if (!isWait) {
                    synchronized (asyncObject) {
                        asyncObject.notify();
                    }
                }
            }
        });
    }

    class MyAsyncTask extends AsyncTask<Void, Integer, Void> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Log.d("TTT", "任务开始");//任务开始
        }

        @Override
        protected Void doInBackground(Void... params) {
            for (int i = 0; i < 20; i++) {
                if (!isCancelled()) {   //判断任务是否取消
                    synchronized (asyncObject) {
                        try {
                            if (isWait) {   //判断线程是否需要进入wait
                                asyncObject.wait();
                            }
                            publishProgress(i);//更新进度
                            try {
                                Thread.currentThread().sleep(500);//休眠500毫秒,模拟耗时操作
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }

                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                } else {
                    break;
                }
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            Log.d("TTT", "进度:" + values[0]);//更新进度
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            Log.d("TTT", "任务完成");//任务完成
        }

        @Override
        protected void onCancelled(Void aVoid) {
            super.onCancelled(aVoid);
            Log.d("TTT", "任务取消");//任务取消
        }
    }
}