AsyncTask 使用及封装实践

前言

IntentService使用及源码分析

HandlerThread源码分析

AsyncTask使用及封装实践

AsyncTask源码分析

这篇博客主要是讲解AsyncTask的使用及封装实践,对于新手们还是有很大的参考意义的,尤其是AsyncTask的封装实践这部分。对于老鸟们,你们可以跳过了。同时需要声明的一点是,下面下载的例子只是进行简单的下载而已,并没有支持断点续传下载。需要的话请自行到github上面找相应的库,因为这并不是本篇博客的重点。

这篇博客主要讲解以下问题:

  • AsyncTask的简单使用
  • AsyncTask的封装使用
  • AsyncTask使用注意事项

AsyncTask的使用例子

简介

AsyncTask ,异步任务。没错,就想字面上理解的那样。它允许我们在子线程执行耗时任务,在UI 线程更新操作(如更新进度条等)。简单来说,就是帮我们做好了子线程与UI 线程的通讯,我们只需要调用响应的方法实现即可。底层是用Handler消息机制实现的。

在Android开发中,我们经常需要下载各种东西,为了给用户较好的体验,我们经常需要显示下载进度。今天我们用以这个为例子,来教大家怎样使用AsyncTak。当然,github上面有很多开源库,实现断点下载,文件重命名等。不过这些不是本篇博客的重点。

效果图

AsyncTask的主要几个方法

  • Void onPreExecute()

在task 任务开始执行的时候调用,在doInBackground(Params… params)方法之前调用,在主线程中执行

  • Result doInBackground(Params… params)

主要用来执行耗时操作,在子线程中执行,Params为我们参数的类型。而Result这个泛型,是我们返回的类型(可以是Integer,Long,String等等类型,只要不是八种基本类型就OK),同时 Result 的类型将作为 onPostExecute(Result result)的参数。

  • Void onProgressUpdate(Progress… values)

    Runs on the UI thread after publishProgress(Progress…) is invoked. 当我们调用 publishProgress()方法的时候,会调用 onProgressUpdate()这个方法

  • Void onPostExecute(Result result)
    在doInBackground()方法执行完毕之后,会调用这个方法,是在主线程中执行的。但如果我们手动调用了cancelled()方法,那么这个方法将不会被调用。

  • void onCancelled()

在Task 任务取消的时候会调用

  • execute(Params… params)

Executes the task with the specified parameters.当我们调用这个方法的时候,会执行任务

  • executeOnExecutor(Executor exec, Params… params)

在指定的线程池里面执行Task

需要注意的是,Params,Progress,Result 并不是一种特定的类型,它其实是泛型,它支持除了八种基本类型之外的类型,跟普通的泛型一样。

AsyncTask使用的几个步骤

这里我们以下载一个apk为例讲解

  1. 写一个类继承AsyncTask,并传入Params,Progress,Result 。三个参数的类型。

比如我们传入的 Params,Progress,Result 的参数的类型分别为 Void, FileInfo, FileInfo,那我们可以这样写。

private class MyDownloadTask extends AsyncTask{

 }

那Void, FileInfo, FileInfo,这几个参数的类型在哪里体现出来呢?

请看下面注释

private class MyDownloadTask extends AsyncTask {

   ---

   // 方法参数的类型为Void,跟我们传入的Void一致,返回类型为 FileInfo ,跟我们传入Result的类型FileInfo一致
    @Override
    protected FileInfo doInBackground(Void... params) {

    }

   // 方法参数类型为FileInfo,跟我们传入Progress的类型FileInfo一致
    @Override
    protected void onProgressUpdate(FileInfo... values) {

    }


   // 方法参数FileInfo,跟我们传入Result的类型FileInfo一致
    @Override
    protected void onPostExecute(FileInfo fileInfo) {


    }


}
  1. 如果我们更新进度的话,需要重写 onProgressUpdate()方法,并在doInBackground()方法里面调用publishProgress()方法
protected FileInfo doInBackground(Void... params) {

   publishProgress(fileInfo);


}

@Override
protected void onProgressUpdate(FileInfo... values) {
    super.onProgressUpdate(values);
    refreshProgress(values[0]);
}
  1. 当我们调用execute(Params… params) 或者 executeOnExecutor(Executor exec, Params… params) 方法的时候,Task将被防盗相应的 Executor 执行。
 MyDownloadTask myDownloadTask = new MyDownloadTask(mDownloadUrl, mDstPath);
 myDownloadTask.execute();

完整的Task代码如下

private class MyDownloadTask extends AsyncTask {

    String mDownLoadUrl;
    String mDstPath;

