架构之异步任务

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双端队列,高效管理任务

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容