游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

国庆在家,从0手撸一个依赖任务加载框架(有源码)

  前言
  我收回标题上的话,从0手撸一个框架一点也不轻松,需要考虑的地方比较多,一些实现和细节值得商榷,是一个比较大的挑战,有不足的地方欢迎大佬们提供意见
  依赖任务加载
  平时我们常常会使用各种第三方框架,如mmkv、glide、leakcanary等优秀的第三方库,大多数第三方库需要初始化后才能使用,因此会出现下面的代码:privatevoidinit{
  mmkv。init(context);
  glide。init(context);
  leakcanary。init(context);
  。。。。。。
  }
  如果不想让任务的初始化阻塞主线程太久,我们可以考虑通过异步的方式加载任务,直到最后一个必要任务加载完毕,开始进行对应的操作。
  如果部分任务是依赖关系,如下图任务A依赖任务B,单纯异步的方式的方式显然不能满足述求。
  我们通常会想到的解决办法有三类:
  将任务B写进任务A的末尾
  监听任务A加载成功的回调函数执行任务B
  通过volatile关键字卡住加载流程
  这样确实能够解决依赖任务的加载问题,但如果任务的数量和依赖关系更复杂呢?
  那如果是这样,你要怎么去处理?
  显然是有一种更通用的方法来解决这种场景,也就是下面会讲到的有向无环图。
  有向无环图的拓扑排序
  上面的依赖关系可以看成一种有向无环图(DirectedAcyclicGraph,DAG),有向可以理解,表现的是任务的依赖关系,而无环是必要的,因为如果任务A和任务B相互依赖,都需要等待对方的结束来开始,经典死锁套娃。
  我们可以通过拓扑排序将最后的线性执行关系呈现出来,什么是拓扑排序?
  将上面复杂依赖任务简单的分析一下,任务A前方没有依赖,因此我们可以将任务A的度记为0,任务B、C、E前方各有一个依赖关系,我们把度记为1,剩下的任务D前方由于有两个依赖关系,我们将度计为2;用一个任务队列储存度为0的任务,每当入列任务加载完毕,它对应依赖任务的度1,新的度为0的任务进队列。
  A入队列,A任务完成后,依赖A任务的任务B、C度1。
  这时任务B、C度都为0,都可以入队列,没有既定的顺序,我们选择入任务C,待C任务完成后,依赖C任务的D任务的度1。
  接着是任务B进去,B任务完成后,任务D、E的度1。
  最后是任务D、E其中的一个进去,随意选择,我们选择任务D。
  最后一个任务E。
  不考虑各个任务之间的耗时情况,依赖任务关系被拓扑排序成ACBDE,是不是发现依赖关系被解开了,排成了线性关系,这种将有向无环图拓扑成线性关系的方式被称为拓扑排序,拓扑结果根据所使用算法的不同而有所差异,这也是后面实现依赖任务加载框架的中心思想。
  手撸依赖任务加载框架
  定义IDAGTask类
  上面提到依赖任务的加载可以通过有向无环图的拓扑排序解决,我们开始用代码实现,先定义一个IDAGTask类:publicclassIDAGTask{
  }
  可能大家会疑问,为什么不用接口或者抽象类的思想去做这个基础类,后面解答这个疑惑。
  特殊的任务会存在加载线程限制,比如只能在主线程对这个任务进行加载,因此我们需要考虑这个任务是否可以同步。异步任务显然需要使用到线程池,定义IDAGTask类实现Runnable接口,方便后续丢进线程池。
  除此之外,之前讲到拓扑排序中任务有个度的概念,其实就是依赖关系的数量,在并发环境下为了保证依赖关系数量的线程可见性,这里我们使用AtomicInteger变量,通过CAS锁来保证依赖数量的实时正确性,因此IDAGTask类变成了这样:publicclassIDAGTaskimplementsRunnable{
  privatefinalbooleanmIsSyn;
  privatefinalAtomicIntegermAtomicInteger;
  booleangetIsAsync{
  returnmIsSyn;
  }
  voidaddRely{
  mAtomicInteger。incrementAndGet;
  }
  voiddeleteRely{
  mAtomicInteger。decrementAndGet;
  }
  intgetRely{
  returnmAtomicInteger。get;
  }
  Override
  publicvoidrun{
  }
  }
  回到之前为什么不用接口或者抽象类的方式来实现这个基础类,一方面为了后续将任务丢进线程池,IDAGTask实现了Runnable接口,接口的方式显然pass,另一方面抽象类的方式涉及到了另一个问题:
  抽象run方法,可以将IDAGTask任务的监听封装进去,比如startTask、completeDAGTask,如果我们继承IDATask,只需要将初始化部分单纯写进run方法就好了,非常优雅,但是有一种case,如果这个任务的初始化是用多线程实现的,我们调用完Task。init,马上执行completeDAGTask的监听其实是不对的
  基于上面的case,我选择了一种不优雅的实现方式,将startTask的监听写在run方法的开头,completeDAGTask的监听需要调用者自己添加,任务初始化是单线程实现写在run方法的末尾即可,任务初始化采用多线程实现,需要将completeDAGTask监听写进加载成功回调
  综上,run方法写进了startTask的回调,因此抽象失败,那么IDAGTask没有抽象方法,自然也不需要作为一个抽象类
  经过一些加工,最后IDATask实现如下:publicclassIDAGTaskimplementsRunnable{
  privatefinalbooleanmIsSyn;
  privatefinalAtomicIntegermAtomicInteger;
  privateIDAGCallBackmDAGCallBack;
  privatefinalSetmNextTaskSet;
  publicIDAGTask{
  this();
  }
  publicIDAGTask(booleanisSyn){
  this(,isSyn);
  }
  publicIDAGTask(Stringalias){
  this(alias,false);
  }
  publicIDAGTask(Stringalias,booleanIsSyn){
  mIsSynIsSyn;
  mAtomicIntegernewAtomicInteger;
  mDAGCallBacknewDAGCallBack(alias);
  mNextTaskSetnewHashSet;
  }
  booleangetIsAsync{
  returnmIsSyn;
  }
  voidaddRely{
  mAtomicInteger。incrementAndGet;
  }
  voiddeleteRely{
  mAtomicInteger。decrementAndGet;
  }
  intgetRely{
  returnmAtomicInteger。get;
  }
  voidaddNextDAGTask(IDAGTaskDAGTask){
  mNextTaskSet。add(DAGTask);
  }
  publicvoidsetDAGCallBack(IDAGCallBackDAGCallBack){
  this。mDAGCallBackDAGCallBack;
  }
  publicvoidcompleteDAGTask{
  for(IDAGTaskDAGTask:mNextTaskSet){
  DAGTask。deleteRely;
  }
  mDAGCallBack。onCompleteDAGTask;
  }
  Override
  publicvoidrun{
  mDAGCallBack。onStartDAGTask;
  }
  }
  定义DAGProject类
  IDAGTask的模板就被敲定了,接下来我们需要建立任务之间的关系:
  Set储存所有的任务,通过addDAGTask添加任务
  MapIDAGTask,Set储存所有的任务与其前置依赖关系,通过addDAGEdge添加任务依赖关系
  整体采用建造者模式构建DAGProject类
  于是DAGProject实现如下:publicclassDAGProject{
  privatefinalSetmTaskSet;
  privatefinalMapIDAGTask,SetmTaskMap;
  publicDAGProject(Builderbuilder){
  mTaskSetbuilder。mTaskSet;
  mTaskMapbuilder。mTaskMap;
  }
  SetgetDAGTaskSet{
  returnmTaskSet;
  }
  MapIDAGTask,SetgetDAGTaskMap{
  returnmTaskMap;
  }
  publicstaticclassBuilder{
  privatefinalSetmTaskSetnewHashSet;
  privatefinalMapIDAGTask,SetmTaskMapnewHashMap;
  publicBuilderaddDAGTask(IDAGTaskDAGTask){
  if(this。mTaskSet。contains(DAGTask)){
  thrownewIllegalArgumentException;
  }
  this。mTaskSet。add(DAGTask);
  returnthis;
  }
  publicBuilderaddDAGEdge(IDAGTaskDAGTask,IDAGTaskpreDAGTask){
  if(!this。mTaskSet。contains(DAGTask)!this。mTaskSet。contains(preDAGTask)){
  thrownewIllegalArgumentException;
  }
  SetpreDAGTaskSetthis。mTaskMap。get(DAGTask);
  if(preDAGTaskSet){
  preDAGTaskSetnewHashSet;
  this。mTaskMap。put(DAGTask,preDAGTaskSet);
  }
  if(preDAGTaskSet。contains(preDAGTask)){
  thrownewIllegalArgumentException;
  }
  DAGTask。addRely;
  preDAGTaskSet。add(preDAGTask);
  preDAGTask。addNextDAGTask(DAGTask);
  returnthis;
  }
  publicDAGProjectbuilder{
  returnnewDAGProject(this);
  }
  }
  }
  使用时,我们需要创建对应的IDAGTask,通过addDAGTask、addDAGEdge方法构建出对应有向无环图:ATaskanewATask;
  BTaskbnewBTask;
  CTaskcnewCTask;
  DTaskdnewDTask;
  ETaskenewETask;
  DAGProjectdagProjectnewDAGProject。Builder
  。addDAGTask(a)
  。addDAGTask(b)
  。addDAGTask(c)
  。addDAGTask(e)
  。addDAGTask(d)
  。addDAGEdge(b,a)
  。addDAGEdge(c,a)
  。addDAGEdge(d,b)
  。addDAGEdge(d,c)
  。addDAGEdge(e,b)
  。builder;
  表达任务依赖关系的DAGProject对象就通过建造者模式构建成功了。
  依赖任务加载的调度
  当多个任务构建成有向无环图的DAGProject后,我们先不着急丢进线程池,执行对应逻辑前先检测是否有环,这样我们可以在任务加载前抛出相互依赖的错误,大可不必等到执行至有环那一步才抛出。虽然有环可以靠输入者去保障,但是在一些小细节方面,我们要求输入者保证过于苛刻也过于差体验。
  将度为0的任务储存在tempTaskQueue
  while循环将任务取出,存在依赖关系则对应的任务度1,如果度为0,添加到resultTaskQueue
  判断最后的resultTaskQueue与之前储存任务的set个数是否相等,false则有环抛出异常publicclassDAGScheduler{
  privatevoidcheckCircle(SetTaskSet,MapIDAGTask,SetTaskMap){
  LinkedListresultTaskQueuenewLinkedList;
  LinkedListtempTaskQueuenewLinkedList;
  for(IDAGTaskDAGTask:tempTaskSet){
  if(tempTaskMap。get(DAGTask)){
  tempTaskQueue。add(DAGTask);
  }
  }
  while(!tempTaskQueue。isEmpty){
  IDAGTasktempDAGTasktempTaskQueue。pop;
  resultTaskQueue。add(tempDAGTask);
  for(IDAGTaskDAGTask:tempTaskMap。keySet){
  SettempDAGSettempTaskMap。get(DAGTask);
  if(tempDAGSet!tempDAGSet。contains(tempDAGTask)){
  tempDAGSet。remove(tempDAGTask);
  if(tempDAGSet。size0){
  tempTaskQueue。add(DAGTask);
  }
  }
  }
  }
  if(resultTaskQueue。size!tempTaskSet。size){
  thrownewIllegalArgumentException(相互依赖,玩屁啊,我不跑了!);
  }
  }
  }pre
  检测完环后,开始调度这些依赖任务,将度为0的任务加入阻塞队列,通过newSingleThreadExecutor开启一个线程不断去阻塞队列拿任务。
  判断拿出的任务属于主线程执行还是异步执行,主线程执行通过handler。post发送出去,异步执行通过线程池execute
  所有任务执行完毕,关闭线程池,结束遍历
  不断将度为0的任务加入阻塞队列publicclassDAGScheduler{
  privatevoidloop{
  for(IDAGTaskDAGTask:mTaskSet){
  if(DAGTask。getRely0){
  mTaskBlockingDeque。add(DAGTask);
  }
  }
  ExecutorServicesingleThreadExecutorExecutors。newSingleThreadExecutor;
  singleThreadExecutor。execute({
  for(;;){
  try{
  while(!mTaskBlockingDeque。isEmpty){
  IDAGTaskexecutedDAGTsk(IDAGTask)mTaskBlockingDeque。take;
  if(executedDAGTsk。getIsAsync){
  HandlerhandlernewHandler(getMainLooper);
  handler。post(executedDAGTsk);
  }else{
  mTaskThreadPool。execute(executedDAGTsk);
  }
  mTaskSet。remove(executedDAGTsk);
  }
  if(mTaskSet。isEmpty){
  singleThreadExecutor。shutdown;
  mTaskThreadPool。shutdown;
  return;
  }
  IteratoriteratormTaskSet。iterator;
  while(iterator。hasNext){
  IDAGTaskDAGTaskiterator。next;
  if(DAGTask。getRely0){
  mTaskBlockingDeque。put(DAGTask);
  iterator。remove;
  }
  }
  }catch(InterruptedExceptione){
  e。printStackTrace;
  }
  }
  });
  }
  }
  至此依赖任务的调度器搭建完毕,配合之前构建好的DAGProject,使用方法如下:
  DAGSchedulerdagSchedulernewDAGScheduler;dagScheduler。start(dagProject);
  使用方式
  第一步,对应build。gradle配置远程依赖,已经发布到mavencentral,不用担心jcenter弃用。
  implementationwork。lingling。dagtask:dagtsk:1。0。0
  第二步,继承IDAGTask类,在run方法中实现对应的初始化逻辑。publicclassATaskextendsIDAGTask{
  publicATask(Stringalias){
  super(alias);
  }
  Override
  publicvoidrun{
  super。run;
  try{
  模拟随机时间
  RandomrandomnewRandom;
  Thread。sleep(random。nextInt(1000));
  }catch(InterruptedExceptione){
  e。printStackTrace;
  }
  第三方框架内部使用同步加载
  completeDAGTask方法写在run方法末尾即可
  completeDAGTask;
  }
  第三方框架内部使用异步加载
  completeDAGTask方法需要写进成功回调
  onLibrarySuccess{
  completeDAGTask;
  }
  }
  tips:加载任务内部未开线程,completeDAGTask方法写在run方法的末尾,感知初始化结束;加载任务内部使用多线程,需要将completeDAGTask方法写进加载成功回调。
  第三步,根据任务的依赖关系构建DAGProject并执行。
  回首一开始出现的复杂依赖关系:
  我们模拟对应的任务,任务A、B、C、D、E,构建DAGProject如下:ATaskanewATask(ATask);
  BTaskbnewBTask(BTask);
  CTaskcnewCTask(CTask);
  DTaskdnewDTask(DTask);
  ETaskenewETask(ETask);
  DAGProjectdagProjectnewDAGProject。Builder
  。addDAGTask(b)
  。addDAGTask(c)
  。addDAGTask(a)
  。addDAGTask(d)
  。addDAGTask(e)
  。addDAGEdge(b,a)
  。addDAGEdge(c,a)
  。addDAGEdge(d,b)
  。addDAGEdge(d,c)
  。addDAGEdge(e,b)
  。builder;
  DAGSchedulerdagSchedulernewDAGScheduler;
  dagScheduler。start(dagProject);
  依赖任务执行结果如下:
  可以看到依赖任务被拆开成A、C、B、E、D的顺序进行执行。
  结语
  行文至此,总算凑到了结尾,1202年了,居然还有人在用java写客户端。
  框架实现整体很简单,但还是踩了很多坑,大到框架整体应该如何实现,小到设计模式应该如何使用、对外应该暴露什么方法、mavencentral如何上传等等各种细节问题,综上,这是一篇很青涩的文章。中途参考了很多大佬的文章思路和美好意见,但还是很不足,欢迎大佬们下场oneone指导。
  最后贴一下github链接:
  https:github。comLING0001DAGTask

