Android AsynTask 分析

来源:互联网 发布:微信一键转发软件下载 编辑:程序博客网 时间:2024/05/22 06:08

      本次我们将来介绍一下Android的异步任务类AsynTask,相信大家平时在开发的时候经常使用。一般我们在做一些比较耗时的操作任务都会开启一个线程去执行任务,因为这样子不会阻塞UI线程,用户操作起来界面的时候才不会卡顿的,大大的增加了用户体验的,但是懒惰的工程师经常嫌弃开启一个线程在更新UI界面的时候还需要再使用Handler。这样子就可能会使得代码不美观。

使用说明

具体的使用我这里就不详细的写demo来说明,因为android都这么久了,大家都用的这么久的时间了,网上一大片的使用说明,下面我写一段伪代码来稍微的说明一下用法,最关键的还是看它的内部实现以及为啥不需要使用Handler去更新UI的。

public class AsynTaskActivity extends Activity {    private DownloadApkTask mDownloadTask;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        this.setContentView(R.layout.activity_web_view);        mDownloadTask = new DownloadApkTask();        mDownloadTask.execute("http://www.yy23.com/path/file.apk");    }    private class DownloadApkTask extends AsyncTask<String, Integer, String> {        @Override        protected void onPreExecute() {            //开始准备执行下载操作的,运行在UI线程中        }        @Override        protected String doInBackground(String... params) {            /**             * 我们在这里执行下载apk的代码(该方法运行在子线程中,是直接做耗时的操作的,             * 所以不能在这里更新UI),如果在下载的过程中我们需要不断的去更新进度的话,             * 我们需要在该方法中调用 publishProgress(progress)来将进度发送到 onProgressUpdate()中             */            return "success";        }        @Override        protected void onProgressUpdate(Integer... values) {            //我们可以在这里去更新进度条的进度(运行在UI线程中)        }        @Override        protected void onPostExecute(String s) {            /**             * 这里表示我们的任务执行结束,运行在UI线程中,这里参数s是 doInBackground()的返回值             * 这个时候我们在 doInBackground来根据有没有抛出异常或者是下载成功,然后返回不同的值             * 然后在判断该方法的参数,再做最后的业务逻辑判断             */        }    }}

      上面就是我们在使用AsynTask的时候的一个模版,AsynTask是一个抽象类我们一般都是通过去继承这个类的,在继承该类的时候有三个泛型参数需要我们自己去实现的。AsyncTask

在使用的时候,有几点需要格外注意:

  • 异步任务的实例必须在UI线程中创建。
  • execute(Params… params)方法必须在UI线程中调用。
  • 不要手动调用onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)这几个方法。
  • 不能在doInBackground(Params… params)中更改UI组件的信息。
  • 一个任务实例只能执行一次,如果执行第二次将会抛出异常。

实现原理

AsynTask执行任务的核心就在于线程池,所以在创建AsynTask对象之前首先会有静态的成员变量用于记录创建的线程池信息。我们都知道在创建线程池的具体参数:

  • corePoolSize
    线程池中的核心线程数,当提交一个任务是,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

  • maximumPoolSize
    线程池中允许的最大执行任务的线程数。如果提交的任务操作最大任务数,切继续提交任务则新提交的任务会保存在阻塞队列中的。也就是我们下面说的workQueue队列中。

  • keepAliveTime
    线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用;

  • unit
    keepAliveTime的单位,一般单位我可以直接使用 TimeUnit.SECONDS(秒), TimeUnit.MINUTES(分钟)等等

  • workQueue
    用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列:
    1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务
    2、LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene
    3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene
    4、priorityBlockingQuene:具有优先级的无界阻塞队列
  • threadFactory
    创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。
    image_1bgsu94kp14i51gr5gh1fofubh9.png-17.9kB

当我们解释了线程池的一些参数之后下面我们就来看看AsynTask参数的线程池的一些参数配置

    ........    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 ThreadFactory sThreadFactory = new ThreadFactory() {        private final AtomicInteger mCount = new AtomicInteger(1);        public Thread newThread(Runnable r) {            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());        }    };    private static final BlockingQueue<Runnable> sPoolWorkQueue =            new LinkedBlockingQueue<Runnable>(128);    public static final Executor THREAD_POOL_EXECUTOR            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);    .......

