美文网首页
Android 异步任务AsyncTask学习

Android 异步任务AsyncTask学习

作者: JustDo23 | 来源:发表于2016-11-25 22:15 被阅读82次

    引言:搞 Android 这么久了,一直没有主动去学习使用 AsyncTask ,现在应该很少有人在使用了,但面试中似乎总有人会问。最近一不小心在某项目中看到了相关的代码,就决定对 AsyncTask 进行一番学习。

    时间:2016年11月20日11:51:41

    作者:JustDo23

    邮箱:JustDo_23@163.com

    01. 背景

    首先,Android 中子线程是不能进行 UI 操作的。单线程操作避免了 UI 混乱的情况。其次,在主线程不能进行耗时操作,否则会阻塞主线程,造成 ANR。因此,将耗时操作放在子线程成为了必然,子线程将处理的结果交给 UI 线程进行 UI 更新,线程间通信必不可少。说到这里,最先想起来的就是 Android 中的 Handler 消息处理机制,除此之外 Android 中还封装了一个抽象类 AsyncTaskAsyncTask其实是对HandlerThread的封装,再者就是AsyncTask内部是线程池实现。

    在单线程模式中需要始终明确两条:

    • UI 线程中能耗时操作,不能被阻塞
    • 非 UI 线程不能直接去更新 UI

    02. 介绍

    官方网站上介绍到AsyncTask是方便的线程操作,允许在后台进行操作并将结果返回给 UI 线程。建议在AsyncTask中执行比较短的操作,如果是灰常灰常耗时的操作则强烈建议使用Executor等线程池进行。一个异步任务定义3个泛型参数Params,Progress,Result,4个步骤onPreExecute,doInBackground,onProgressUpdate,onPostExecute;三个泛型就是启动参数,任务进度,返回结果,四个步骤就是启动准备,后台执行,任务进度,任务结果

    03. 入门代码

    新建一个SimpleTask继承AsyncTak,接着需要指定三个泛型,看到报错提示必须重写doInBackground方法。然后继续重写其他的方法。

    /**
     * 简单使用
     *
     * @author JustDo23
     */
    public class SimpleTask extends AsyncTask<Void, Void, Void> {
    
      /**
       * 异步操作执行前初始化操作
       */
      @Override
      protected void onPreExecute() {
        super.onPreExecute();
        LogUtil.e("--->onPreExecute()");
      }
    
      /**
       * 异步执行后台线程将要完成的任务[这个是必须重写的方法]
       *
       * @param params 泛型中的参数
       */
      @Override
      protected Void doInBackground(Void... params) {
        LogUtil.e("--->doInBackground()");
        publishProgress();// 进行进度更新
        return null;
      }
    
      /**
       * 异步任务[doInBackground]完成后系统自动回调
       *
       * @param result [doInBackground]返回的结果
       */
      @Override
      protected void onPostExecute(Void result) {
        super.onPostExecute(result);
        LogUtil.e("--->onPostExecute()");
      }
    
      /**
       * 进度更新 [doInBackground]方法中调用 publishProgress 方法后执行
       *
       * @param progress
       */
      @Override
      protected void onProgressUpdate(Void... progress) {
        super.onProgressUpdate(progress);
        LogUtil.e("--->onProgressUpdate()");
      }
    
      @Override
      protected void onCancelled(Void aVoid) {
        super.onCancelled(aVoid);
      }
    
      @Override
      protected void onCancelled() {
        super.onCancelled();
      }
    
    }
    

    在 Activity 中的onCreate方法中进行调用

    new SimpleTask().execute();
    

    打印出执行步骤

    E/JustDo23: --->onPreExecute()
    E/JustDo23: --->doInBackground()
    E/JustDo23: --->onProgressUpdate()
    E/JustDo23: --->onPostExecute()
    

    04. 异步加载网络图片

    布局代码

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical"
      android:padding="20dp">
    
      <ImageView
        android:id="@+id/iv_net"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    
      <ProgressBar
        android:id="@+id/pb_loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:visibility="gone"/>
    
    </RelativeLayout>
    

    网络加载代码

    /**
     * 展示图片
     *
     * @author JustDo23
     */
    public class ImageShowActivity extends Activity {
    
      private ImageView iv_net;// 图片
      private ProgressBar pb_loading;// 加载圈
    
      private String imageUrl = "http://img4.duitang.com/uploads/item/201611/03/20161103224830_YGisc.thumb.700_0.jpeg";
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image_show);
        iv_net = (ImageView) findViewById(R.id.iv_net);
        pb_loading = (ProgressBar) findViewById(R.id.pb_loading);
    
        new ImageTask().execute(imageUrl);// 在这里进行调用
      }
    
      class ImageTask extends AsyncTask<String, Integer, Bitmap> {
    
        @Override
        protected void onPreExecute() {
          super.onPreExecute();
          pb_loading.setVisibility(View.VISIBLE);// 先将 loading 显示
        }
    
        @Override
        protected Bitmap doInBackground(String... params) {
          String url = params[0];// 从参数中获取网络地址
          Bitmap bitmap = null;// 从网络加载的图片
          try {
            Thread.sleep(3000);// 以下是进行网络获取图片
            URLConnection urlConnection = new URL(url).openConnection();
            InputStream inputStream = urlConnection.getInputStream();
            BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
            bitmap = BitmapFactory.decodeStream(bufferedInputStream);
            inputStream.close();
            bufferedInputStream.close();
          } catch (Exception e) {
            e.printStackTrace();
          }
          return bitmap;// 将加载的图片返回
        }
    
        @Override
        protected void onPostExecute(Bitmap bitmap) {
          super.onPostExecute(bitmap);
          pb_loading.setVisibility(View.GONE);// 将 loading 隐藏
          iv_net.setImageBitmap(bitmap);// 界面上设置显示图片
        }
      }
    }
    

    05. 进度条显示进度

    使用水平的进度条和一个 for 循环来模拟一下网络加载进度及界面的更新

    /**
     * 利用 AsyncTask 实现进度加载显示
     *
     * @author JustDo23
     */
    public class LoadingActivity extends Activity {
    
      private ProgressBar pb_loading;// 加载条
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.acitvity_loading);
        pb_loading = (ProgressBar) findViewById(R.id.pb_loading);
    
        new LoadingTask().execute();
      }
    
      class LoadingTask extends AsyncTask<Void, Integer, Void> {
    
        @Override
        protected Void doInBackground(Void... params) {
          try {// 循环模拟加载进度
            for (int i = 0; i <= 100; i++) {
              LogUtil.e("progress = " + i);
              publishProgress(i);// 去更新界面
              Thread.sleep(300);
            }
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          return null;
        }
    
        @Override
        protected void onProgressUpdate(Integer... progress) {
          pb_loading.setProgress(progress[0]);
        }
      }
    }
    

    频繁关闭启动界面,发现界面上的进度条有时会一直没有进度;在日志打印过程中,发现AsyncTask从0到100的打印,一圈打印完了再打印第二圈。也就是AsyncTask内部维护的 task 是串行的,一个执行完了,再去执行下一个。

    06. 优化

    public class LoadingActivity extends Activity {
    
      private ProgressBar pb_loading;// 加载条
      private LoadingTask loadingTask;// 异步加载任务
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.acitvity_loading);
        pb_loading = (ProgressBar) findViewById(R.id.pb_loading);
    
        loadingTask = new LoadingTask();
        loadingTask.execute();
      }
    
      @Override
      protected void onPause() {
        super.onPause();
        if (loadingTask != null && loadingTask.getStatus() == AsyncTask.Status.RUNNING) {
          loadingTask.cancel(true);// cancel 方法只是将对应的 AsyncTask 标记为了取消状态,并不是真的取消线程执行了。
        }
      }
    
      class LoadingTask extends AsyncTask<Void, Integer, Void> {
    
        @Override
        protected Void doInBackground(Void... params) {
          try {// 循环模拟加载进度
            for (int i = 0; i <= 100; i++) {
              if (isCancelled()) {
                break;// 如果是取消状态就直接跳出自动结束线程
              }
              LogUtil.e("progress = " + i);
              publishProgress(i);// 去更新界面
              Thread.sleep(300);
            }
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          return null;
        }
    
        @Override
        protected void onProgressUpdate(Integer... progress) {
          if (isCancelled()) {
            return;// 如果是取消状态就直接返回
          }
          pb_loading.setProgress(progress[0]);
        }
      }
    }
    

    注意:cancel 方法只是将对应的 AsyncTask 标记为了取消状态,并不是真的取消线程执行了。

    到这里,便对AsyncTask有了一个基础的入门。以上的学习主要参考慕课网视频Android必学-AsyncTask基础

    07. 串并行

    网上有很多关于AsyncTask的串并行问题介绍,起初AsyncTask串行的,一个一个的执行;接着修改成了并行,多线程并发执行;接着又修改成了支持串行和并行,我们直接调用execute(Params... params)是进行串行,调用executeOnExecutor(Executor exec, Params... params)是并传递类型,来选择进行并行还是串行。其实点开execute源码

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

    从中可以看到是同一方法。参数有:AsyncTask.SERIAL_EXECUTORAsyncTask.THREAD_POOL_EXECUTOR;第一个是默认的,串行操作;第二个是进行并行操作。

    08. 强调

    • 异步任务的实例必须在 UI 线程中创建
    • execute(Params... params)方法必须在 UI 线程中调用
    • 不要手动回调onPreExecute(),doInBackground(Params... params),onPostExecute(Result result),onProgressUpdate(Progress... values)这四个方法
    • 不能在doInBackground(Params... params)方法中更新 UI
    • 一个任务实例只能执行一次,第二次执行就会抛出异常java.lang.IllegalStateException: Cannot execute task: the task is already running

    09. 小综合

    参考 CSDN 上的文章详解Android中AsyncTask的使用自己动手写了一个完整的 Demo

    • 在执行AsyncTask.cancel(true);的时候先回调onCancelled()然后再回调onCancelled(Result result)点击看下源码onCancelled(Result result) 的内部实现是直接调用onCancelled()
    • 在执行AsyncTask.cancel(true);后,onPostExecute(Result result)方法是没有进行回调的
    • AsyncTask 的状态被存放在一个枚举Status中,总共有三种状态FINISHED,PENDING,RUNNING
    • 点击查看一下execute(Params... params)方法就会明白为啥只能执行一次
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,  Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    //如果该任务正在被执行则抛出异常
                    //值得一提的是,在调用cancel取消任务后,状态仍未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)");
            }
        }
    
        //改变状态为RUNNING
        mStatus = Status.RUNNING;
    
        //调用onPreExecute方法
        onPreExecute();
    
        mWorker.mParams = params;
        sExecutor.execute(mFuture);
    
        return this;
    }
    

    更多源码解析可以到文章详解Android中AsyncTask的使用中查看

    文章推荐

    相关文章

      网友评论

          本文标题:Android 异步任务AsyncTask学习

          本文链接:https://www.haomeiwen.com/subject/wdumpttx.html