AsyncTask异步
AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后会把执行的进度和最终结果传递给主线程并更新UI。从实现上来说,AsyncTask内部封装了Thread和Handler,通过AsyncTask可以更加方便的执行后台任务以及在主线程中访问UI,但AsyncTask并不适合非常耗时的后台任务,对于特别耗时的任务,建议使用线程池。
AsyncTask本身是一个抽象的泛型类,它提供了Params、Progress、Result 三个泛型参数,其类声明如下:
public abstract class AsyncTask<Params, Progress, Result> {……}
由类声明可以看出AsyncTask抽象类确实定义了三种泛型类型 Params,Progress和Result,它们分别含义如下:
Params :表示参数的类型(简单来说就是异步任务所需要到的参数)
Progress :表示后台任务执行的百分比或者进度(简单来说就是就是ijindu回调用)
Result :后台执行任务最终返回的结果类型(简单来说就是结果回调用)
如果AsyncTask不需要传递具体参数,那么这三个泛型参数可以使用Void代替。现在我们创建一个自定义的AsyncTask,并对使用中要注意到的一些关键事项做说明。
AsyncTask后面的三个参数作用说明:
第一个参数:对应DoInBackground方法里面的参数类型;
第二个参数:对应onProgressUpdate方法里面的参数类型;
第三个参数:对应doInBackground方法的返回值以及onPostExecute方法的参数
现在来看看他的源码及简单执行:
public class CustomAsyncTask extends AsyncTask<String, Integer, Bitmap> {
public UpdateUI updateUI;
public CustomAsyncTask(UpdateUI mUpdateUI){
updateUI = mUpdateUI;
}
/**
* onPreExecute:选择性覆写的方法
* 在主线程中执行,在异步任务执行之前,该方法将会被调用一般用来在执行后台任务前对UI做一些标记和准备工作,如在界面上显示一个进度条。
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* 抽象方法,必须实现,在线程池中被调用,耗时的异步任务就在该方法里面执行
* 将在onPreExecute方法执行后执行。其参数是一个可变类型,表示异步任务的输入参数,在该方法中还可通过publishProgress(Progress… values)
* 来更新实时的任务进度,而publishProgress方法则会调用onProgressUpdate方法。
* 此外doInBackground方法会将计算的返回结果传递给onPostExecute方法。
* 原本的泛型方法是这样的:doInBackground(Params...params)
* @return
*/
@Override
protected Bitmap doInBackground(String... strings) {
//TODO:一步任务的具体逻辑在这里实现
Bitmap bitmap = null;
try {
URL imageurl = new URL(strings[0]);
HttpURLConnection conn = (HttpURLConnection)imageurl.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
is.close();
}catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
return bitmap;
}
/**
* onProgressUpdate选择性覆写的方法
* 在主线程中执行,当后台任务的执行进度发生改变的时候此方法会被调用。原本的泛型方法是这样的:onProgressUpdate(Progress... values)
* 当该方法在父类的publishProgress(Progress… values)方法被调用后执行,一般用于更新UI进度,如更新进度条的当前进度。
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
Log.e("TAGTAG", "onProgressUpdate下载进度: " + values );
}
/**
* 选择性覆写的方法,在主线程中执行
* 在doInBackground执行完成后,被UI线程调用,原本的泛型方法是这样的:onPostExecute (Result... result)
* doInBackground方法的返回值将作为此方法的参数传递到UI线程中,并执行一些UI更新的相关操作。
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (null != updateUI){
updateUI.UpdateProgressBar(bitmap);
}
}
/**
* onCancelled是可以选择性覆写的方法
* 在主线程中执行,mAsyncTask .cancel(true)的方式调用
* 当异步任务被取消时,该方法将被调用,要注意的是这个时候onPostExecute将不会被执行
*/
@Override
protected void onCancelled() {
super.onCancelled();
}
public interface UpdateUI {
void UpdateProgressBar(Bitmap bitmap);
}
}
在activity中使用AsyncTask的时候要注意一下几点必须遵守的规则:
————AsyncTask的实例必须在主线程(UI线程)中创建(因为AsyncTask在构造方法中完成Handler的创建——获取到主线程的Looper对象,所以必须是在主线程里面完成AsyncTask的初始化,否则后面通过Handler回调通信失败),execute方法也必须在主线程中调用;
————不要在程序中直接的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法
————不能在doInBackground(Params… params)中更新UI
————一个AsyncTask对象只能被执行一次,也就是execute方法只能调用一次,多次调用将会抛出异常
在activity中的使用方式很简单:
public class RecyclerViewActivity extends BaseActivity implements CustomAsyncTask.UpdateUI {
private static String DOWNLOAD_FILE_JPG_URL = "http://img4.imgtn.bdimg.com/it/u=68625945,479175229&fm=26&gp=0.jpg";
private ImageView ivImage;
private CustomAsyncTask downLoadAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler_view);
ivImage = findViewById(R.id.ivImage);
downLoadAsyncTask = new CustomAsyncTask(this);
findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downLoadAsyncTask.execute(DOWNLOAD_FILE_JPG_URL);
}
});
}
@Override
public void UpdateProgressBar(Bitmap bitmap) {
ivImage.setImageBitmap(bitmap);
}
}
现在我们从源码角度分析AsyncTask的运行:
我们使用AsyncTask的第一步是通过new关键字创建AsyncTask的对象,在构造方法中传值,这个没什么好说的。然后是通过对象调用. execute(Params… params)方法,在execute(Params… params)的源码里面我们可以看到,他直接调用了executeOnExecutor(Executor exec, Params… params)方法。这里仔细看方法的第一个参数:是Executor,它代表什么?代表的是线程池。这个我们后面细说,这里先提一下。executeOnExecutor()方法的源码如下:
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;
}
我们可以看到:
首先,只有当mStatus!=Status.PENDING的时候,也就是任务尚未执行的状态下,异步任务才会被调用执行,否则抛出“Cannot execute task……”的异常。
然后,改变mStatus = Status.RUNNING状态。
紧接着,调用onPreExecute()方法,这也证明了我们前面所说的,onPreExecute()方法是最先被执行的,而且从执行的过程来看,到这一步,代码任务还是在主线程里面执行,并没有被切换到工作线程中去,所以onPreExecute()方法里面可以更新UI。
再然后便是异步任务所需要的参数的赋值:mWorker.mParams = params。mWorker 是一个工作任务队列WorkerRunnable。mWorker.mParams这个参数最终被封装为FutureTask对象,而FutureTask对象本质上是一个Runnable角色的并发类。
最后才是调用exec.execute(mFuture)开始执行异步任务,到了这一步,线程才被切换到工作线程。
这里我们再来说说Executor exec这个线程池接口对象,他是一个串行的线程池,一个进程中所有的AsyncTask全部在这个串行的线程池中排队执行。系统首先会把Params参数封装为WorkerRunnable对象,然后传给FutureTask,最后FutureTask对象被交给Executor的实现类的execute()方法去处理。如下:
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);
}
}
}
execute()方法会首先把FutureTask对象插入mTasks任务队列中去,如果这个时候没有正在活动的AsyncTask任务,那么就会调用scheduleNext()方法来执行异步任务AsyncTask。当AsyncTask把任务执行完后,会继续执行其他的任务直到所有的任务都被执行完为止。因此我们可以认为AsyncTask是串行执行的。
从前面的代码中我们可以得知:AsyncTask里面有两个线程池(Executor 的实现类SerialExecutor和Executor THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler)。线程池SerialExecutor用于任务排队,THREAD_POOL_EXECUTOR才是真正用于异步任务执行,之所以还要一个这样的异步的执行队列,主要就是为了满足Android3.0以上系统的并发需求。而InternalHandler则主要是用于工作线程和UI线程之间的环境切换以及数据传递。
现在我们在回到AsyncTask初始的地方——构造方法。源码如下:
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);
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);
}
}
};
}
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;
}
线程池主要完成两个功能:完成mWorker对象和mFuture对象的创建。FutureTask的run方法会调用mWorker的call()方法,因此mWorker的call()方法最终还是在工作线程里面执行的。在mWorker的call()方法中,首先执行的是mTaskInvoked.set(true),表示当前任务已经被调用过了。接着是设置工作线程的优先等级。紧接着是执行doInBackground(mParams)并将结果返回给postResult()方法。
而postResult()方法里面主要是通过getHandler()获取一个handler对象,然后发送一条”MESSAGE_POST_RESULT”的消息(通过message.sendToTarget()的方式发出去的,sendToTarget()方法源码中的target实际上就是一个Handler)。这个handler有点特殊:
InternalHandler sHandler = null;
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:
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
从这里我们可以知道,InternalHandler是一个静态的单例,为了能够将执行环节切换到主线程,就必须保证InternalHandler必须在主线程中被创建。由于静态成员变量会在加载类(class文件)的时候就被初始化,因此这就变相要求AsyncTask的实现类必须在主线程中加载,否则同一个进程中的AsyncTask都将无法正常工作。InternalHandler收到MESSAGE_POST_RESULT标识的消息后就执行finish()方法:
result.mTask.finish(result.mData[0])
具体如下:
rivate void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
inish方法逻辑很简单,就是判断请求是否被取消,如果取消的话,就调用onCancelled()方法,否则执行onPostExecute()方法,而onPostExecute()方法的参数正是doInBackground的返回结果,在这里我们可以通过回调去更新UI。到这里AsyncTask的工作过程分析完毕。
另外有一点需要注意:AsyncTask的execute方法是串行执行的,不能实现并发。如果要实现并发功能的话,要用到executeOnExecutor()方法,他的具体用法如下:
downLoadAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_FILE_JPG_URL);
我们来验证一下:
private void syncAndAsync(){
new CustomAsyncTask(this, "00001").execute(DOWNLOAD_FILE_JPG_URL);
new CustomAsyncTask(this, "00002").execute(DOWNLOAD_FILE_JPG_URL);
new CustomAsyncTask(this, "00003").execute(DOWNLOAD_FILE_JPG_URL);
new CustomAsyncTask(this, "00004").execute(DOWNLOAD_FILE_JPG_URL);
new CustomAsyncTask(this, "00005").execute(DOWNLOAD_FILE_JPG_URL);
new CustomAsyncTask(this, "qqqqq").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_FILE_JPG_URL);
new CustomAsyncTask(this, "wwwww").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_FILE_JPG_URL);
new CustomAsyncTask(this, "eeeee").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_FILE_JPG_URL);
new CustomAsyncTask(this, "rrrrr").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_FILE_JPG_URL);
new CustomAsyncTask(this, "ttttt").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_FILE_JPG_URL);
}
执行结果如下:
我们可以看到名称以“0000”开头的异步任务,执行都是按我们调用的顺序执行的。名称为字母的异步任务,执行顺序上,跟我们的调用顺序没有关联。这正好印证了我们上面的结论。
上一篇: linux关于时间处理