从上面的代码中我们可以看出CORE_POOL_SIZE数目是:虚拟机可用的最大处理器数量(这里并不是CPU的最大个数,这个是表面的字段意思);MAXIMUM_POOL_SIZE是CORE_POOL_SIZE*2+1的,如果当线程池中的任务数目操作了maximum_pool_size的时候,则会保存到workqueue中该缓存队列最多可以缓存128个任务。

/**     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.     */    public AsyncTask() {        mWorker = new WorkerRunnable<Params, Result>() {            public Result call() throws Exception {                mTaskInvoked.set(true);                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);                //noinspection unchecked                return postResult(doInBackground(mParams));            }        };        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 occured while executing doInBackground()",                            e.getCause());                } catch (CancellationException e) {                    postResultIfNotInvoked(null);                }            }        };    }    private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {        Params[] mParams;    }

    通过在构造方法中我们看到创建了一个FutureTask以及创建一个Callable对象,这里我们就需要看看我们之前的关于 FutureTask、Callable和Future的关联和使用,因为你只有把这几个东西看懂了之后,你才能看懂这个里面的封装代码。我们这里只是稍微做一个简短的介绍:

Future(type:interface): 里面定义了很多方法用来判断异步任务的执行状态以及获取执行的结果
Callable (type:interface): 里面仅仅定义了一个 call方法直接运行在FutureTask的runn方法,其子类主要是通过参数传入到FutureTask构造方法中。
FutureTask:实现了Runnable接口和 Future的类,然后在构造方法来获取Callable接口的子对象,所以该类可以在线程池中执行,也可以通过Thread类来执行,也是一个线程类。

    通过我们对Callable、Future以及FutureTaskde的理解,我们知道线程池执行FutureTask任务其实也是直接运行FutureTask的run()方法的,而Callable类的call()方法其实也是直接在run方法中运行的,只不过call()方法会有返回值,所以我们也可以在call()方法写我们需要直接的耗时操作,然后将结果返回出去。当任务执行完成的时候就会调用done()方法的,这个时候的done方法其实也还是在run()方法里面的也就是说同样还在子线程中的。所以我们看到的 doInBackground()函数是直接运行在子线程里面的,然后通过call()方法将doInBackground方法执行完成的结果返回出去,

mWorker = new WorkerRunnable<Params, Result>() {        public Result call() throws Exception {            mTaskInvoked.set(true);            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);            //noinspection unchecked            return postResult(doInBackground(mParams));        }};

通过这个简短的代码中我们可以看的出来doInBackground()是直接运行在子线程中,然后将该函数的返回值当作参数传递到postResult()方法中

@SuppressWarnings("unchecked")private Result postResult(Result result) {    Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,            new AsyncTaskResult<Result>(this, result));    message.sendToTarget();    return result;}

从代码中我们可以看出postResult()其实也没有做什么操作,也就只是发送了一个Handler消息出去了,消息类型为MESSAGE_POST_RESULT的,同时封装了一个AsyncTaskResult将当前对象和结果封装起来然后统一发送到Handler中的。

private static class InternalHandler extends Handler {    public void handleMessage(Message msg) {        AsyncTaskResult result = (AsyncTaskResult) msg.obj;        switch (msg.what) {            case MESSAGE_POST_RESULT:                // There is only one result                result.mTask.finish(result.mData[0]);                break;                .......        }    }}private void finish(Result result) {    if (isCancelled()) {        onCancelled(result);    } else {        onPostExecute(result);    }    mStatus = Status.FINISHED;}

      从这几段的代码中可以非常的容易的就看到最后postResult()发送了一个Message消息之后,最后调用了finish()方法,最后判断如果我们没有主动调用cancel方法的话,则会调用onPostExecute(),该方法就是我们之前demo里面的用来处理最后结果状态的函数,从代码中我们可以看到onPostExecute就是直接运行在主线程里面的,原因是在postResult()的时候,使用了handler将doInBackground方法的结果一致发送到主线程里面,然后我们也可以重写onPostExecute方法来处理结束的业务逻辑,如果我们主动的将任务取消之后则会调用onCancelled函数,最后我们只要复写该函数就可以来处理相应的业务逻辑了。
      在上面的时候我们还有一个重要的功能就是更新界面进度条的时候还没有分析:其实我们在doInBackground中使用publishProgress方法来发送消息到UI线程的onProgressUpdate方法的,其实我们啥也不用去看,只要看源代码以及源代码里面的代码注视就行了,因为api文档的生成就是通过代码的注视来生成的,所以看代码和代码的注视是我们学习的一个重要的关键。

/**     * This method can be invoked from {@link #doInBackground} to     * publish updates on the UI thread while the background computation is     * still running. Each call to this method will trigger the execution of     * {@link #onProgressUpdate} on the UI thread.     * {@link #onProgressUpdate} will note be called if the task has been     * canceled.     * @param values The progress values to update the UI with.     * @see #onProgressUpdate     * @see #doInBackground     */    protected final void publishProgress(Progress... values) {        if (!isCancelled()) {            sHandler.obtainMessage(MESSAGE_POST_PROGRESS,                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();        }    }

     在代码中,我们发现publishProgress方法中也是将当前进度值和对象封装成一个AsyncTaskResult对象然后发送了一个MESSAGE_POST_PROGRESS的message到InternalHandler中的,在InnerHandler处理消息的时候最后调用的是onProgressUpdate(progress),该函数也是我们在AsynTask复写的方法所以我们也知道了publishProgress和onProgressUpdate联系了吧。

private static class InternalHandler extends Handler {    public void handleMessage(Message msg) {        AsyncTaskResult result = (AsyncTaskResult) msg.obj;        switch (msg.what) {            .......            case MESSAGE_POST_PROGRESS:                result.mTask.onProgressUpdate(result.mData);                break;        }    }}/** * Runs on the UI thread after {@link #publishProgress} is invoked. * The specified values are the values passed to {@link #publishProgress}. * * @param values The values indicating progress. * * @see #publishProgress * @see #doInBackground */protected void onProgressUpdate(Progress... values) {}

      当创建完AsynTask对象之后我们就可以执行execute(Params …paras)来触发任务的执行,从实际的代码中我们可以看出是直接将我们在构造方法中创建的mFuture任务直接提交到线程池中执行的,同时也看到了之前的回调方法 onPreExecute(),然后任务的状态置为RUNNING状态,如果当前对象的状态已经是运行或者结束状态的话,再运行的话就会抛出异常的,也就是说我们的任务不能execute两次的。很多的人在这里可能会非常的疑惑就是:这里的执行任务非常非常的简单,只是一个mFutureTask提交到线程池中去执行,在执行之前先调用一个方法,也就是我们所说的调用之前的准备工作。其实最最关键的还是我们之前的构造方法中的。

public final AsyncTask<Params, Progress, Result> execute(Params... params) {    return executeOnExecutor(sDefaultExecutor, params);}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();    mWorker.mParams = params;    exec.execute(mFuture);    return this;}

总结

      通过上面我们对AsyncTask的分析我们知道在创建该对象的同时也会创建一个线程任务,然后执行的时候会将该内部的任务提交到线程池中去执行,然后内部维护着一个InnerHandler用于将线程中的状态同步到主线程的方法中,我们只要复写该类的指定方法来做相应的操作就可以了,其实这个跟我们 Thread + Handler模式来处理耗时的操作是一样的,只是人家把Thread 和 Handler 都封装到一个类中,然后暴漏出相应的方法让调用者调用就行了,这样子就可以显得代码非常的简洁和易读的,同时我们也可以充分的利用系统暴漏给我们的线程池去执行我们想要的任务,其实很多的时候我们完全不需要使用去创建什么线程池了,比如说我们还可以使用AsyncTask中的execute(Runnable runnable)直接提交一个我们自定义的异步线程任务类,这样子是充分的利用了系统自带的线程池的。

PS:由于写作水平和对知识的总结水平的有限,所以导致写出来的博客有点记流水账似的,还请大家看的少喷,我也不断的提高自己的,如果大家有什么疑问可以跟我联系;我个人的观点尽所有的可能还原代码的最真实的情况,因为代码才是最最真实的,源代码的阅读比你阅读什么博客都更直接的,博客只是别人的一个总结,但是阅读代码来的体会则是自己来的最真实的。

原创粉丝点击