当前位置: 首页 > news >正文

AsyncTask实现原理

Android开发知识 onGithub

说到AsyncTask,它几乎能够用最简单的方式将操作异步执行,再呈现给UI线程。你不需要自己写一个线程,然后通过Handler去将结果返回给UI线程。只要简单的重写 onPreExecutedoInBackgroundonProgressUpdate,onPostExecute四个方法,然后调用execute方法,是不是超级简单。

可是,你了解AsyncTask是如何操作你的任务的吗?它是如何封装Handler将异步任务执行结果返回给UI线程的?使用AsyncTask有哪些需要注意的?本文从源码分析AsyncTask的工作原理,部分内容来自源码。

1.任务执行方式

目前的AsyncTask默认的任务处理是在单线程中顺序执行,之前有过一段时间可以在线程池中执行,不信你看execute方法的注释:

我简单的翻译一下,execute方法将队列中的任务在一个后台的单线程或线程池中执行。AsyncTask的第一个版本是顺序执行。在1.6(DONUT)版本后,改成多任务的线程池中执行。但在3.2(HONEYCOMB)后,为了避免一些线程同步的错误,又改回在单线程中执行。如果想在线程池中执行,可以这样:

new AsynTask().executeOn(AsyncTask.THREAD_POOL_EXECUTOR,"")
复制代码

显然这种方法,并不建议。

既然说到AsyncTask.THREAD_POOL_EXECUTOR,它是什么呢?

public static final Executor THREAD_POOL_EXECUTOR  = 
      new ThreadPoolExecutor(CORE_POOL_SIZE, 
          MAXIMUM_POOL_SIZE, 
          KEEP_ALIVE,                
          TimeUnit.SECONDS, 
          sPoolWorkQueue, 
          sThreadFactory);
复制代码

THREAD_POOL_EXECUTOR是一个线程池的执行器(有关线程池的可以参考 这篇文章。在这里你只要了解它是一个核心线程数量是CPU数+1,最大线程数量是2*CPU数量+1就可以了。

SerialExecutor

话说回来,单线程顺序执行是如何执行的?请看:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
复制代码

这个sDefaultExecutor是AsyncTask任务执行器。看过源码你会发现有这样一个方法:

/** @hide */
public static void setDefaultExecutor(Executor exec) {        
    sDefaultExecutor = exec;
}
复制代码

sDefaultExecutor是可以设置的,只不过你调用不了,被隐藏了(@hide)。

那么SERIAL_EXECUTOR是什么呢?它是一个SerialExecutor的实例。

可以看到,在execute中会调用offer方法会将Runnable r包装一下放到ArrayDeque队列里,包装的新Runnable保证原来的Runnable执行之后会去取队列里的下一个Runnable,从而不会导致中断。 scheduleNext做了什么呢?可以看到scheduleNext是从队列中取出Runnable然后交给THREAD_POOL_EXECUTOR执行。也就是说SerialExecutor只是将任务按先后顺序排列到队列中,真正执行任务的是THREAD_POOL_EXECUTOR

2.任务执行过程

在你调用execute的时候会这样:

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {    
  return executeOnExecutor(sDefaultExecutor, params);
}
复制代码

你看,最后还是会调用到executeOnExecutor,默认传了一个SERIAL_EXECUTOR。并且,看见那个@MainThread了吧,execute一定在主线程调用。

请看executeOnExecutor:

每个AsyncTask都有一个Status,代表这个AsyncTask的状态,Status是一个枚举变量,每一个状态在这个Task的生命周期里赋值一次,也就是这个Task一定会经历 PENDING -> RUNNING -> FINISHED 的过程。 PENDING代表Task还没有被执行,RUNNING代表当前任务正在执行,FINISHED代表的是onPostExecute方法已经执行完了,而不是doInBackground

/** 
  * Indicates the current status of the task. Each status will be set only once 
  * during the lifetime of a task. 
  */
 public enum Status {       
    PENDING,    
    RUNNING,   
    FINISHED,
}
复制代码

话说回到executeOnExecutor中,如果当前的Task的状态不是PENDING,那么就会抛出异常。也就是同一个Task,你只能execute一次,直到它的异步任务执行完成,你才可以再次调用他的execute方法,否则一定会报错。 然后调用onPreExecute方法,之后会提交给SERIAL_EXECUTOR执行。但是这个mWorker是什么?mFuture是什么?

mWorker

mWorkerWorkerRunnable的具体实现,实现Callable接口,相当于一个能够保存参数,返回结果的Runnable

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {    
  Params[] mParams;
}
复制代码

当你创建一个AsyncTask的时候就会创建mWorkermFutureTask

可以看到,mWorkercall方法主要的工作是设置call是否被调用,调用你重写的doInBackground方法,获得Result(这个Result的类型就是你声明AsyncTask时传入的类型),再将Result调用postResult方法返回。关于postResult请往下看。

mFuture

可以看到mFuture中有一个postResultIfNotInvoked(get());方法,通过get方法获得mWorker的执行结果,然后调用postResultIfNotInvoked方法,由于某些原因,mWorkercall可能没有执行,所以在postResultIfNotInvoked中能够保证postResult一定会执行一次,要不在mWorkercall中执行,要不在postResultIfNotInvoked中执行。

private void postResultIfNotInvoked(Result result) {    
      final boolean wasTaskInvoked = mTaskInvoked.get();    
      if (!wasTaskInvoked) {        
          postResult(result);    
      }
}
复制代码

那么这个postResult是干什么的?

private Result postResult(Result result) {        
    @SuppressWarnings("unchecked")    
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,  
                  new AsyncTaskResult<Result>(this, result));    
    message.sendToTarget();    
    return result;
}
复制代码

