代码改变世界

AsyncTask源码解析

2017-07-15 11:40  ttylinux  阅读(193)  评论(0编辑  收藏  举报
本文分为以下几部分:
1.AsyncTask的使用介绍
2.AsyncTask的实现逻辑
3.其它牵涉到的概念
 
1.AsyncTask的使用
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    @Override
    protected void onPreExecute() {
        progressDialog.show();
    }
    @Override
    protected Boolean doInBackground(Void... params) {
        try {
            while (true) {
                int downloadPercent = doDownload();
                publishProgress(downloadPercent);
                if (downloadPercent >= 100) {
                    break;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }
    @Override
    protected void onProgressUpdate(Integer... values) {
        progressDialog.setMessage("当前下载进度:" + values[0] + "%");
    }
    @Override
    protected void onPostExecute(Boolean result) {
        progressDialog.dismiss();
        if (result) {
            Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
        }
    }
}
View Code
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    ……
}
View Code

这里我们把AsyncTask的第一个泛型参数指定为Void(doInBackground(Params...)),表示在执行AsyncTask的时候不需要传入参数给后台任务。第二个泛型参数指定为Integer,表示使用整型数据来作为进度显示单位(onProgressUpdate(Progress...))。第三个泛型参数指定为Boolean(onPostExecute(Result)),则表示使用布尔型数据来反馈执行结果。

 

1. onPreExecute()
这个方法会在后台任务开始执行之间调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
2. doInBackground(Params...)---只有在这个方法中执行耗时操作
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。
3. onProgressUpdate(Progress...)
当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
4. onPostExecute(Result)
当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
 
执行:执行如下代码,就开启一个下载任务(实际可能只是放到队列中,没有开始执行)
new DownloadTask().execute();
View Code
需要传递参数给后台任务,那么如何定义?
这样定义:
class DownloadTask extends AsyncTask<String, Integer, Boolean>{

@Override
    protected Boolean doInBackground(String... params) {
    ....
// String url = params[0]; ,这个就是客户端传入的参数
// String url2 = params[1]; 客户端传入的参数
   }
}
//使用
DownloadTask  task = new DownloadTask();
task.execute("url","url2");
View Code

 

2.AsyncTask的实现逻辑

1).AsyncTask是一个抽象类,子类继承抽象类,要实现抽象类的抽象方法
2).要实现的抽象方法:
抽象类AsyncTask只有一个抽象方法doInBackground,子类是必须要实现的
    /**
     * Override this method to perform a computation on a background thread. The
     * specified parameters are the parameters passed to {@link #execute}
     * by the caller of this task.
     *
     * This method can call {@link #publishProgress} to publish updates
     * on the UI thread.
     *
     * @param params The parameters of the task.
     *
     * @return A result, defined by the subclass of this task.
     *
     * @see #onPreExecute()
     * @see #onPostExecute
     * @see #publishProgress
     */
    @WorkerThread
    protected abstract Result doInBackground(Params... params);
