在Android中,主线程是UI线程,当需要根据其他数据进行更新UI时,如果获取数据的操作比较耗时的话,会触发ANR,所以我们应该讲耗时的操作进行异步操作,尤其是请求网络数据的操作应该放在后台线程进行,避免ANR。
而AsyncTask是Android里很常用的异步任务请求方法,AsyncTaks基本用法都会用,网上也要好多教程,就不写例子了。
以前用的时候分析过源码,发现学问还是挺多的,今天总结一下,错误之处的请诸位指正。
分析一个AsyncTask的源码,需要了解的知识:线程池,future模式,无锁操作,handler、looper和Message之间的关系。
1.先看一下AsyncTask的基本成员们:
public abstract class AsyncTask<Params, Progress, Result> { private static final String LOG_TAG = "AsyncTask"; private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = CPU_COUNT + 1; private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE = 1; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); /** * 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 final int MESSAGE_POST_RESULT = 0x1; private static final int MESSAGE_POST_PROGRESS = 0x2; private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; private static InternalHandler sHandler; private final WorkerRunnable<Params, Result> mWorker; private final FutureTask<Result> mFuture; private volatile Status mStatus = Status.PENDING; private final AtomicBoolean mCancelled = new AtomicBoolean(); private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); //后续省略 }
首先我们看到了AsyncTask必备的3大参数Params,Progress,Result。后续我们会再见到它们。
CPU_COUNT是java虚拟机可用的处理器个数,可以通过Runtime类的availableProcessors()方法得到。
看到刚开始的几个静态常量CORE_POOL_SIZE和MAXMUM_POOL_SIZE,就知道AsyncTask实现了一个线程池的机制。
定义了一个线程工场,以便于自定义线程
任务阻塞队列是一个定义了一个大小为128的无界任务队列。
ThreadPoolExecutor定义了一个线程池,核心池大小是CORE_POOL_SIZE,线程池最大容量是MAXIMUM_POOL_SIZE,多余空闲线程的存活时间是KEEP_ALIVE,单位是秒,使用一个大小为128的LinkedBlockingQueue作为任务队列,用一个无锁整型计数的线程工场产生线程。
定义了两个常量MESSAGE_POST_RESULT和MESSAGE_POST_PROGRESS,用于与主线程通信的标签,表示当获取到与主线程交互的数据类型,是进度信息还是结果信息。
定义了两个无锁布尔值作为任务激活或者取消的标志位,无锁操作即CAS,使用于并发情况下数据的更新。
然后看一下这里自定义的SeiralExcuter,实现了Executor接口,用于串行执行任务,并且设置为默认的Executor.
AsyncTask定义了两个线程池:异步线程池THREAD_POOL_EXECUTOR,同步线程池SeiralExcuter
随着Android版本的不同,AsyncTask处理任务默认是串行还是并行几经更迭,至今又改回串行,因为如果有一个UI的状态信息需要不同任务下反复更改,那么并行处理显然会出现更新顺序的问题,很麻烦,所以尽量使用串行的方法。
2.执行任务的方式。
一个自定义WorkerRunnable,实现了Callable接口
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { Params[] mParams; }
再看一下AsyncTask的初始化方法:
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); } } }; }
随后用这个WorkerRunnable实例构造了一个FutureTask实例,这个FutureTask则是JDK中对于future模式的一种实现。future模式是多线程开发中常用的一种设计模式,主要思想是异步调用。当Client想Server请求数据时,如果这个请求操作耗时比较长,那么Server会先立即返回一个FutureData,这个数据是一份虚拟的数据,此时Client就可以去做其他的事情了,过一段时间在调用get方法,如果这个时候Server已经准备好返回数据了,就会返回我们真正需要的数据RealData,否则Client会进入等待状态,直至返回RealData。简单的说,可以把FutureData理解成一份契约,可以用来装配RealData的契约。
这样Client就不用一直等待数据的返回,拿到FutureData后就可以先去做别的事情了,到需要的时候调用get方法获得RealData.
实现一个Callable接口,它的call方法会构造我们需要的真实数据并返回。
构造futuretask的时候,使用Callable接口,告诉futuretask我们需要的数据应该如何产生,使用future.get()来获取实际的数据。
在重复一遍,WorkerRunnable实现了Callable接口,然后用这个实例去构造一个FuntureTask实例。
当准备好future模式后,我们需要决定让任务串行执行还是并行执行,Android官方认为大多数情况下尽量选择串行执行,直接调用execute方法即默认使用SeiralExcuter。
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
如果一定要并行执行,则调用executeOnExecutor方法,并且设置Executor为前文定义的线程池THREAD_POOL_EXECTOR:
execute方法也是调用executeOnExecutor方法,只是将executor直接设置为默认的SeiralExecutor。
再强调一遍,必须在UI线程中调用执行方法。
3.执行的过程
直接看一下executeOnExector方法:
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; }
可以看到,在执行前先检查任务状态,如果正在执行或者已经完成,则抛出异常。处于挂起状态则设置为RUNNING状态,随后执行onPreExecute方法,此方法需要重写,用于执行任务前对UI的动作。
随后将执行任务所需参数赋给我们自定义的WorkerRunnable实例mWorker构造future实例mFuture,执行futureTask,处理任务。最后返回this,是为了保持UI线程对这个AsyncTask的引用。
到这里,我们回头看一下AsyncTask初始化的方法了。 (上文已引用,此处隐藏)
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); } } }; }
WorkerRunnable中的Call方法是继承自Callable的方法,这个方法会返回需要构造的实际数据。任务线程被激活后就将其优先级设置为后台线程,将我们任务需要的Param参数传递给doInBackground方法,去在后台执行耗时任务,整个耗时任务包括网络请求和信息处理的过程,在执行该方法的时候,可以激活publishProcess方法来进行主线程进度更新的显示,当处理完数据后返回Result。
在mFuture中重写了done方法,即当mFuture的mWorker处理完数据后,mFuture调用了get方法和postResultIfNotInvoked方法,get方法如下:
public final Result get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return mFuture.get(timeout, unit); }
调用future模式的get方法,获取到了我们想要的结果数据,之后通过postResultIfNotInvoked方法,将数据发送出去。
private void postResultIfNotInvoked(Result result) { final boolean wasTaskInvoked = mTaskInvoked.get(); if (!wasTaskInvoked) { 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; }
终于到这一步了,可以看到数据处理完毕后,我们把在FutureTask中处理后的数据最终传给了一个message,用来与UI线程进行通信,有了Message,Handler还远么?通过getHandler方法得到一个Handler,这个Handler是自定义的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: // 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; }
此处Looper.getMainLooper () 获得UI线程的Lopper,准备更新主线程的UI啦!
根据请求的标签信息判定所需行为,当需要更新进度信息时,将数据分发给onProgressUpdate方法去主线程显示进度的更新,当需要传递结果时调用finish方法,此时如果请求已被取消则不发送,如果没被取消则发送至UI线程进行UI信息的更新。
至此,AsyncTask的基本流程就走了一遍了。
总结一下:
利用FutureTask进行异步请求,通常情况下串行处理请求,如果有并行需求则使用线程池应对并发请求,线程池的配置取决于当前虚拟机所能使用的CPU数量。
三大参数:Params任务后台执行所需的参数类型,Progress是如果需要前台进度更新的更新值类型,Result则是任务返回值的类型。
使用execute方法激发异步任务执行,在发出请求之前,使用onPreExecute可以在执行任务前对UI进行标记,随后调用了doInBackground方法,进行后台耗时任务的操作,在这个过程中可以调用publishProgress方法将信息放入message,通过InternalHandler传递,调用onProgressUpdate方法将进度更新信息发送至UI线程,展示进度情况,处理完毕后,InternalHandler将结果数据的message通过onPostExecute发送至UI线程,UI线程根据结果数据进行UI的更新。
所以,在使用AsyncTask时,我们需要重写上面标红的四个方法。最少得重写一个doInBackground方法,一般情况下还得重写一个onPostExecute方法。
这种大的流程框架已经被写好(注意executrOnExecutor方法是final的),我么只需要自己定制流程一部分具体实现的方式,也就是设计模式之中的模板方法模式。
public class MainActivity extends Activity { private static final String TAG = "ASYNC_TASK"; private Button execute; private Button cancel; private ProgressBar progressBar; private TextView textView; private MyTask mTask; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); execute = (Button) findViewById(R.id.execute); execute.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //注意每次需new一个实例,新建的任务只能执行一次,否则会出现异常 mTask = new MyTask(); mTask.execute("http://www.baidu.com"); execute.setEnabled(false); cancel.setEnabled(true); } }); cancel = (Button) findViewById(R.id.cancel); cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //取消一个正在执行的任务,onCancelled方法将会被调用 mTask.cancel(true); } }); progressBar = (ProgressBar) findViewById(R.id.progress_bar); textView = (TextView) findViewById(R.id.text_view); } private class MyTask extends AsyncTask<String, Integer, String> { //onPreExecute方法用于在执行后台任务前做一些UI操作 @Override protected void onPreExecute() { Log.i(TAG, "onPreExecute() called"); textView.setText("loading..."); } //doInBackground方法内部执行后台任务,不可在此方法内修改UI @Override protected String doInBackground(String... params) { Log.i(TAG, "doInBackground(Params... params) called"); try { HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(params[0]); HttpResponse response = client.execute(get); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { HttpEntity entity = response.getEntity(); InputStream is = entity.getContent(); long total = entity.getContentLength(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int count = 0; int length = -1; while ((length = is.read(buf)) != -1) { baos.write(buf, 0, length); count += length; //调用publishProgress公布进度,最后onProgressUpdate方法将被执行 publishProgress((int) ((count / (float) total) * 100)); //为了演示进度,休眠500毫秒 Thread.sleep(500); } return new String(baos.toByteArray(), "gb2312"); } } catch (Exception e) { Log.e(TAG, e.getMessage()); } return null; } //onProgressUpdate方法用于更新进度信息 @Override protected void onProgressUpdate(Integer... progresses) { Log.i(TAG, "onProgressUpdate(Progress... progresses) called"); progressBar.setProgress(progresses[0]); textView.setText("loading..." + progresses[0] + "%"); } //onPostExecute方法用于在执行完后台任务后更新UI,显示结果 @Override protected void onPostExecute(String result) { Log.i(TAG, "onPostExecute(Result result) called"); textView.setText(result); execute.setEnabled(true); cancel.setEnabled(false); } //onCancelled方法用于在取消执行中的任务时更改UI @Override protected void onCancelled() { Log.i(TAG, "onCancelled() called"); textView.setText("cancelled"); progressBar.setProgress(0); execute.setEnabled(true); cancel.setEnabled(false); } } }
源码地址:https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/AsyncTask.java