可以看到postResult实际上是获得了一个AsyncTask内部的一个Handler,将result包装在AsyncTaskResult中,并将它放在message发送给Handler。

那么AsyncTaskResult是如何封装的?

private static class AsyncTaskResult<Data> {    
      final AsyncTask mTask;    
      final Data[] mData;    
      AsyncTaskResult(AsyncTask task, Data... data) {        
          mTask = task;        
          mData = data;    
      }
}
复制代码

可以看到包含AsyncTask的实例(mTask)和数据(mData)。当将任务执行的结果返回时,mData保存的是Result,当更新进度的时候mData保存的是和Progress类型一样的数据。你可以往下看。

获取执行结果和更新执行的进度

先说一说Handler

每个AsyncTask都会获得一个InternalHandler的实例。可以看到,InternalHandler绑定到了主线程的Looper中(关于Looper与Handler的关系,可以参考这篇文章,所以你在异步线程中执行的结果最终都可以通过InternalHandler交给主线程处理。再看handlerMessage方法,获得AsyncTaskResult对象,如果传的是MESSAGE_POST_RESULT类型,就调用AsyncTask的finish方法(别忘了result.mTask其实就是当前的AsyncTask)。

finish做了什么?

private void finish(Result result) {    
      if (isCancelled()) {        
          onCancelled(result);    
      } else {        
          onPostExecute(result);    
       }    
      mStatus = Status.FINISHED;
}
复制代码

可以看到,判断你是否取消了任务,取消则优先执行onCancelled回调,否则执行onPostExecute,并更改Task的状态。

如果是一个MESSAGE_POST_PROGRESS,就会执行onProgressUpdate方法。那MESSAGE_POST_PROGRESS的信息是谁去发送的呢?请看:

protected final void publishProgress(Progress... values) {    
    if (!isCancelled()) {        
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,                
               new AsyncTaskResult<Progress>(this, values)).sendToTarget();    
    }
}
复制代码

也就是当你调用publishProgress的时候,会将传递的values包装成AsyncTaskResultAsyncTaskResult的mData会保存进度的数据,将message发送给handler。

有个方法需要说明一下,就是cancle方法。

public final boolean cancel(boolean mayInterruptIfRunning) {    
      mCancelled.set(true);    
      return mFuture.cancel(mayInterruptIfRunning);
}
复制代码

