AsyncTask的使用及源码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013928412/article/details/80807503
一、AsyncTask概述
Android中的线程或者线程池是个很重要的概念,在Android中分为主线程和子线程,主线程是应用开启之后默认的线程,也是UI线程,主要用来处理与界面相关的事情。除了主线程之外的线程都是子线程。比如一些网络请求、IO操作等一些耗时操作都需要放在子线程执行,否则就会导致UI界面卡顿,掉帧等问题。Android中对于子线程多线程的处理有一些比较好的封装,比如AsyncTask,IntentService,HandlerThread等,今天主要介绍一下AsyncTask利弊以及源码分析。
AsyncTask能够简单方便的使用UI线程。这个类可以在不操作线程或者Handler的情况下,处理后台任务,然后将结果更新在UI线程。
AsyncTask是一个操作Thread和Handler的辅助类。AsyncTask只能被用来执行简单不太耗时的操作(比如一次执行也就几秒钟)这种情况,如果想要执行特别好使的操作,那么建议使用java.util.concurrent包里面的类,比如Executor,ThreadPoolExecutor和FutureTask.
异步任务指的是在子线程执行耗时的操作,然后将最终的结果刷新在UI线程上。AsyncTask执行需要传递三种类型,分别是Params,Progresss和Result,四步,分别是:onPreExecute,doInBackground,onProgressUpdate和onPostExecute。
二、AsyncTask使用
1、AsyncTask的简单使用
AsyncTask是个抽象类,至少需要复写doInBackground(Params...)这个方法,而且最好也将onPostExecute(Result)这个方法复写了。
举个栗子:
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);
2、AsyncTask的泛型
需要明确一下三种类型
1、Params,执行时,需要传递给任务的参数的类型
2、Progress,在后台任务执行期间,不断刷新的参数的类型
3、后台任务执行之后返回结果的类型
并不是所有的异步任务都需要传递这三种类型,如果不需要类型,可以直接使用Void:
private class MyTask extends AsyncTask<Void, Void, Void> { ... }
3、常用的四个方法
当一个异步任务被执行的时候,以下四个方法将会被调用:
1、onPreExecute(),在任务执行之前会在主线程调用此方法。这个方法通常被用来做一些初始化操作,比如在任务执行之前显示一个进度条
2、doInBackground(Params...),在onPreExecute()执行之后会在子线程立即被执行。这个步骤用于执行耗时的后台计算。异步任务的参数将会被传递到此步骤。计算结果必须在这个方法中返回,并且会将结果传递到最后一步。此步骤还可以使用publishProgress(Progress..)更新一个或多个进度单元。这些值将会被在onProgressUpdate(Progress...)步骤中更新到UI线程。
3、onProgressUpdate(Progress...),在publishProgress(Progress...)之后,在UI线程被调用。执行的时机是不明确的。这个方法被用来在后台计算仍在执行时,在UI界面上显示任何形式的进度。例如,它可以用来为进度条设置动画效果或者在文本中显示日志。
4、onPostExecute(Result),后台任务执行完成之后,会在UI线程调用此方法。后台执行计算的结果会被作为一个参数传递给此方法。
4、取消一个任务
一个任务可以通过调用cancle(boolean)被取消掉。调用此方法会导致后续的调用isCancelled()方法返回true。调用此方法后,onCancelled(Object)将在doInBackground(Object[])返回后调用,而不是onPostExecute(Object)。为了确保尽快取消任务,尽量始终在doInBackground(Object[])中定期检查isCancelled()的返回值。
5、使用AsyncTask的注意事项
● AsyncTask的对象必须在UI线程创建
● execute(Params...)必须在UI线程调用
不要手动调用onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法
不能在doInBackground(Params... params)中更改UI组件的信息。
一个任务实例只能执行一次,如果执行第二次将会抛出异常。
三、AsyncTask源码分析
先看构造方法:
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);
            }
        }
    };
}
从代码中可以看出,实际上,构造方法实际上就初始化了两个变量,mWorker和mFuture,并且将mWorker作为参数传递给mFuture的构造方法中。mWorker是一个Callale对象,mFuture是一个FutureTask对象。FutureTask实现了Runnable接口
可以看到mWorker的call方法里面执行的是耗时操作,并将执行结果通过postResult(result)传递给内部的Handler,但是目前为止还没有真正的执行。
下面来看执行的部分:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}
调用了executeOnExecutor()方法,我们来看具体的逻辑:
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;
}
从中可以看出,先执行了onPreExecute()方法,然后执行了exec.execute(mFuture),将在构造方法中初始化的mFuture传递进去了,那么exec是什么呢?从AsyncTask的execute方法可以知道,exec是sDefaultExecutor,是个SerialExecutor,具体代码如下:
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);
        }
    }
}
从这段代码可以看出,最终调用了SerialExecutor类的execute方法,其内部维持了一个队列,并且在添加的时候使用了锁机制,可以保证AsyncTask是串行执行的,新加入的任务添加对队列中,然后不断使用THREAD_POOOL_EXECUTOR执行,执行完成之后继续执行队列中的下一个任务。并且,由于 sDefaultExecutor是个static的静态属性,导致一个应用内的所有的异步任务都将会放入到这一个队列中,一个一个执行。
再来跟踪 THREAD_POOOL_EXECUTOR,
public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
可以看出其实就是一个线程池,用来执行任务。
我们再回顾一下,先执行完构造方法中mWorker中call()方法中的内容,执行完doInBackground(),又执行postResult():
private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}
将执行结果通过getHandler()发送出去:
private static class InternalHandler extends Handler {
    public InternalHandler() {
        super(Looper.getMainLooper());
    }

    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    @Override
    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;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}
在handler中,当收到MESSAGE_POST_RESULT之后,就开始调用finish方法
private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}
如果设置了cancel(true),就会回调onCancelled,否则就会回调onPostExecute()方法。
四、总结
1、AsyncTask在Android3.0之后默认的execute()是串行执行的,也就是即使开启了多个一步任务,也会一个一个执行
2、一个AsyncTask实例只能被执行一次,从executeOnExecutor就可以看出,会判断当前的任务执行状态,如果当前实例正在运行或者已经完成,就会抛出异常。
3、AsyncTask的cancle(true)并不会直接将任务终止,而仅仅是改变了一个标记,我们需要在doInBackground中通过isCanceled()来判断当前是否被设置为取消
4、由于onPostExecute可以在主线程中进行操作,以及InternalHandler需要将执行环境切换到主线程,因此需要AsyncTask必须在主线程中进行加载。
5、如果使用了executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)这种方式来设置并行执行任务,可能会抛出异常,原因是由于AsyncTask默认的线程池的workQueue是128个,并且:
如果当前线程池中的数量小于corePoolSize,创建并添加的任务。
如果当前线程池中的数量等于corePoolSize,缓冲队列 workQueue未满,那么任务被放入缓冲队列、等待任务调度执行。
如果当前线程池中的数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量小于maximumPoolSize,新提交任务会创建新线程执行任务。
如果当前线程池中的数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量等于maximumPoolSize,新提交任务由Handler处理。
当线程池中的线程大于corePoolSize时,多余线程空闲时间超过keepAliveTime时,会关闭这部分线程。
而handler默认的处理方式是抛出异常,解决办法是使用自定义的线程池。
6、由于 sDefaultExecutor是个静态属性,默认情况下,会导致一个应用内的所有的异步任务都会串行执行。如果执行特别耗时的操作,很容易导致内存泄漏等问题

猜你喜欢

转载自blog.csdn.net/u013928412/article/details/80807503