    public MyDownloadTask(String downloadUrl, String dstPath) {
        this.mDownLoadUrl = downloadUrl;
        this.mDstPath = dstPath;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        start();
    }

    @Override
    protected FileInfo doInBackground(Void... params) {
        //url字符串,检查网址是否已http:// 开头
        mDownLoadUrl = (mDownLoadUrl.startsWith("http://")) ? mDownLoadUrl : "http://" +
                mDownLoadUrl;
        Log.d(TAG, "doInBackground: mDownLoadUrl=" + mDownLoadUrl);
        Log.d(TAG, "doInBackground: mDstPath=" + mDstPath);
        URL url = null;
        FileInfo fileInfo = null;
        int contentLength = -1;
        int downloadLength = 0;
        OutputStream output = null;
        InputStream istream = null;
        try {
            url = new URL(mDownLoadUrl);
            //打开到url的连接
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            contentLength = connection.getContentLength();
            Log.i(TAG, "doInBackground: contentLength=" + contentLength);
            //O部分,大体来说就是先检查文件夹是否存在,不存在则创建
            istream = connection.getInputStream();
            String filename = mDownLoadUrl.substring(mDownLoadUrl.lastIndexOf("/") + 1);

            File dir = new File(mDstPath);
            if (!dir.exists()) {
                dir.mkdir();
            }
            File file = new File(mDstPath + filename);
            // 如果存在同名文件,重命名
            if (file.exists()) {
                file = FileUtils.rename(file.getPath());
            }


            output = new FileOutputStream(file);
            byte[] buffer = new byte[1024 * 4];
            int count = 0;
            int len = -1;
            while ((len = istream.read(buffer)) != -1) {
                output.write(buffer, 0, len);
                downloadLength += len;

                if (count == 10) {
                    fileInfo = new FileInfo(contentLength, downloadLength, file, file.getPath
                            (), file.getName());
                    publishProgress(fileInfo);
                    count = 0;
                }
                count++;

            }
            //                有可能count还没有走到10
            fileInfo = new FileInfo(contentLength, downloadLength, file, file.getPath(), file
                    .getName());
            publishProgress(fileInfo);
            output.flush();
            output.close();
            istream.close();

        } catch (Exception e) {
            e.printStackTrace();
            try {
                IOUtils.close(output);
                IOUtils.close(istream);
            } catch (IOException e1) {
                e1.printStackTrace();
            }

        } finally {
            try {
                IOUtils.close(output);
                IOUtils.close(istream);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
        return fileInfo;
    }

    @Override
    protected void onProgressUpdate(FileInfo... values) {
        super.onProgressUpdate(values);
        refreshProgress(values[0]);
    }

    @Override
    protected void onPostExecute(FileInfo fileInfo) {
        super.onPostExecute(fileInfo);
        downloadfinish(fileInfo);

    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

private void start() {
    mTvDownloadText.setText("开始下载");
    mProgressBar.setMax(100);
    mProgressBar.setProgress(0);
}

private void downloadfinish(FileInfo fileInfo) {
    Log.i(TAG, "onPostExecute: 下载完成=" + fileInfo.mPath);
    Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
}

private void refreshProgress(FileInfo value) {
    FileInfo fileInfo = value;
    if (fileInfo != null) {
        mProgressBar.setMax((int) fileInfo.mLength);
        mProgressBar.setProgress((int) fileInfo.mDownloadLength);
        mDownText = fileInfo.mFile.getName() + "下载了" + fileInfo.mDownloadLength + "总长度是" +
                fileInfo.mLength;
        mTvDownloadText.setText(mDownText);
    }
}

AsyncTask的封装使用

前面我们讲完了AsyncTask的基本使用,不知道你有没有发现,其实代码耦合性是挺高的,

  • 我们直接在 onProgressUpdata(),onPostExecute()方法里面更新我们的界面,即我们的AsyncTask访问了我们Activity里面的控件,那如果我们修改了Activity的控件,我们岂不是又要去阅读AsyncTask的代码,去做相应的修改。
  • 下一次我们如果要下载别的东西,按照我们前面的代码,我们又要重新复制一份,这样无疑是做了很多重复的工作。

说到这样,我相信大多数人的第一感觉就是把AsyncTask提取为外部类,封装起来。是的,确实,我们就是要把AsyncTask提取为外部类。那提取为歪不累之后呢?我们要访问Activity里面的空间,要怎样访问呢?

  1. 在Activity里面定义静态方法
  2. 把需要访问的View对象通过构造函数传递进来
  3. 采用接口回调机制

前面说到的三种方法,是可以做到AsyncTask与外界进行通讯的。但第一第二中方法明显不行。原因如下:

  • 第一种方法定义静态方法,那View对象也必须定义为static变量,这static变量的级别比较高,不易被垃圾回收机制回收,易发生没存泄露。
  • 第二种方法,把需要访问的View对象通过构造函数传递进来。如果需要访问的对象少的话,勉强可以接受,如果多的话,那岂不是要定义很多成员变量。不过最致命的还算是代码耦合性太高了。还不如AsyncTask直接作为内部类。

好了,说了这么多,下面我们一起来看怎样使用接口回调机制来进行解耦。

AsyncTask 使用接口回调机制来进行解耦

  1. 使用接口回调机制,首先我们必须有一个接口
public interface DownloadListener {

    void onStart();
    void onProgress(FileInfo fileInfo);
    void onFinish(FileInfo FileInfo);
    void onPaused(FileInfo fileInfo);
    void onCancled();
}
  1. 将DownLoadTask提取为一个外部类,并将需要传递的参数传递进来
public class DownloadTask extends AsyncTask {

    private  String mDownloadUrl;
    private final String mDstPath;
    private final String mFileName;
    private final DownloadListener mDownloadListener;

    public DownloadTask(String downloadUrl, String dstPath, String fileName, DownloadListener downloadListener){
        mDownloadUrl = downloadUrl;
        mDstPath = dstPath;
        mFileName = fileName;
        mDownloadListener = downloadListener;
    }
}
  1. 在相应的地方调用我们接口的方法
public class DownloadTask extends AsyncTask {

    ----

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mDownloadListener.onStart();
    }

    @Override
    protected FileInfo doInBackground(Void... params) {

       ----
            int len = -1;
            while ((len = istream.read(buffer)) != -1) {
                output.write(buffer, 0, len);
                downloadLength += len;

                if (count == 10) {
                    fileInfo = new FileInfo(contentLength, downloadLength, file, file.getPath
                            (), file.getName());
                    publishProgress(fileInfo);
                    count = 0;
                }
                count++;

            }
            //                有可能count还没有走到10
            fileInfo = new FileInfo(contentLength, downloadLength, file, file.getPath(), file
                    .getName());
            publishProgress(fileInfo);
            output.flush();
            output.close();
            istream.close();



        return fileInfo;
    }

    @Override
    protected void onProgressUpdate(FileInfo... values) {
        super.onProgressUpdate(values);
        mDownloadListener.onProgress(values[0]);
    }

    @Override
    protected void onPostExecute(FileInfo fileInfo) {
        super.onPostExecute(fileInfo);
        mDownloadListener.onFinish(fileInfo);
    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
        mDownloadListener.onCancled();
    }
}

使用

以后我们要下载东西,只需要调用下面的方法即可。同时,如果产品再更改需求,比如,从显示一个进度条ProgressDialog对话框,改成显示一个ProgressBar,我们只需要在
onProgress()里面做相应的修改就好了,在也不用去阅读DownloadTask里面的代码呢?减少了代码的耦合性,是不是瞬间感觉世界很美好呢?

mDownloadTask = new DownloadTask(mDownloadUrl, mDstPath, null, new
        DownloadListener() {
    @Override
    public void onStart() {
          start();
    }

    @Override
    public void onProgress(FileInfo fileInfo) {
        refreshProgress(fileInfo);
    }

    @Override
    public void onFinish(FileInfo fileInfo) {
        downloadfinish(fileInfo);
    }

    @Override
    public void onPaused(FileInfo fileInfo) {

    }

    @Override
    public void onCancled() {

    }
});
mDownloadTask.execute();

AsyncTask使用的注意事项

  • The AsyncTask class must be loaded on the UI thread. This is done automatically as of JELLY_BEAN.
  • The task instance must be created on the UI thread.(AsyncTask必须在UI 线程里面初始化
  • execute(Params…) must be invoked on the UI thread.
  • Do not call onPreExecute(), onPostExecute(Result), doInBackground(Params…), onProgressUpdate(Progress…) manually.(不要手动地调用 onPreExecute(), onPostExecute(Result), doInBackground(Params…), onProgressUpdate(Progress…) 这些方法)
  • The task can be executed only once (an exception will be thrown if a second execution is attempted.) (Task任务只能被执行一次,否则会抛出异常)

相关知识点推荐:

IntentService使用及源码分析

HandlerThread源码分析

AsyncTask使用及封装实践

AsyncTask源码分析

Demo下载地址

IT文库 » AsyncTask 使用及封装实践
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址