AsyncTask你真的会用吗?实战、原理、最佳实践!(Android Q)

AsyncTask你真的会用吗?实战、原理、最佳实践!(Android Q)AsyncTaskAsy 可以非常方便 简单的在 UI 线程中使用 它帮助我们在不使用 Thread 和 Handler 的情况下 在后台执行一个后台 异步 任务 并且把执行结果通知到 UI 线程中 也就是说 使用 AsyncTask 可以极大的简化我们进行后台任务的操作 使用它 我们不必关心工作线程是如何启动的 也不必关心 工作线程和 UI 线程之间的通信问题 这些都被 AsyncTask 通过内部封装实现了 我们只需要按接口规范使用它即可 AsyncTask 适用于短时的后台 异步 任务 最多几秒钟 它并不适合时间

AsyncTask


AsyncTask可以非常方便、简单的在UI线程中使用,它帮助我们在不使用Thread和Handler的情况下,在后台执行一个后台(异步)任务,并且把执行结果通知到UI线程中。也就是说,使用AsyncTask可以极大的简化我们进行后台任务的操作,使用它,我们不必关心工作线程是如何启动的,也不必关心,工作线程和UI线程之间的通信问题,这些都被AsyncTask通过内部封装实现了,我们只需要按接口规范使用它即可。

AsyncTask适用于短时的后台(异步)任务(最多几秒钟),它并不适合时间较长的后台任务,如果我们想在后台线程执行长时间的异步任务,可以使用java.util.concurrent并发工具包中的Executor、ThreadPoolExecutor和FutureTask等。

AsyncTask的声明和执行

AsyncTask执行后台任务,是由后台线程执行具体的任务,最后把结果发布到UI线程。

一个后台任务的声明由3种泛型类型:Params、Progress和Result,以及4个过程:onPreExecute、doInBackground、onProgressUpdate和onPostExecute构成。

我们来看使用AsyncTask有哪些要求?
  • 必须创建AsyncTask的子类,以它的子类形式来使用。
  • 必须在子类中至少实现doInBackground()回调方法,通常我们还会实现onPostExecute()方法。
  • AsyncTask类必须在UI线程中被加载(Android 4.1之后自动执行)。
  • AsyncTask实例对象,必须在UI线程中创建。
  • 必须在UI线程中调用execute()来运行任务。
3种泛型

示例:

 private class DownloadFilesTask extends AsyncTask 
   
     {} 
   

示例中的Params、Progress和Result分别是String、Integer和Long。

4个步骤
AsyncTask后台任务执行过程,将会经历4个步骤:

onPreExecute():UI线程中执行。它发生在后台任务开始执行之前,我们可以通过它来进行任务的配置,比如开始展示进度条等。

doInBackground(Params…):后台线程中执行。它在onPreExecute完成之后立即执行,它会执行较长时间的后台运算任务。运算结果必须由该步骤return,发布给onPostExecute方法。我们也可以在该过程中,调用一次或多次publishProgress(Progress…)方法来发布执行进度信息,进度信息最终会发布到UI线程中,成为onProgressUpdate(Progress…)的参数。

onProgressUpdate(Progress…):UI线程中执行。它是在调用publishProgress(Progress…)之后执行的,它通常用来展示后台任务的执行进度。

onPostExecute(Result):UI线程中执行。它是在后台任务执行结束之后调用的,由doInBackground(Params…)方法return的结果作为参数。

任务的取消

AsyncTask后台任务执行中的任意时刻,都可以调用cancel(boolean)来进行取消操作。

cancel方法需要关注以下几点:
  • 执行cancel方法之后,调用isCancelled()将返回true。
  • 执行cancel方法之后,onCancelled(java.lang.Object)方法将会在doInBackground(java.lang.Object[]) return之后执行(代替了onPostExecute方法)。
  • 我们应该尽可能及时的检测到后台任务的取消。可以在doInBackground方法中,使用isCancelled()来定时执行检测工作,一旦发现任务取消,立即取消现有操作,执行return。

AsyncTask演示Demo