作用是设置被取消的状态,然后取消FutureTask的执行。当task已经执行完了,或已经被取消,或因为某些原因不能被取消,会返回false。如果任务已经执行,那么根据mayInterruptIfRunning决定是否打断(interrupt)当前正在执行Task的线程。 调用这个方法会在doInBackground返回后回调onCancelled方法,并且onPostExecute不会执行,所以当你需要取消Task的时候记得在doInBackground通过isCancelled检查返回值。


注意事项

1. 由于AsyncTask是单线程顺序执行的,所以不要用AsyncTask执行耗时太久的操作,如果有很多耗时太久的线程,最好使用线程池。

2. onPreExecuteonProgressUpdateonPostExecute都是在UI线程调用的,doInBackground在后台线程执行。

3. 调用cancel方法取消任务执行,这个时候onPostExecute就不会执行了,取而代之的是cancel方法,所以为了尽快的退出任务的执行,在doInBackground中调用isCancelled检查是否取消的状态。

4. 其他

  • AsyncTask类一定要在主线程加载
  • AsyncTask类的实例一定在主线程创建
  • execute方法一定在主线程调用
  • 不要主动调用onPreExecute等方法
  • 任务只能在完成前执行一次。

相关文章:

  • 最简单的无缝轮播
  • c中perror函数
  • 小身材超能量Oracle新一代数据库机帮助所有规模企业迈向云端
  • Confluence 6 注册单一小工具
  • Redis分布式锁的try-with-resources实现
  • shell脚本案例(五)利用nmap批量扫描存活主机
  • Echarts关于仪表盘
  • mysql 查询当天、本周,本月,上一个月的数据---https://www.cnblogs.com/benefitworld/p/5832897.html...
  • php实现求数组中出现次数超过一半的数字(isset($arr[$val]))(取不同数看剩)(排序取中)...
  • linux---文件颜色含义
  • echarts学习笔记 各图配置(折线图、圆环图、柱形图、折线面积图)
  • 如何查看一个网页特定效果的js代码(动画效果可js和css)(页面可以看到js的源代码)...
  • (转)创业的注意事项
  • Retrofit 用Soap协议访问WebService 详解
  • kunbernetes存储系统-基于NFS的PV服务
  • Django 博客开发教程 16 - 统计文章阅读量
  • HTTP请求重发
  • OSS Web直传 (文件图片)
  • Puppeteer:浏览器控制器
  • Python_OOP
  • Quartz初级教程
  • spring学习第二天
  • Unix命令
  • 百度小程序遇到的问题
  • 多线程 start 和 run 方法到底有什么区别?
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 技术胖1-4季视频复习— (看视频笔记)
  • 体验javascript之美-第五课 匿名函数自执行和闭包是一回事儿吗?
  • 微信端页面使用-webkit-box和绝对定位时,元素上移的问题
  • 阿里云移动端播放器高级功能介绍
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​iOS实时查看App运行日志
  • ​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​
  • #免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程
  • (10)Linux冯诺依曼结构操作系统的再次理解
  • (173)FPGA约束:单周期时序分析或默认时序分析
  • (2)Java 简介
  • (4)事件处理——(6)给.ready()回调函数传递一个参数(Passing an argument to the .ready() callback)...
  • (动态规划)5. 最长回文子串 java解决
  • (二)学习JVM —— 垃圾回收机制
  • (力扣记录)1448. 统计二叉树中好节点的数目
  • (十三)Java springcloud B2B2C o2o多用户商城 springcloud架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)...
  • (算法)N皇后问题
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • (已解决)什么是vue导航守卫
  • .bat文件调用java类的main方法
  • .Net 4.0并行库实用性演练
  • .NET Framework与.NET Framework SDK有什么不同?
  • .NET HttpWebRequest、WebClient、HttpClient
  • @Documented注解的作用
  • @ResponseBody
  • [ NOI 2001 ] 食物链
  • [ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2019-6116
  • [ 转载 ] SharePoint 资料
  • [《百万宝贝》观后]To be or not to be?