AsyncTask 用法及源码解析
来源:互联网 发布:百度一下淘宝网裤子 编辑:程序博客网 时间:2024/06/08 06:23
AsyncTask 是一种轻量级异步任务类,可以在线程池执行后台任务,获取到的结果传递给主线程并且在主线程中更新 UI。AsyncTask 比较适合执行短时间任务,对于长时间任务推荐使用 Executor,ThreadPoolExecutor 和 FutureTask。
AsyncTask 是一个抽象类,提供三个泛型参数,分别是 Params,Progress 和 Result;以及 4 个步骤:onPreExecute,doInBackground,onProgressUpdate 和 onPostExecute。
AsyncTask 三个泛型参数
Params:发送给执行任务的参数类型。
Progress:执行后台任务进度的类型。
Result:执行完后台任务返回的结果类型。
AsyncTask 四个核心方法
onPreExecute():在任务执行之前调用,主线程执行;主要做一些初始化工作,比如在用户界面展示进度条。
doInBackground(Params…):onPreExecute() 执行完成后被调用,在线程池执行;所有的异步操作都在这个方法执行,执行结果被返回时,onPostExecute(Result) 会被调用。如果在该方法中调用 publishProgress(Progress…),那么方法 onProgressUpdate(Progress…) 也会被调用,主要用于更新后台任务进度。
onProgressUpdate(Progress…):publishProgress(Progress…) 执行完之后被调用,在主线程执行;主要在用户界面显示后台任务执行进度。
onPostExecute(Result):doInBackground(Params…) 执行完之后调用,在主线程执行;参数 Result 是 doInBackground(Params…) 的返回值。
一个异步任务可以通过调用 cancel(boolean) 随时取消,此时 isCancelled() 被调用,这就导致 doInBackground(Params…) 执行完后 onPostExecute(Result) 不会被调用。
在使用 AsyncTask 的过程中,要注意以下几点:
AsyncTask 类必须在 UI 线程加载,Android 4.1 已经自动绑定了。
AsyncTask 实例必须在 UI 线程创建。
execute(Params…) 必须在 UI 线程调用。
不要手动调用 onPreExecute()、onPostExecute(Result)、doInBackground(Params…)、onProgressUpdate(Progress…)。
一个 AsyncTask 对象只能被执行一次;否则会抛异常。
以上是 AsnycTask 基本知识点,掌握知识点后就要学会如何使用它。那么接下来就来学习 AsyncTask 用法。
AsyncTask 用法
AsyncTask 是抽象类,不能直接实例化,必须创建新类并继承它,抽象方法 doInBackground(Params…) 是一定要重写的,其它三个方法根据自己的需求确定。以下通过 URL 获取数据为例子来讲解 AsyncTask 的用法。代码如下:
public class AsyncTaskExample extends AsyncTask<String, Integer, String> { @Override protected void onPreExecute() { super.onPreExecute(); mLoad.setVisibility(View.VISIBLE); } @Override protected String doInBackground(String... params) { return getUrlResponse(params[0]); } @Override protected void onPostExecute(String s) { super.onPostExecute(s); mLoad.setVisibility(View.GONE); mText.setText(s); }}
从代码中可以很清晰地看出,第 5 行是显示加载进度条,表示正在获取数据;第 10 行是核心代码,异步操作,网络请求数据并将结果返回;第 16 - 17 行代码主要操作是隐藏进度条,表示数据加载完毕,并将获取到的结果显示出来。这里主要给出核心代码,至于其它代码也就调用而已。
那么该如何调用呢?很简单,一行代码就搞定
new AsyncTaskExample().execute(url);
AsyncTask 源码解析
知其然必知其所以然。对于新知识点,学会使用之后,就应该探究其原理。由于个人倾向于通过画图来理解知识点的流程,因此先简单地给出 AsyncTask 任务执行的流程图,再根据流程图和源码进行讲解。流程图如下:
对于源码的理解,一般是以最终调用的方法为入口,一步一步地理解整个流程。那么对于 AsyncTask 该从哪里入手呢?当然是从方法 execute(Params… params) 入手了,代码如下:
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params);}
对于 AsyncTask 不同版本,execute(Params… params) 方法的执行方式是不一样的。Android 1.6 以前,AsyncTask 是单线程串行执行任务的;Android 1.6,AsyncTask 是线程池多线程并行执行任务;但是到 Android 3.0,AsyncTask 又改为单线程串行执行任务的。该方法的逻辑很简单,直接调用方法 executeOnExecutor(Executor exec, Params… params),将我们传入的参数 params 和 sDefaultExecutor 传到该方法里,并将的返回值返回。那么来看下该方法的具体实现,代码如下:
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;}
executeOnExecutor(Executor exec, Params… params) 方法是在线程池 THREAD_POOL_EXECUTOR 执行,允许多任务并发执行,但是不推荐采用多任务并发执行;在主线程执行。该方法实现的主要功能是:
检查任务状态,并记录任务当前状态;
调用 onPreExecute() 方法,根据我们自己的需求可以重写该方法;
将我们传入的参数 params 赋值给 WorkRunnable 中字段 mParams(稍后解释);
调用 SerialExecutor 中方法 execute(Runnable r) 执行任务。
mWorker 是 WorkerRunnable
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; }};
该回调方法实现的主要功能:
将 mTaskInvoked 设置为 true,表示任务已经被调用过;
设置线程优先级为后台线程;
调用 doInBackground(mParams) 方法,异步执行,后台执行的逻辑都写在这个方法里面,一定要被重写;如果任务执行抛出异常时,取消任务;
调用 postResult(result) 方法;
postResult(result) 的具体实现如下:
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result;}
从代码里可以看出,将执行结果通过 sHandler 发送 MESSAGE_POST_RESULT 的消息,然后 handleMessage() 方法收到消息后进行相应的处理。sHandler 是 InternalHandler 实例,主要作用是将任务执行的环境从线程切换到主线程中,从 InternalHandler 的具体实现就可以看出了。代码如下:
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: result.mTask.finish(result.mData[0]); break; case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break; } }}
从构造函数 InternalHandler() 就可以看出了,获取主线程 Looper,而 Handler 必须与 Looper 进行绑定,因此可以断定是在主线程里。handleMessage()
函数对两种消息进行处理:MESSAGE_POST_RESULT 和 MESSAGE_POST_PROGRESS;而我们刚刚发送的消息是 MESSAGE_POST_RESULT,那就先来看该消息收到后会做什么处理吧?很显然,调用 finish(Result result),具体实现如下:
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED;}
如果任务被取消了,直接调用 onCancelled(result) 方法,onPostExecute(result) 方法不会被调用;否则就调用 onPostExecute(result) 方法,该方法需要被重写,在主线程执行,根据返回的结果进行相应的处理;最后修改任务的状态。那么对于消息 MESSAGE_POST_PROGRESS 是从哪里发出来的呢?还记得在前面的知识点讲解中有提到过如果在 doInBackground(mParams) 方法中调用 publishProgress(Progress…) 方法时,方法 onProgressUpdate(Progress…) 也会被调用,用于后台任务进度更新。没错,消息 MESSAGE_POST_PROGRESS 就是用来处理进度更新的。先看下 publishProgress(Progress…) 具体实现:
protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget(); }}
很显然,如果任务没有被取消的话,就会发送消息 MESSAGE_POST_PROGRESS,那么来看下收到该消息后的处理逻辑,即调用 onProgressUpdate(Progress… values) 方法,该方法需要我们根据自己的需求进行重写。
再回到 executeOnExecutor(Executor exec, Params… params) 方法,第 14 行代码开始执行任务,在理解如何执行任务之前,先来理解参数 mFuture 和 sDefaultExecutor 的含义。
mFuture 是 FutureTask 实例,在 AsyncTask 构造方法中初始化。将 mWorker 作为参数传入 FutureTask 构造函数,个人认为传入该参数的作用是由于 FutureTask 中 run() 方法会被调用,而在该方法里会通过传入参数 mWorker 调用 call() 方法,进而使任务得到执行。FutureTask 是一个并发执行任务类,可以执行任务、取消任务、查询结果、获取结果;提交到线程池执行。实现的接口有 Future、Runnable。
对于传入的参数 sDefaultExecutor,究竟是什么啥玩意呢?让我们来探个究竟吧。sDefaultExecutor 是 SerialExecutor 的实例,而 SerialExecutor 实际上是一个串行的线程池,主要的功能是一个进程中所有的 AsyncTask 任务都在这个串行的线程池中排队执行。看到这里,是不是还不知道任务真正在哪里被开始执行?其实以上都只是铺垫,下面才真正拉开序幕。真正开始执行任务的逻辑是在 SerialExecutor 中 execute(Runnable r) 方法里,具体实现如下:
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); } }}
首先将 AsyncTask 通过线程池 SerialExecutor 添加到队列里(从这里可以看出 SerialExecutor 的作用),然后重写 run() 方法,并判断 mActive 是否为 null,即当前是否有任务在执行,如果有任务执行的话就等待该任务执行完后再执行其他任务,否则就执行任务,即调用 scheduleNext() 方法,该方法的主要功能是从队列 mTasks 获取任务,任务不为空的话就直接提交到线程池 THREAD_POOL_EXECUTOR 里执行(任务真正开始执行),即启动任务,根据个人的理解,任务被启动后,会调用第 6 行代码,即 run() 方法,进而调用 FutureTask 中 run() 方法,从而会调用 WorkerRunnable 中 call() 方法,因此任务被执行,我们重写的方法也会被调用。结合以上流程图应该能更清晰地理解 AsyncTask 执行流程。
以上是自己在学习 《Android 开发艺术探索》 这本书第十一章关于 AsyncTask 这个主题的学习笔记,由于自己能力有限,有错误的地方欢迎指出。
参考资料
https://developer.android.com/reference/android/os/AsyncTask.html
《Android 开发艺术探索》》中 第 11 章 Android 的线程和线程池
- AsyncTask 用法及源码解析
- AsyncTask使用及源码解析
- Android AsyncTask用法及源码分析
- Picasso 用法及源码解析
- ArrayList用法及源码解析
- LinkedList用法及源码解析
- HashMap用法及源码解析
- AsyncTask源码解析
- AsyncTask源码解析
- Android AsyncTask源码解析
- AsyncTask源码解析
- AsyncTask源码解析
- Android AsyncTask 源码解析
- Android AsyncTask 源码解析
- Android AsyncTask 源码解析
- Android AsyncTask 源码解析
- AsyncTask源码解析
- Android AsyncTask 源码解析
- WIN10 安装 MySQL 5.7 .zip
- perl将字符串时间转换成 epoch time
- 工厂模式定义
- MATLAB入门教程
- perl 字符串格式转化为时间格式,时间戳,epchotime
- AsyncTask 用法及源码解析
- POJ2299Ultra-QuickSort逆序对裸题
- 关于JS触发全屏和退出全屏的介绍
- Android 常用知识点
- Mysql并发时经典常见的死锁原因及解决方法
- MATLAB入门教程
- Discuz!开发之发帖回帖@会员功能代码解析
- View和ViewGroup
- 2017回顾优米网历年定位