示例代码,创建了一个DownloadFilesTask类,继承于AsyncTask,用于执行后台任务。

 private class DownloadFilesTask extends AsyncTask 
   
     {//3个泛型分别为:String, Integer, Long protected Long doInBackground(String... urls) {//参数为字符串列表 int count = urls.length; long time = 0; for (int i = 0; i < count; i++) { try { Thread.sleep(1000);//示例任务,执行线程休眠1秒,模拟耗时任务 } catch (InterruptedException e) { e.printStackTrace(); } time += 1000; publishProgress((int) (((i + 1) / (float) count) * 100));//更新进度 if (isCancelled()) break;//检测是否执行过程中取消了任务 } return time; } protected void onProgressUpdate(Integer... progress) { //更新进度。这里的参数是Integer对象 mTextView.setText("进度:" + progress[0]); } protected void onPostExecute(Long result) { //执行结果回调 mTextView.setText("time : " + result + " 毫秒"); } } 
   

异步任务准备好了,我们创建一个DownloadFilesTask类的实例,调用execute()方法即可执行:

 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new DownloadFilesTask().execute("url1", "url2", "url3", "url3"); } 

例子很简单,但是也完整的展示了AsyncTask的基本用法。

接下来我们来分析AsyncTask的实现原理。

AsyncTask源码解析


我们接下来分析AsyncTask的源码,看下它的实现原理。

在Demo中,我们调用AsyncTask的实例对象的execute方法来实现调用的,那么我们就从execute方法作为入口点来分析。

AsyncTask的execute方法:

 @MainThread public final AsyncTask 
    
      execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } 
    

这里调用了executeOnExecutor方法,参数sDefaultExecutor是一个线程池,作为默认线程池。

executeOnExecutor方法:

 @MainThread 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; } 
    
逻辑解析
  • 该方法在UI线程中执行。
  • 属性mStatus用来判断当前任务的状态,当任务正在运行或已经结束了,会抛出Error。
  • 设置mStatus的状态为Status.RUNNING。
  • 调用onPreExecute()方法,该方法通常在使用AsyncTask时,需要实现。
  • 把参数params保存在mWorker.mParams中。
  • 调用线程池,执行mFuture。

线程池执行的部分我们后面来分析,先来看这里有2个重要的属性mWorker和mFuture,它们起到了非常重要的作用,我们来看。

mWorker、mFuture

属性mWorker和mFuture是在AsyncTask的构造方法中初始化的,它们是后台线程控制逻辑的核心所在。

我们来看AsyncTask的构造方法:

 public AsyncTask() {//这里是在UI线程中执行的 this((Looper) null); } public AsyncTask(@Nullable Handler handler) { this(handler != null ? handler.getLooper() : null); } 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);//这里设置了线程的优先级为THREAD_PRIORITY_BACKGROUND //noinspection unchecked result = doInBackground(mParams);//这里调用了doInBackground方法 Binder.flushPendingCommands(); } catch (Throwable tr) { mCancelled.set(true); throw tr; } finally { postResult(result); //post结果给UI线程 } 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 static abstract class WorkerRunnable 
      
        implements Callable 
       
         { Params[] mParams; } 
        
       
      
    
逻辑解析:
  1. 首先初始化Handler对象,因为AsyncTask涉及到后台线程和UI线程之间的通信,它内部实现使用了Handler通信机制。
  2. mWorker属性赋值,这里的mWorker是一个WorkerRunnable对象,它是一个抽象类,继承自Callable接口,用于执行异步任务并获取线程执行结果。WorkerRunnable对象的call()方法是在后台线程中执行的。这里可以看到在call()方法中,调用了doInBackground方法。
  3. call()方法中设置了线程的优先级为THREAD_PRIORITY_BACKGROUND(后台线程优先级)。
  4. mFuture属性赋值,这里的mFuture是一个FutureTask对象,FutureTask可以用于线程任务的控制等逻辑,异步任务执行完成后,会回调done()方法。
  5. done()方法中,调用postResultIfNotInvoked方法,把任务结果返回给UI线程,这里执行的是非正常情况下的返回,正常执行结束会在mWorker的call方法中通过调用postResult(result)将结果返回给UI线程。

Handler处理

我们来看,后台任务处理完成后,会在WorkerRunnable的call方法中,调用postResult(result)将结果返回给UI线程,但如果后台任务执行之前被终止,则会把结果传递给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 
    
      (this, result)); message.sendToTarget(); return result; } 
    

postResult方法,创建一个message对象,把执行结果封装成一个AsyncTaskResult对象,通过Handler机制,将后台执行结果传递给UI线程(这里只考虑UI线程一种情况)。

