AsyncTask异步

Hasana ·
更新时间:2024-09-21
· 925 次阅读

AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后会把执行的进度和最终结果传递给主线程并更新UI。从实现上来说,AsyncTask内部封装了Thread和Handler,通过AsyncTask可以更加方便的执行后台任务以及在主线程中访问UI,但AsyncTask并不适合非常耗时的后台任务,对于特别耗时的任务,建议使用线程池。

AsyncTask本身是一个抽象的泛型类,它提供了Params、Progress、Result 三个泛型参数,其类声明如下:

public abstract class AsyncTask {……}

由类声明可以看出AsyncTask抽象类确实定义了三种泛型类型 Params,Progress和Result,它们分别含义如下:

Params :表示参数的类型(简单来说就是异步任务所需要到的参数) Progress :表示后台任务执行的百分比或者进度(简单来说就是就是ijindu回调用) Result :后台执行任务最终返回的结果类型(简单来说就是结果回调用)

如果AsyncTask不需要传递具体参数,那么这三个泛型参数可以使用Void代替。现在我们创建一个自定义的AsyncTask,并对使用中要注意到的一些关键事项做说明。
AsyncTask后面的三个参数作用说明:

第一个参数:对应DoInBackground方法里面的参数类型; 第二个参数:对应onProgressUpdate方法里面的参数类型; 第三个参数:对应doInBackground方法的返回值以及onPostExecute方法的参数

现在来看看他的源码及简单执行:

public class CustomAsyncTask extends AsyncTask { 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 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 mTasks = new ArrayDeque(); 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() { 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(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(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); }

执行结果如下:

原创文章 118获赞 149访问量 9万+ 关注 私信 展开阅读全文
作者:任缥缈



asynctask

需要 登录 后方可回复, 如果你还没有账号请 注册新账号