架构之异步任务

作者: shone | 来源:发表于2016-09-09 00:38 被阅读208次

    AsyncTask是啥?
    AsyncTask是一个围绕Handler和Thread而设计的辅助类,封装了在工作线程中与UI交互的细节,只需要对应重写几个回调方法即可,并使得代码更加简洁,优雅。但要注意的是AsyncTask并不构成一个通用的线程框架 ,这在Android官方介绍中有提到:

    AsyncTask is designed to be a helper class around {@link Thread} and 
    {@link Handler}* and does not constitute a generic threading framework.
    

    AsyncTask的适用于短耗时操作,如果是长时间多线程执行,推荐使用java.util.concurrent包下的API,如Executor,ThreadPoolExecutor,FutureTask...

    特性:

    1.任务运行在后台线程,结果返回到UI线程
    2.三个通用参数:Params,Progress,Result对应参数,进度和结果集
    3.操作仅4步曲

    onPreExecute
    doInBackground
    onProgressUpdate
    onPostExecute

    官方示例

      private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
          protected Long doInBackground(URL... urls) {
              int count = urls.length;
              long totalSize = 0;
              for (int i = 0; i < count; i++) {
                  totalSize += Downloader.downloadFile(urls[i]);
                  publishProgress((int) ((i / (float) count) * 100));
                  // Escape early if cancel() is called
                  if (isCancelled()) break;
              }
              return totalSize;
          }
     
          protected void onProgressUpdate(Integer... progress) {
              setProgressPercent(progress[0]);
          }
     
          protected void onPostExecute(Long result) {
              showDialog("Downloaded " + result + " bytes");
          }
      }
    
    new DownloadFilesTask().execute(url1, url2, url3);
    

    历史

    September 2009: Android 1.6.之前,AsyncTask被执行在单线程上!
    Android 1.6.以后:AsyncTask改变成,设计为线程池方式,允许多任务并发执行!
    February 2011: Android 3.0.以后,AsyncTask的多任务只在单线程上被执行,为了避免不同版本因为并发执行带来的异常问题,但是仍然可以配置成多线程执行,只需调用executeOnExecutor(java.util.concurrent.Executor,Object[])方法,传入THREAD_POOL_EXECUTOR或者自定义Executor!

    分析

    1.最重要的2个成员

    private final WorkerRunnable<Params, Result> mWorker;//核心工作人员
    private final FutureTask<Result> mFuture;//对mWorker的一层包装,可以统计工作完成情况
    

    2.构造方法,实例化工作人员

    public AsyncTask() {
            mWorker = new WorkerRunnable<Params, Result>() {
                public Result call() throws Exception {
                    mTaskInvoked.set(true);
    
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    Result result = doInBackground(mParams);//后台任务开始执行
                    Binder.flushPendingCommands();
                    return postResult(result);//把结果交给Handler发出去,UI更新
                }
            };
    
            mFuture = new FutureTask<Result>(mWorker) {
                @Override
                protected void done() {//工作人员把工作做完后
                    try {
                        postResultIfNotInvoked(get());//检测工作人员是否正常完成工作
                    } catch (InterruptedException e) {
                        android.util.Log.w(LOG_TAG, e);
                    } catch (ExecutionException e) {
                        throw new RuntimeException("An error occurred while executing doInBackground()",
                                e.getCause());
                    } catch (CancellationException e) {
                        postResultIfNotInvoked(null);
                    }
                }
            };
        }
    
    

    3.单线程模型(默认)

    private static class SerialExecutor implements Executor {
            final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
            Runnable mActive;
    
            public synchronized void execute(final Runnable r) {
                mTasks.offer(new Runnable() {
                    public void run() {
                        try {
                            r.run();
                        } finally {
                            scheduleNext();
                        }
                    }
                });
                if (mActive == null) {
                    scheduleNext();
                }
            }
    
            protected synchronized void scheduleNext() {
                if ((mActive = mTasks.poll()) != null) {
                    THREAD_POOL_EXECUTOR.execute(mActive);
                }
            }
        }
    

    默认的指挥官是SERIAL_EXECUTOR,字面意思是顺序执行器,内部维护着一个双端队列ArrayDeque:

    ArrayDeque缺点是线程非安全,优点是效率高,用作堆栈时快于Stack,用作队列时快于LinkedList。

    每接收一个任务,都会先把任务扔到队列里,然后检测当前是否有正在被执行的任务,没有的话,就从队列里取出head,交给另一个并发执行器去执行,如果当前有任务正在执行的话,就不做调度,默默的等任务执行完,如果执行完了,再调度下一个将要被执行的任务对象!

    3.多线程模型(可选)

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE = 1;
    private static final BlockingQueue<Runnable> sPoolWorkQueue =
                new LinkedBlockingQueue<Runnable>(128);
    
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);
    
            public Thread newThread(Runnable r) {
                return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
            }
    };
    
    /**
      * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR
                = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                        TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
    

    唯一的线程池指挥官ThreadPoolExecutor,权利大大的,拥有一个128容量的阻塞任务队列,集团化作战随时待命,空闲线程1s的存活时间,机动性能不是盖的:

    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory)
    

    必杀技:

    1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
    2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
    3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
    4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
    5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
    6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
    

    4.最平凡的工作人员:Callable
    平常用的较多的创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口。

    这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。
    如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
    "造物主"为了解决这个问题,就提供了Callable和Future这对搭档,通过它们可以在任务执行完毕之后得到任务执行结果。

    private static abstract class WorkerRunnable<Params, Result> 
                           implements Callable<Result> {    
             Params[] mParams;
    }
    

    在本异步任务中当然不能缺席Callable,这里是用内部类去实现了,并且自带加特技Params,主要是方便把参数传递给doInBackground(mParams)~

    4.最可敬的工作人员:FutureTask
    FutureTask是RunnableFuture接口的实现类,RunnableFuture既包含Runnable特性,又兼具Future特点
    上面说了Callable要配合Future使用,达到效果,而线程池执行者需要执行Runnable对象,所以用FutureTask最好不过了!

    Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

    在异步任务构造器里分别完成了WorkerRunnable和FutureTask的实例化!

    5.开始作战
    第1步:下达命令

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

    MainThread注解表示该方法只能在主线程调用~
    第2步:装填弹药

    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
                Params... params) {
            if (mStatus != Status.PENDING) {
                switch (mStatus) {
                    case 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)");
                }
            }
    
            mStatus = Status.RUNNING;
    
            onPreExecute();//UI前戏,预处理
    
            mWorker.mParams = params;//绑定参数,传参
            exec.execute(mFuture);//执行器执行任务
    
            return this;
    }
    

    第3步:开火

    public Result call() throws Exception {
          mTaskInvoked.set(true);
          Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//线程优先级,后台线程
           //noinspection unchecked
           Result result = doInBackground(mParams);//回调到doInBackground,后台执行
           Binder.flushPendingCommands();
           return postResult(result);
    }
    

    第4步:打扫战场

    protected void done() {
         try {
              postResultIfNotInvoked(get());//检测任务完成情况
         } catch (InterruptedException e) {
              android.util.Log.w(LOG_TAG, e);
         } catch (ExecutionException e) {
               throw new RuntimeException("An error occurred while executing doInBackground()",
                                e.getCause());
         } catch (CancellationException e) {
               postResultIfNotInvoked(null);
         }
    }
    

    第5步:向总部报告作战成果

    private Result postResult(Result result) {
            @SuppressWarnings("unchecked")
            Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                    new AsyncTaskResult<Result>(this, result));
            message.sendToTarget();//发消息
            return result;
    }
    
    public void handleMessage(Message msg) {
           AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
           switch (msg.what) {
                case MESSAGE_POST_RESULT://报告结果-result
                     // There is only one result
                     result.mTask.finish(result.mData[0]);
                     break;
                case MESSAGE_POST_PROGRESS://报告进度
                     result.mTask.onProgressUpdate(result.mData);
                     break;
           }
     }
    
    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);//回调UI,任务取消了 
        } else {
            onPostExecute(result);//回调UI,任务执行完毕,结果是result
        }
        mStatus = Status.FINISHED;//更新状态
    }
    

    到此,异步作战就算完毕了~
    此次作战学到了:
    1.单兵作战神器SerialExecutor
    2.群战(并发)神器,ThreadPoolExecutor
    3.Callable与Future协同作战,威力大
    4.群战(并发)法宝AtomicBoolean,原子操作
    5.离不开的通信使者Handler
    6.ArrayDeque双端队列,高效管理任务

    相关文章

      网友评论

        本文标题:架构之异步任务

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