View Code
3).创建子类的一个实例,然后执行execute方法,这样就开启一个AsyncTask任务。
那么,这个时候,要查看创建一个实例,然后执行execute方法,都做了什么。
实例化AsyncTask时做的事情:
  /**
     * 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
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };

        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);
                }
            }
        };
    }

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

调用execute方法做的事情:

  /**
     * Executes the task with the specified parameters. The task returns
     * itself (this) so that the caller can keep a reference to it.
     *
     * <p>Note: this function schedules the task on a queue for a single background
     * thread or pool of threads depending on the platform version.  When first
     * introduced, AsyncTasks were executed serially on a single background thread.
     * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
     * to a pool of threads allowing multiple tasks to operate in parallel. Starting
     * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being
     * executed on a single thread to avoid common application errors caused
     * by parallel execution.  If you truly want parallel execution, you can use
     * the {@link #executeOnExecutor} version of this method
     * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
     * on its use.
     *
     * <p>This method must be invoked on the UI thread.
     *
     * @param params The parameters of the task.
     *
     * @return This instance of AsyncTask.
     *
     * @throws IllegalStateException If {@link #getStatus()} returns either
     *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
     *
     * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
     * @see #execute(Runnable)
     */
    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

    /**
     * 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);
View Code
似乎,阅读完注释,就知道它的执行原理了。
 
执行execute,就是创建一个任务,把这个任务加入到一个队列中,然后等待线程(可以是单一的线程,也可以是从线程池中取出来的线程)来执行。因为,所有的实例都是使用同一个THREAD_POOL_EXECUTOR(是一个静态final变量,当某个类首次加载的时候,就会初始化,之后就是供所有的实例使用)。那么,每当我们new DownloadTask().execute("url"),创建一个任务,然后将任务推给队列,这个执行的顺序是怎样的呢?
在早期的系统版本(低于android.os.Build.VEERSION_CODES#DONUT的版本),是采用串行的方式执行,某一个时刻只有一个任务在执行;从系统版本android.os.Build.VERSION_CODES#DONUT开始,修改为并行的方式执行,多个任务,分配给多个线程,并行执行;到了版本android.os.Build.VERSION_CODES#HONEYCOMB,它又修改为串行的方式。真是折腾。
并行执行的问题,看应用场景,如果某些任务是要按顺序一个一个执行的,对执行顺序有要求的,那么这些任务就不能采用并行的方式执行,而要采用串行的方式执行。因为并行执行,它不能确保所有任务的执行顺序。
 
现在默认是串行的方式,如果需要该任务是并行执行的,那么,使用如下方式:
DownloadTask task = new DownloadTask();
task.executeOnExecutor(THREAD_POOL_EXECUTOR,"url");
View Code

 

4)executeOnExecutor(Executor exec,Params... params)执行细节

  private final FutureTask<Result> mFuture;

    /**
     * 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);

   /**
     * An {@link Executor} that executes tasks one at a time in serial
     * order.  This serialization is global to a particular process.
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;  

    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);
            }
        }
    }

   /**
     * Executes the task with the specified parameters. The task returns
     * itself (this) so that the caller can keep a reference to it.
     *
     * <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to
     * allow multiple tasks to run in parallel on a pool of threads managed by
     * AsyncTask, however you can also use your own {@link Executor} for custom
     * behavior.
     *
     * <p><em>Warning:</em> Allowing multiple tasks to run in parallel from
     * a thread pool is generally <em>not</em> what one wants, because the order
     * of their operation is not defined.  For example, if these tasks are used
     * to modify any state in common (such as writing a file due to a button click),
     * there are no guarantees on the order of the modifications.
     * Without careful work it is possible in rare cases for the newer version
     * of the data to be over-written by an older one, leading to obscure data
     * loss and stability issues.  Such changes are best
     * executed in serial; to guarantee such work is serialized regardless of
     * platform version you can use this function with {@link #SERIAL_EXECUTOR}.
     *
     * <p>This method must be invoked on the UI thread.
     *
     * @param exec The executor to use.  {@link #THREAD_POOL_EXECUTOR} is available as a
     *              convenient process-wide thread pool for tasks that are loosely coupled.
     * @param params The parameters of the task.
     *
     * @return This instance of AsyncTask.
     *
     * @throws IllegalStateException If {@link #getStatus()} returns either
     *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
     *
     * @see #execute(Object[])
     */
    @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();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }
View Code
a.SerialExecutor的实现逻辑:
1).mTasks.offer(Runnable),创建一个新的Runnable,添加到队列尾部。这个新的Runnable就是交给THREAD_POOL_EXECUTOR执行的任务。新的Runnable包含了从客户端提交的任务。
新的Runnable的逻辑是:先执行客户端提交的任务(也就是mFuture);然后,执行scheduleNext方法,scheduleNext做的事情,从队列的头部取出创建的Runnable,然后交给THREAD_POOL_EXECUTOR执行。
通过这种方式,就可以保证所有的任务是按照串行的方式来执行的。新添加的mFuture,使用新创建的一个Runnable,然后,将这个Runnable添加到队列尾部;然后,每次执行的时候,是从队列的的头部取添加的Runnable。
2).触发执行的入口是:
   if (mActive == null) {
         scheduleNext();
 }
3).scheduleNext方法,是同步方法,确保每次只有一个线程可以执行该方法。THREAD_POOL_EXECUTOR有多个线程。
  protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        
View Code

通过SerialExecutor,就可以实现串行的执行。

 

实现逻辑总结:
1.执行从在executeOnExecutor开始,在该方法中调用 onPreExecute(),这是在UI线程执行的。
2.当THREAD_POOL_EXECUTOR有空闲线程时,就会最终执行mFuture任务,而mFuture包含mWorker,它会执行:Result result = doInBackground(mParams);----mFuture是在第三方线程中运行的
3.将执行的结果发送给UI线程:
 在mWorker中会调用postResult方法;postResult方法,通过UI线程的handler发送消息。这里的关键是InternalHandler的创建,它使用的是Ui线程的Looper,这样消息才会给Ui线程的消息循环处理。
  private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

    private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }

    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;
            }
        }
    }
View Code
4.执行过程中的进度更新:
因为该方法是在第三方线程中运行的,所以,同样是使用Ui线程的Handler来发送消息
/**
     * 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 not be called if the task has been
     * canceled.
     *
     * @param values The progress values to update the UI with.
     *
     * @see #onProgressUpdate
     * @see #doInBackground
     */
    @WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }
View Code

 

 

3.其它牵涉到的概念介绍
 
1.FutureTask
2.Callable
 
 
 
引用:
部分示例代码引用自网络