串行执行的线程池实现

到了这里,Asynctask的执行部分、结果处理以及跨线程通信部分,都已经分析完成了,接下来我们来看后台线程是如何执行的,任务队列是如何实现的。

我们从上文知道,AsyncTask的execute方法中,直接使用了一个默认线程池sDefaultExecutor来负责后台线程的创建及执行。

回顾一下:

 @MainThread public final AsyncTask 
    
      execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } 
    

sDefaultExecutor的单线程队列的实现:

 private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); 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); } } } 
      
    

我们可以看到,sDefaultExecutor是SerialExecutor的一个实例对象。

SerialExecutor的属性:
后台任务队列的执行过程:
  1. 当第一次有任务进来时,先把任务添加到mTasks队列中,这时mActive == null,执行scheduleNext方法。
  2. scheduleNext方法中,从队列中取出队列头部的Runnale对象,并赋给mActive,最后把它交由THREAD_POOL_EXECUTOR线程池执行。
  3. 当第一个任务执行结束后,紧接着会调用scheduleNext方法执行下一个任务,以此类推……

串行执行的秘密:这里其实就实现了一个单线程的任务队列,所有AnsyncTask的任务,都会顺序的,单线程执行。它的实现原理其实就是,准备了一个可以扩容的先进先出的任务队列mTasks,所有的后台任务执行时,先进入队列中,然后调用scheduleNext执行,当前任务结束后,继续执行下一个任务,这样也就实现了串行的任务处理。

线程池THREAD_POOL_EXECUTOR

这里的线程池其实没有发挥线程池的作用,因为默认情况下,AsyncTask只会执行单个任务调用,因为在SerialExecutor中已经实现了任务的队列管理了,SerialExecutor会单个顺序的执行线程任务的调用。

THREAD_POOL_EXECUTOR的定义:
 public static final Executor THREAD_POOL_EXECUTOR; private static final int CORE_POOL_SIZE = 1; private static final int MAXIMUM_POOL_SIZE = 20; private static final int BACKUP_POOL_SIZE = 5; private static final int KEEP_ALIVE_SECONDS = 3; static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, new SynchronousQueue 
    
      (), sThreadFactory); threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy); THREAD_POOL_EXECUTOR = threadPoolExecutor; } 
    

THREAD_POOL_EXECUTOR其实是一个可并发的线程池,它的核心线程数是1,最大线程数是20。(注意,不同Android版本中的实现会有不同)

线程的创建:
 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()); } }; 

线程池是通过ThreadFactory对象来进行线程创建的,并且会为线程命名为”AsyncTask #” + mCount.getAndIncrement()。

AsyncTask并发的实现

在Android 3.0时,AsyncTask的任务执行改回了单一线程中顺序执行,那么我们如果想用AsyncTask实现并发任务,可以做到吗?答案是肯定的,我们来看如何实现。

我们来看AsyncTask的executeOnExecutor方法:

 public final AsyncTask 
    
      executeOnExecutor(Executor exec, Params... params) { …… exec.execute(mFuture); return this; } 
    
源码解析:

注意:虽然这里可以实现线程池的并发处理,但是并不建议这么做,如果想要实现并发的后台任务,推荐使用java.util.concurrent并发工具包中的Executor、ThreadPoolExecutor和FutureTask等。

AsyncTask的最佳实践


到了这里,我们已经深入了解了AsyncTask的使用及实现原理了,那么我们在开发时,有哪些是需要注意的呢?

AsyncTask使用时的一些要求:

  • AsyncTask类必须在UI线程中被加载(Android 4.1之后自动执行)。
  • AsyncTask实例对象,必须在UI线程中创建。
  • 必须在UI线程中调用execute()来运行任务。
  • 必须创建AsyncTask的子类,以它的子类形式来使用。
  • 必须在子类中至少实现doInBackground()回调方法,通常我们还会实现onPostExecute()方法。
  • 不要手动调用onPreExecute(), onPostExecute(Result), doInBackground(Params…), onProgressUpdate(Progress…)方法。
  • AsyncTask后台任务,只能执行一次,否则会抛出异常。
  • 整个进程中的AsyncTask线程池及任务队列都是同一个(自定义的除外),大量的调用AsyncTask执行后台任务会造成任务队列整体执行时间的变长,导致任务执行的时间不可控。
  • AsyncTask的默认线程优先级是Process.THREAD_PRIORITY_BACKGROUND(后台线程级别),优先级较低,分配的CPU资源会较少,不适合执行优先级较高的任务。

