从源码角度谈谈AsyncTask的使用及其原理

从事Android开发的都知道,我们在进行耗时操作的时候是不能在主线程进行的,不然会报ANR异常,因此我们必须开启一个子线程,在线程中处理耗时操作。但是在子线程中我们拿到了耗时操作返回的数据之后需要在UI上展示,但是在子线程又不能对UI进行更新,于是乎在Android内部就有了消息通信机制Handler以及AsyncTask。今天我们来讲讲AsyncTask的使用以及配合他的源码来讲讲他的内部原理。

介绍

AsyncTask是一个抽象类,内部其实他定义了两个线程池(ThreadPoolExecutor、SerialExecutor)以及一个Handler(InternalHandler)。而我们在使用AsyncTask的时候,一般会往这个类里面传递三个参数,ParamsProgressResult

Params:这个参数代表需要传进来进行加载或者进行耗时操作的参数。一般来说我们可以传入一个网络请求的地址。

Progress:这个参数代表我们需要在进行耗时操作的时候更进进度条的参数返回值。

Result:这个参数代表我们在进行完成耗时操作之后拿到的结果数据。

而在这个类内部拥有四个方法。

onPreExecute():我们在进行初始化数据的时候调用这个方法,这个方法是在主线程执行。

doInBackground():我们在进行耗时操作的时候调用这个方法,所有的耗时操作都在这个里面进行操作。并且将耗时操作的结果返回回去。

onProgressUpdata():对控件的进度进行操作。

onPostExecute():这个方法里面会拿到doInBackground()方法中返回的参数结果,我们在这个方法里面可以对UI进行操作,从而达到更新UI的结果。

整体代码如下所示:

class MyAsyncTask extends AsyncTask<String,Integer,String>{
 
        @Override
        protected void onPreExecute() {
            //数据初始化操作
            super.onPreExecute();
        }
 
        @Override
        protected String doInBackground(String... strings) {
            //耗时操作,并将结果返回
            return null;
        }
 
        @Override
        protected void onProgressUpdate(Integer... values) {
            //对进度条进行更新操作
            super.onProgressUpdate(values);
        }
 
        @Override
        protected void onPostExecute(String s) {
            //UI更新操作
            super.onPostExecute(s);
        }
    }
复制代码

之后我们在主线程中使用execute()方法来开启整个任务,执行任务。

AsyncTask的使用其实非常简单,很多地方其本身就已经帮助我们封装好了,这使得我们使用起来简单方便。但是作为一个有追求的工程师,单单会使用是远远不够的,我们还必须了解其内部的原理,从而达到知其然也知其所以然的目的。下面我们来结合源码来谈谈内部原理。

源码解析

我们在之前说过,其实AsyncTask的内部主要是对三个东西进行了封装处理,两个线程池(ThreadPoolExecutor、SerialExecutor)以及一个Handler(InternalHandler)。那内部是怎么对这些东西进行耗时操作的呐?我们结合我们的时候,一步一步来进行分析。

