AsyncTask 必知必会
2016-09-22 15:43
411 查看
概要
AsyncTask在后台执行操作,并把结果发布到 UI 线程,避免手动调用 Thread 和 Handler.
官网提到
AsyncTask适用于短时间操作,最多几秒时间。why? 后面源码分析说明。
官网还提到,如果执行长时间操作,强烈推荐我们使用并发包下的
Executor,
ThreadPoolExecutor,
FutureTask。 这个怎么玩呢? 后面举例。
例子
我把官网的小例子改编了下private class DownloadFilesTask extends AsyncTask<URL, Void, 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]); } return totalSize; } protected void onPostExecute(Long result) { showDialog("Downloaded " + result + " bytes"); } }
执行
AsyncTask代码如下
new DownloadFilesTask().execute(url1, url2, url3);
构造方法
给开发者都调用的构造方法只有无参的构造方法/** * Creates a new asynchronous task. This constructor must be invoked on the UI thread. */ public AsyncTask() { this((Looper) null); }
从注释看,构造方法必须在
UI thread中调用,why? 然后调用了带
Looper参数的构造方法
public AsyncTask(@Nullable Looper callbackLooper) { mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() ? getMainHandler() : new Handler(callbackLooper); 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); } } }; }
这个构造函数只初始化了三个变量
mHandler,
mWorker,
mFuture。
mWorker类型是
Callable,是用来执行的任务。
mFuture最主要的作用是用来获取执行的结果,例如
AsyncTask的
get()方法就是用
mFuture来获取结果的。这里还复写了
done()方法,也就是任务
mWorker任务执行完毕后,会调用的,这里只是简单保证了结果会被发送到
UI Thread中。
mHandler因为传入的参数为
null,所以它的值为
getMainHandler()
private static Handler getMainHandler() { synchronized (AsyncTask.class) { if (sHandler == null) { sHandler = new InternalHandler(Looper.getMainLooper()); } return sHandler; } } private static class InternalHandler extends Handler { public InternalHandler(Looper looper) { super(looper); } @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; } } }
也就是说
mHandler也就是用主线程
Looper创建的一个
Handler对象, 用来传递结果,以及更新进度。
这也就回答了为何要在
UI thread中创建
AsyncTask对象,因为要保证用
Handler能把消息发送回
UI thread。
execute()
@MainThread public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } @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)"); } } // 更新 asyncTask 状态 mStatus = Status.RUNNING; // 后台任务执行前调用 onPreExecute(); mWorker.mParams = params; //执行任务 exec.execute(mFuture); return this; }
execute()方法实际是调用了
executeOnExecutor()方法,这两个方法都加上了
@MainThread注解,告诉这两个方法需要在
main thread中调用,原理与前面一样。
executeOnExecutor()中通过对
mStatus的判断的那段代码,就可以知晓为何一个
AsyncTask对象只能调用一次
execute()方法了。
executeOnExecutor()最后用
sDefaultExecutor的
execute()方法执行了
mFuture任务。看看
sDefaultExecutor
public static final Executor THREAD_POOL_EXECUTOR; static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); threadPoolExecutor.allowCoreThreadTimeOut(true); THREAD_POOL_EXECUTOR = threadPoolExecutor; } private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; public static final Executor SERIAL_EXECUTOR = new 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); } } }
sDefaultExecutor是静态的对象,也就说所有的
AsyncTask共享一个
sDefaultExecutor,那么如果创建了 N 个
AsyncTask对象并且调用
execute()方法,都会用
sDefaultExecutor执行任务。
sDefaultExecutor决定了
AsyncTask执行任务的顺序,是顺序执行任务还是并行执行任务。 默认的是用的
SerialExecutor,它保证任务的顺序执行,怎么保证了?
SerialExecutor的
execute()和
scheduleNext()方法都添加了
synchronized关键字,也就是任意时刻,只有一个任务能提交,而且从代码逻辑 看,提交之后,必须执行完,才能提交第二个任务并执行,如此循环下去,就保证了任务的顺序执行。
OK, 现在回到正题, 现在是用
sDefaultExecutor提交了
mFuture, 而
mFuture是由
mWorker构造,那么就会执行
mWorker的
call()方法
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; } };
call()方法中调用了喜闻乐见的
doInBackground()方法,注意,这个方法是在后台线程中执行的,由
sDefaultExecutor保证的。
然后在异常处理的代码中发现,在发生异常的时候,首先会把任务设置为取消状态,然后再抛出。
在
finally中,用
postResult(result)把结果发送出去,最后会传递到
UI thread中,代码如下
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 class InternalHandler extends Handler { public InternalHandler(Looper looper) { super(looper); } @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; } } } private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
在
finish()方法中可以看到,如果任务取消了(前面提到的异常 或者 手动调用
asyncTask.cancel(boolean)),就会调用
onCancelled()回调,如果是正常流程,就执行
onPostExecute()回调。
并发执行任务
默认情况下,AsyncTask是顺序执行任务,那么可不可以并行执行任务呢?我们只要改变执行任务的
Executor就行了。
setDefaultExecutor()方法可以改变默认的
Executor,但是对开发者来说这个方法是不可以的
/** @hide */ public static void setDefaultExecutor(Executor exec) { sDefaultExecutor = exec; }
这个方法是隐藏的,why? 因为一旦开发者改变了
AsyncTask的
sDefaultExecutor,一会并行执行一会顺序执行,那岂不是乱套了~
那么利用
executeOnExecutor()方法自己传入
Executor
@MainThread public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {}
AsyncTask同时也提供了一个线程池用来并行执行任务
public static final Executor THREAD_POOL_EXECUTOR; static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); threadPoolExecutor.allowCoreThreadTimeOut(true); THREAD_POOL_EXECUTOR = threadPoolExecutor; }
也就是说
new MyAsyncTask().execute(THREAD_POOL_EXECUTOR, url1, url2, url3)就可以并行执行多个任务。
那有人会问,既然可以并行,为何默认要顺序执行? 问题就出在
ThreadPoolExecutor上,如果任务的处理速度超过了任务的提交速度,那么并行完全 OK。 然而,如果提交的任务的速度远远大于任务的处理速度,那么 CPU 超负荷,性能得不到保障。因此,如果想要并行,就要考虑如何构造
ThreadPoolExecutor对象。
进度更新
到现在为止,我还没有提到如何更新进度,官方的例子如下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"); } }
从例子中可以看到,在
doInBackground()方法中,调用了
publishProgress()方法来发布进度,源码如下
@WorkerThread protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget(); } } private static class InternalHandler extends Handler { // ... @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; } } }
publishProgress()是通过
mHandler把消息传回
UI thread,然后回调了
onProgressUpdate()(这个方法在例子中复写了)。 但是我们要注意,只有在这个任务没有被取消的情况下,才能发布进度。
如何正确取消任务
可能通过AsyncTask的
cancel()方法取消任务
public final boolean cancel(boolean mayInterruptIfRunning) { mCancelled.set(true); return mFuture.cancel(mayInterruptIfRunning); }
从源码实现看,参数
mayInterruptIfRunning代表是否中断线程。如果线程不能响应中断,那么这种方式是无法取消任务的,只能等后台任务执行完毕,然后回调
onCancelled()而不是
onPostExecute()。
哪些情况线程能响应中断,请参考
Thread类的
interrupt()方法.
例子中,在
doInBackground()方法中,用
for循环,进行了多个下载任务,需要注意的是在
publishProgress()方法后,会调用
isCancelled()来判断任务是否取消,如果取消了就不再进行下一个下载任务。 这给了我们一个很好的示范,如果在
doInbackground()中通过循环来处理一些事情,需要在恰当的位置判断任务是否中断,从而决定任务是否进行下一次循环。
执行长时间任务
开头提到,官网说如果运行的不是几秒的任务,而是长时间的任务,就不要用AsyncTask,而是用
Executor,
ThreadPoolExecutor,
FutureTask。 其实这是因为
AsyncTask默认顺序执行任务,如果某一个任务执行时间过长,会导致后面的任务延迟太长,而如果你很有把握,可以并发执行,也就不存在所谓的不能执行长时间任务了。
那么,既然官网都说了这茬,那么如何做呢? 如果你对 Java 并发的这些类掌握得比较好的话,就很容易了。
demo 代码如下
public class TestActivity extends AppCompatActivity { private TextView mTextView; private Handler mUIHandler; private FutureTask<String> mFutureTask; private ExecutorService mService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test_acrtivity); mTextView = findViewById(R.id.tv); mService = Executors.newCachedThreadPool(); mFutureTask = new FutureTask<String>(new Task()) { @Override protected void done() { runOnUiThread(new Runnable() { @Override public void run() { mUIHandler.sendEmptyMessage(666); } }); } }; mUIHandler = new Handler(getMainLooper()) { @Override public void handleMessage(Message msg) { if (msg.what == 666) { try { mTextView.setText(mFutureTask.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } } }; } @Override protected void onResume() { super.onResume(); mService.execute(mFutureTask); } private static class Task implements Callable<String> { @Override public String call() throws Exception { Log.d("david", "doInBackground ... "); // 假如这里3秒代表的是30秒,甚至更长时间运行的任务 TimeUnit.SECONDS.sleep(3); return "Download has completed!"; } } }
原理与
AsyncTask一致,我就不献丑解释代码了。
那么,你可能有疑问,这明明可以通过
Thread和
Handler实现的,为何要搞这么复杂。 简单的理由有几个
Thread 类执行任务无法查询执行的结果,也没有 API 调用来取消任务。
并发任务需要用
ThreadPoolExecutor,而 Thread 类无法做到这一点。
其实把代码做下封装,基本上就是
AsyncTask了。
相关文章推荐
- 从源码安装Mysql/Percona 5.5
- 浅析Ruby的源代码布局及其编程风格
- Android AsyncTask源码分析
- asp.net 抓取网页源码三种实现方法
- JS小游戏之仙剑翻牌源码详解
- JS小游戏之宇宙战机源码详解
- 深入浅析knockout源码分析之订阅
- Android中异步类AsyncTask用法总结
- jQuery源码分析之jQuery中的循环技巧详解
- 本人自用的global.js库源码分享
- java中原码、反码与补码的问题分析
- ASP.NET使用HttpWebRequest读取远程网页源代码
- Android中AsyncTask的用法实例分享
- PHP网页游戏学习之Xnova(ogame)源码解读(六)
- C#获取网页HTML源码实例
- PHP网页游戏学习之Xnova(ogame)源码解读(八)
- PHP网页游戏学习之Xnova(ogame)源码解读(四)
- Android的异步任务AsyncTask详解
- Android使用AsyncTask实现多线程下载的方法
- Android 中糟糕的AsyncTask