关于执行顺序及Android历史版本中的变更

AsyncTask后台任务的执行顺序是不可靠的,因为它在Android历史版本中经历了多次变更。在刚刚引入AsyncTask时,AsyncTask后台任务的执行,是在单一线程中顺序执行的;但是在Android 1.6中,AsyncTask的任务执行改成了可多线程并发执行;在Android 3.0时,AsyncTask的任务执行又改回了单一线程中顺序执行,以避免多线程造成的并发等问题。

如果我们想通过AsyncTask来并发执行后台任务,可以使用executeOnExecutor(java.util.concurrent.Executor, java.lang.Object[])方法,传递一个线程池来执行。

注意:AsyncTask在Android R(Android 11)中被标记为弃用状态,建议使用java.util.concurrent并发工具包来代替。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/223266.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午2:28
下一篇 2026年3月17日 下午2:29


相关推荐

  • 解决ubuntu16.04中codeblocks中文显示不全的问题[通俗易懂]

    解决ubuntu16.04中codeblocks中文显示不全的问题[通俗易懂]ubuntu16.04中安装中文库、中文输入法、搜狗输入法、解决终端无法显示中文的问题、安装字体(YaheiConsolas字体)、更换漂亮绚丽flatbulous主题。codeblock设置字体为:kacstdigital或centuryschoolbookl解决中文注释显示不全的问题,修改codeblocks丑陋的运行窗口为ubuntu16.04默认的终端。

    2022年7月26日
    20
  • cmd命令怎么切换盘符_cmd命令行怎么切换到d盘

    cmd命令怎么切换盘符_cmd命令行怎么切换到d盘用命令行,要从C盘切换到F盘,然后傻傻地输入了cdcd是在同一个盘下切换空间用的盘之间切换应该用:F:盘符名加一个冒号,大小写不限。

    2022年10月3日
    5
  • int(1)和int(10)_int char区别

    int(1)和int(10)_int char区别int(1)和int(11)是否有区别?

    2022年4月20日
    50
  • mysql中更改密码的首选语句_MySQL如何更改用户密码?(代码实例)「建议收藏」

    mysql中更改密码的首选语句_MySQL如何更改用户密码?(代码实例)「建议收藏」在MySQL中,可以使用3种不同的语句更改用户帐户密码:UPDATEstatementSETPASSWORDstatementALTERUSERstatement.但在更改帐户密码之前,应记住两件非常重要的事情:-要更改密码的用户帐户详细信息。-要更改密码的用户正在使用该应用程序,因为如果在不更改应用程序的连接字符串的情况下更改了密码,则该应用程序将无法连接到数据库服务器。现在让我们学习…

    2022年8月12日
    13
  • IDEA设置背景为自定义照片「建议收藏」

    IDEA设置背景为自定义照片「建议收藏」一分钟教你把女朋友的照片设置成IDEA的背景图片【建议收藏】1.为什么写这篇文章?2.操作方法2.1.步骤12.2.步骤22.3.快捷操作一2.4.快捷操作二1.为什么写这篇文章?事情是这样的,在2021年6月10日早上我在CSDN上发布了文章《你真的懂Java怎么输出HelloWorld吗?》。这篇文章就如同标题一样,讲的是Java输出HelloWorld时源码的实现原理,本身再正常不过的一篇文章,但没想到的是。。十几天过去了,我却收到如下的评论??!!大家居然对我的IDEA背

    2022年6月15日
    56
  • se3948_30.03.23

    se3948_30.03.23题目描述题解好仙的题啊考虑设交集大小至少为xxx的个数为axa_xax​,则ax=(xn)(22n−x−1)a_x=(_x^n)(2^{2^{n-x}}-1)ax​=(xn​)(22n−x−1)然后我们考虑构造容斥系数fxf_xfx​,使得ans=∑x=0nfxaxans=\sum_{x=0}^nf_xa_xans=∑x=0n​fx​ax​然后我们考虑到如果交集大小恰好为…

    2022年10月8日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号