AsyncTask使用及源码分析
Posted android的那点事
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AsyncTask使用及源码分析相关的知识,希望对你有一定的参考价值。
先简单说一下AsyncTask如何使用的.
AsyncTask使用
1.先写一个类继承AsyncTask,AsyncTask这里有三个泛型参数,分别是"执行任务输入的参数","后台执行任务的执行进度","后台执行最终结果参数";不是每个参数都要使用,不使用时用java.lang.Void类型代替。
private class MyAsyncTask extends AsyncTask<String,Integer ,String>{
....}2.调用execute方法开始执行任务,execute方法输入执行任务输入参数
MyAsyncTask mTask = new MyAsyncTask();
mTask.execute("http://www.jianshu.com");3.下面分析AsyncTask内部各个方法的使用
private class MyAsyncTask extends AsyncTask<String,Integer ,String>{
//用于执行后台任务前的一些UI操作,比如提示用户开始下载了,这个是一个主线程,可以直接更新UI
@Override
protected void onPreExecute() {
super.onPreExecute();
}
//执行后台耗时任务,是在子线程进行,这里千万不能更新UI
@Override
protected String doInBackground(String... params) {
//publishProgress(50);这个就是更新后台任务执行的百分比,调用这个方法,最后是调用了onProgressUpdate
//用于更新后台任务进度信息
return null;
}
//用于更新进度信息,这个是一个主线程,可以直接更新UI,比如提示用户下载了百分之多少
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
//执行完后台任务后更新UI,是在主线程,可以直接更新UI显示结果
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
}
//取消执行任务时更新UI,是在主线程,可以直接更新UI
@Override
protected void onCancelled() {
super.onCancelled();
}
}
1.execute(Params... params),执行一个异步任务,需要我们在代码中调用此方法,触发异步任务的执行。
2.onPreExecute(),在execute(Params... params)被调用后立即执行,用于执行后台任务前的一些UI操作,比如提示用户开始下载了,这个是一个主线程,可以直接更新UI
3.doInBackground(Params... params),在onPreExecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。在执行过程中可以调用publishProgress(Progress... values)来更新进度信息。这里千万不能更新UI
4.onProgressUpdate(Progress... values),在调用publishProgress(Progress... values)时,此方法被执行,直接将进度信息更新到UI组件上。
5.onPostExecute(Result result),当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。
6.onCancelled取消执行任务时更新UI,是在主线程,可以直接更新UI.
接下来我们从源码上分析,它是如何实现的,在使用过程中有什么缺点,又是如何优化的.
AsyncTask源码分析
先看一下AsyncTask的构造方法
public AsyncTask() {
//这个是实现Callable接口
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;
}
};
//FutureTask是实现Futrure接口,java异步任务方法
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);
}
}
};
}
看他的构造方法,一个是实现Callable接口的WorkerRunnable,另一个FutureTask,其实了解java的多线程机制,异步任务应该就很好明白,这个我简单讲一下,在WorkerRunnable类中call方法里面会执行doInBackground(mParams),就是这是我们前面提到的执行耗时任务的方法; FutureTask类中done方法是在耗时任务执行完成就会回调,回调调用了 postResultIfNotInvoked(get());get()方法就是返回执行结果的,这个方法一个阻塞方法,当耗时任务还没有执行完时,它不会返回结果,而阻塞的,只有当执行完成后,才会返回执行结果.postResultIfNotInvoked(get())这个方法后面再我讲,我先讲一下这个任务是如何开始执行的;
当我们调用execute(Params... params),开始执行一个异步任务;
先看一下源码:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
这个只是调用了executeOnExecutor这个方法,这个方法有二个参数,第二个参数params是执行异步任务参数,这个不多讲,应该明白,是第一个sDefaultExecutor是一个线程池;
private static volatile Executor sDefaultExecutor=SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
public static final Executor THREAD_POOL_EXECUTOR;
//这个是静态代码块,AsyncTask初始化时就先执行这一块代码
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;
}
ThreadPoolExecutor是线程池,这里先简单讲解一下这个类;
corePoolSize:线程池的核心线程数,默认情况下,核心线程数会一直在线程池中存活,即使它们处理闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会执行超时策略,这个时间间隔由keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止。
maximumPoolSize:线程池所能容纳的最大线程数量,当活动线程数到达这个数值后,后续的新任务将会被阻塞。
keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程。
unit:用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒),TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分钟)等。
workQueue:线程池中的任务队列,通过线程池的execute方法提交Runnable对象会存储在这个队列中。
threadFactory:线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r)。
除了上面的参数外还有个不常用的参数,RejectExecutionHandler,这个参数表示当ThreadPoolExecutor已经关闭或者ThreadPoolExecutor已经饱和时(达到了最大线程池大小而且工作队列已经满),execute方法将会调用Handler的rejectExecution方法来通知调用者,默认情况 下是抛出一个RejectExecutionException异常。了解完相关构造函数的参数,我们再来看看ThreadPoolExecutor执行任务时的大致规则:
(1)如果线程池的数量还未达到核心线程的数量,那么会直接启动一个核心线程来执行任务
(2)如果线程池中的线程数量已经达到或者超出核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
(3)如果在步骤(2)中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
(4)如果在步骤(3)中线程数量已经达到线程池规定的最大值,那么就会拒绝执行此任务,ThreadPoolExecutor会调用RejectExecutionHandler的rejectExecution方法来通知调用者。
下面我们就先介绍ThreadPoolExecutor的构造方法中各个参数的含义。
我们再进去看一下这个方法
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
mStatus = Status.RUNNING;
//开始执行任务时回调的方法.这个前面介绍如何使用时讲过
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
通过调用execute(Params... params),然后调用executeOnExecutor方法,executeOnExecutor方法中,首先调用onPreExecute();这个方法执行任务前的回调,做一些简单的工作,比如提示用户开始下载了等等; exec.execute(mFuture);这个方法是关键,把我们前面AsyncTask构造方法中的mFuture提交给 exec线程池,开始执行任务,这就是把耗时任务封装起来丢给线程池,线程池里的子线程根据任务队列里的任务开始处理任务,然后处理完成后FutureTask中done方法会回调;我们回到postResultIfNotInvoked(get())这个方法;
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
这个方法中,result就是get()返回来的结果,就是耗时任务执行的结果
然后再调了 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;
}
一看这源码,不就发一个消息,hander接收消息处理
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
//这里接收耗时任务执行完成结束,调finish方法
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
hander接收耗时任务执行完成结束,调finish方法;
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
finish方法调用 onPostExecute(result);到这里应该明白了,为什么 onPostExecute(result)会回调任务处理完的的结束,并且可以在onPostExecute(result)方法中直接更新UI了.
好了,AsyncTask的源码差不多也就这些;在这里总结一下使用AsyncTask有哪些坑;
AsyncTask中维护着一个长度为128的缓冲队列,缓冲队列已满时,如果此时向线程提交任务,将会抛出RejectedExecutionException。
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);生命周期
很多开发者会认为一个在Activity中创建的AsyncTask会随着Activity的销毁而销毁。然而事实并非如此。AsyncTask会一直执行, 直到doInBackground()方法执行完毕。然后,如果 cancel(boolean)被调用, 那么onCancelled(Result result) 方法会被执行;否则,执行onPostExecute(Result result) 方法。如果我们的Activity销毁之前,没有取消 AsyncTask,这有可能让我们的AsyncTask崩溃(crash)。因为它想要处理的view已经不存在了。所以,我们总是必须确保在销毁活动之前取消任务。总之,我们使用AsyncTask需要确保AsyncTask正确地取消。
另外,即使我们正确地调用了cancle() 也未必能真正地取消任务。因为如果在doInBackgroud里有一个不可中断的操作,比如BitmapFactory.decodeStream(),那么这个操作会继续下去。
内存泄漏
如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。结果丢失
屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。
本人做android开发多年,以后会陆续更新关于android高级UI,NDK开发,性能优化等文章,更多请关注我的微信公众号:谢谢!
以上是关于AsyncTask使用及源码分析的主要内容,如果未能解决你的问题,请参考以下文章
Android中AsyncTask基本用法与源码分析(API 23)