首先,我们在使用AsyncTask的时候,一般都会在主线程中new出这个对象来,那么我们先来看看他的构造方法里面做了什么处理。

 /**
     * 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);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return 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);
                }
            }
        };
    }
复制代码

我们可以看到这里面代码很长,但是主要就是做了两步操作。第一,实例化了一个WorkerRunnable方法,第二个实例化了FutureTask方法。WorkerRunnable方法其实是一个Runnable方法,在第12行我们看到了我们之前的耗时操作doInBackground()方法,可见我们在进行耗时操作的时候,其实是在这个方法中执行的。执行完成之后将结果通过postResult()方法返回。而FureTask方法其实是实现了Runnable接口,我们将WorkerRunnable这个方法传递进去,具体的使用我们在后面会进行讲解。

在执行AsyncTask的时候,我们会调用execute()。我们看下这个方法内部进行的操作。

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

可以看到这个方法内部其实是调用了executeOnExecutor()这个方法,我们在来看看这个方法内部做了什么操作。

 @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;
    }
复制代码

我们看到这个方法内部其实做了这么几步操作。

首先通过一个switch来进行了两次判断,第一次判断是否任务正在运行,RUNNING。如果正在运行则抛出一个异常。这就代表一个任务只能调用一次execute,调用两次则会报错。第二次判断这个任务是否已经结束了,FINISHED,如果结束了还执行也会抛出异常,这个任务已经执行完成,一个task只能执行一次操作。

之后我们用调用onPreExecute()方法来进行初始化数据操作。

然后我们看到在最后我们执行了一个方法exec.execute(mFutrue)将我们之前的mFuture任务传递进行。

那么exec是什么?其实exec是sDefaultExecutor,再追溯上去发现这个sDefaultExecutor其实是SerialExecutor。那我们在跳进这个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);
            }
        }
    }
复制代码

可以看到,里面逻辑并不复杂。首先是实例化了一个任务队列ArrayDeque,之后用一个synchronized进行一个同步锁操作。这样保证的目的是一次只能往任务队列中添加一个任务,只有等任务队列头部执行完成之后,才能调用scheduleNext去执行下一个任务。这就导致一个什么结果呐?这就说明,其实我们的AsyncTask在执行耗时操作任务的时候,并不是并行操作,而是使用了SerialExecutor来行了维护了一个任务队列,通过同步锁的形式一个一个往里面添加执行的任务,最后拿出来一个一个的操作。其实其内部是个串行操作。

之后我们在来看下ThreadPoolExecutor。

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
复制代码

我们可以看到,其实这里面维护了一个线程池的操作,开启了一定数量的线程,然后调用execute()方法来执行这个线程,即为我们开头说的那个workerRunnable线程中的任务操作。最后将结果交给postResult()方法去处理。

我们再来看看这个postResult()方法内部是怎么进行操作的。

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

我们可以从代码中看到,其实内部有一个Message的消息机制,他通过sendToTarget()方法想Handler发送了一条消息。那么我们再跳转到Handler中去查看下代码。

 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;
            }
        }
    }
复制代码

从源码中我们可以看到,这个handleMessage里面做了两步操作,第一是判断这个任务是否后已经结束了MESSAGE_POST_RESULT,如果是返回了这个则代表任务已经结束了,调用finish()方法来结束掉。第二个是判断MESSAGE_POST_PROGRESS,代表更新进度条的操作,等于说我们在使用更新进度条的时候一般会去调用onPorgressUpdate()方法就是在这里面进行操作的。

最后我们在来看下finish()方法中做了什么操作。

private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
复制代码

我们可以很清楚的看到,这里面其实也做了两步操作,第一是判断这个异步任务是否取消cancel了,如果cancle了就直接调用onCancelled()方法取消任务;如果不是cancle了那么就直接调用onPostExecute()方法将result结果返回给主线程去使用。

到此,AsyncTask的内部原理就全部分析完成了。

我们来总结一下:

AsyncTask内部其实是维护了两个线程池(ThreadPoolExecutor、SerialExecutor)和一个消息机制Handler(InternalHandler)。我们在使用AsyncTask的时候,在构造方法中会先实例化一个Runnable接口对象WorkerRunnable和一个任务对象FureTask。WorkerRunnable是一个Runnable,一般我们的doInbackground等耗时操作都在这个里面进行,最后将这个对象添加进入FureTask内部。

而在AsyncTask内部有两个线程池,SerialExecutor的作用其实并不是执行线程操作。而是维护了一个任务队列,通过一个同步锁的形式不断往任务队列中添加异步任务,并且在执行完队列头才能再去执行下一个任务,这就导致了我们在执行多个异步任务的时候并非我们所想的是并行,而是串行。当然如果你想进行并行操作直接调用executeOnExecutor()方法即可。

而另一个线程池ThreadPoolExecutor则是真正的执行异步操作的线程池对象。他开启线程,执行FutureTask中的任务,在执行完成之后通过一个内部的Handler,即InternalHandler将结果通知给主线程,即onPostExecutor()方法,从而执行更新UI的操作。从而完成以上所以内部的请求。

猜你喜欢

转载自juejin.im/post/5b76620bf265da28084ec898