浙江藏着一天空之城,景色堪比西藏,门票0元知道的人并不多天南地北大拜年头条百景中国年随着社会的进步,人们不仅仅在提高生活需求,更多的是将生活追求转为精神追求,而对于精神追求的方式有很多种,自由自在的旅行生活就是其中的一种,这时……小米9Pro5G配色及储存规格曝光梦之白和钛金黑IT之家9月23日消息根据mysmartprice的报道,他们已经在小米发布会之前获得了一些关于即将发布的小米9Pro5G和Redmi8手机的颜色和存储版本的独家信息。据……人民日报的这些金句我必须分享给大家1。人最高级的炫耀,是你这一生拒绝过什么。你能拒绝的东西里,藏着你不随波逐流的性格,和内心深处不为人知的骄傲。2。拿得定,见得透,事无不成,若十年未得真理,即十年无志,终……聚焦移动办公场景,华为MatePadPro智慧分屏体验随着移动设备多任务处理能力与消费者需求的逐渐提升,拥有优秀移动办公能力的平板电脑无疑成为硬件厂商眼中的新蓝海。11月下旬,在华为MatePad及全场景新品发布会上,华为消费者业……十年之后,HTCWildfire手机经典回归搭载紫光展锐虎贲2010年,HTC首次发布了Wildfire品牌,并在欧洲推出Wildfire系列智能手机,成为Android手机市场上的一款热销机型。十年之后,HTCWildfire品……网络套餐都有了,赵明微博表示荣耀5G手机V30下月发布IT之家10月31日消息今天国内运营商正式公布了5G套餐,华为荣耀业务部总裁赵明也公布了5G新机荣耀V30的消息,11月正式发布。根据之前的报道,近日一款型号为OXFAN……华为EMUI10滚屏翻译之背后的学问IT之家10月17日消息此前,华为官方发布一则短视频以介绍华为Mate30系列手机中接入的滚屏翻译功能。现在华为EMUI官方微信号撰文详解EMUI10滚屏翻译及其背后的OCR技……华为屏下摄像头手机专利曝光无边框设计加持IT之家6月2日消息今天,国外媒体LetsgoDigital报道了华为一款手机中包含了两项新专利,一项是全新的无边框手机设计,而另一项则为屏下摄像头。这两项专利由华为在2019……无线传声来了!华为Mate30Pro5G推送EMUI10。1感谢IT之家网友张大理石的线索投递!IT之家5月13日消息昨日晚间,华为向EMUI10。0新特性内测报名入选成功的用户推送了Mate30(4G)Mate30(5G)Mat……华为麒麟710A正式实现商业化量产中芯国际代工,14nm制程IT之家5月11日消息今年4月份,荣耀Play4T发布,采用6。39英寸魅眼屏,拥有4。5mm孔径,搭配后置指纹识别方案。在这款手机的内部搭载了华为海思麒麟710A处理器。……尘埃落定!辽宁队签约185cm后卫新星,杨鸣但愿赌对了,未来随着离队消息不断间出现,辽宁男篮的夺冠班底,也变得四分五裂了,张镇麟大概率征战NBA,不回归辽宁队了,而朱荣振也确认重返山东,刘志轩也离队了,高诗岩还在谈判当中,加上郭艾伦闹离……5万亿旧改来袭,20年的老房子又吃香了?两类人或从中受益从这两年拆迁的情况来看,那些梦想着一夜暴富的人可能要落空了,未来拆迁的面积大大减少,那些投资老破小的炒房客可能面临房子砸在手里。其实,去年住建部已经明确,未来旧改会全面接替棚改……
真正的强者低处渡己,高处渡人莎士比亚曾说:黑夜无论怎样悠长,白昼总会到来。人生短短数十载,往往是顺境与逆境同在,幸福与苦难同行。糊涂的人,在顺境中享乐,逆境中消磨,幸福中放纵,苦难中沉沦……被黑头困扰很久后,我成功混进了它们内部,拿到了这份一手情报又到了疯狂出油的夏天,旺盛的油脂就像是黑头的兴奋剂,那生长速度简直比变异的奥密克戎病毒还强上几分!为了制服黑头,人们尝试了多种方法:徒手挤压式(图源:网络)……欧足联官方将向去年欧冠决赛中所有持票的利物浦球迷退款据欧足联官方消息,欧足联将给所有去年去现场观看欧冠决赛且持有门票的利物浦球迷退款。此外,所有在去年欧冠决赛的开球时间前没有进入球场的球迷或者根本无法进入球场的球迷都将获得退款。……国乒男单半决赛林诗栋向鹏纷纷输球无缘决赛,女单包揽冠亚军!北京时间1月21日凌晨,wtt挑战赛多哈站男单半决赛正在上演,第一波半决赛由国乒小将林诗栋和韩国选手张禹珍上演,很遗憾的是林诗栋以大比分03张禹珍输掉了半决赛、无缘决赛,止步四……26年后再看马玉琴和李玉成,才明白他们结婚是最好的选择人们总说,真正的爱情是不分年龄,国籍甚至是性别的。现在年龄相差十分大的爱情或婚姻比比皆是,比如导演张纪中和他的80后小娇妻杜星霖,二人虽然相差31岁,可是同甘共苦的创业经……陕西一4A景区被拆,曾耗资3亿打造,如今规划成谜在我国众多的旅游城市中,陕西省凭借其丰富的旅游资源成为了人们节假日旅游的不二之选。大荔,古称同州,地处关中平原东部最开阔地带,是渭南市第一面积大县有中国枣乡之称。近年来,大荔县……键盘进风辅助散热,宏碁A7设计师笔记本发布首发价5499元IT之家5月10日消息,最近宏碁一口气发布了很多款笔记本,其中就包括了这款宏碁A7设计师笔记本。售价方面,这款笔记本目前只有I51240PRTX305016512G的版本,国行……初心未变魅族旗舰播放器20周年纪念款将于明天发布IT之家12月22日消息,魅友大会2022将于12月23日14:30在珠海举行,今日官方宣布,魅族旗舰播放器20周年纪念款将在此次大会上发布。我们知道,魅族最初就是做MP……萨尔瓦多区块链布局已见成效,8亿美元债券按时偿还萨尔瓦多作为区块链行业的顶流,但是传统主流媒体先前都对其债务(萨尔瓦多于1月份有8亿美元债券到期,另外还需支付对应利息)按时偿还表示悲观,但是现如今都对其债务按时清偿选择性失明……我们在CES2023上看到的最好的电动汽车等,飞行汽车真的要在CES上,我们看到了一辆非常性感的新款梅赛德斯奔驰,一辆看起来就像在Tron中的摩托车,还有一辆想成为你朋友的宝马。每年在CES上,世界上最大和最具创新性的公司都会为您……2022年夏季完成的所有英超联赛转会球员名单目标汇总了本赛季迄今为止完成的所有英超联赛转会足球转会是比赛中最令人兴奋的方面之一,球迷们对即将到来令人垂涎的新签约的球员前景垂涎三尺。英超联赛是世界上一些最富有的……杜锋看到辽宁队阵容,相信所有教练都会流口水虎扑01月01日讯202122赛季CBA常规赛第17轮,辽宁男篮11595广东男篮。赛后发布会,广东主帅杜锋坦言羡慕辽宁队深厚阵容。杜锋点评比赛:首秀感谢所有球迷支持,2……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网