当前位置: 首页 > 科技观察

AndroidBolts-更简单的线程调度和任务管理

时间:2023-03-12 13:50:48 科技观察

UsainStLeoBoltUsainStLeoBolt,牙买加短跑运动员,男子100米、男子200米和男子400米接力世界纪录保持者,同时也是上述三个项目的三连冠奥运金牌得主。螺栓可用于将完整的操作拆分为多个子任务。这些子任务可以自由拆分、组合和替换。每个任务都可以作为整个任务链的一部分在指定的线程中运行。同时可以从上游任务中获取任务结果,将当前任务的结果发布给下游任务,无需考虑线程间的交互。Bolts-AndroidAndroid下Bolts的实现Bolts-OC下Bolts的ObjC实现Bolts-SwiftSwift下Bolts的实现在主线程使用图片更新UI,同时返回当前UI状态json,在子线程将json数据保存到本地文件,完成后在主线程弹出提示,其中涉及到4个线程切换,后面的任务需要上一个任务完成后的返回值作为参数。用Thread+Handler实现,线程调度非常不灵活,代码可读性差,不美观,扩展性差,错误处理极其麻烦。Stringurl="http://www.baidu.com";Handlerhandler=newHandler(Looper.getMainLooper());newThread(()->{//下载Bitmapbitmap=downloadBitmap(url);handler.post(()->{//UpdateUIStringjson=updateUI(bitmap);newThread(()->{//WriteUIstatetostoragesaveUIState(json);//保存成功后,提示handler.post(()->toastMsg("savefinish."));}).start();});}).start();使用RxJava实现,线程调度非常灵活,链式调用,代码清晰,可扩展性好,有统一的异常处理机制,但是Rx是一个非常强大的库,如果只用于线程调度,Rx有点太重了.Observable.just(URL)//Download.map(this::downloadBitmap).subscribeOn(Schedulers.newThread())//更新UI.observeOn(AndroidSchedulers.mainThread()).map(this::updateUI)//存储UIstate.observeOn(Schedulers.io()).map(this::saveUIState)//显示提示.observeOn(AndroidSchedulers.mainThread()).subscribe(rst->toastMsg("saveto"+rst),//handleerrorThrowable::打印堆栈跟踪);使用bolts实现灵活的线程调度,链式调??用,代码清晰,扩展性好,统一的异常处理机制。虽然算子没有Rx那么多,但是优点是类库非常非常小,只有38KB。Task.forResult(URL)//Download.onSuccess(task->downloadBitmap(task.getResult()),Task.BACKGROUND_EXECUTOR)//更新UI.onSuccess(task->updateUI(task.getResult()),Task.UI_THREAD_EXECUTOR)//存储UI状态.onSuccess(task->saveUIState(task.getResult()),Task.BACKGROUND_EXECUTOR)//Prompt.onSuccess(task->toastMsg("saveto"+task.getResult()),Task.UI_THREAD_EXECUT//handleerror.continueWith(task->{if(task.isFaulted()){task.getError().printStackTrace();returnfalse;}returntrue;});线程调度器有4种执行线程,分配任务到指定的线程执行,分别是backgroud——后台线程池,可以并发执行任务。scheduled——单线程池,只有一个线程,主要用来执行延时操作。immediate——即时线程,如果线程调用栈小于15,然后在当前Thread执行,否则代理到后台。uiThread——为Android设计,使用Handler发送到主线程执行。backgroud是mainly用于后台并发执行多任务publicstaticfinalExecutorServiceBACKGROUND_EXECUTOR=BoltsExecutors.background();根据Android平台下CPU核数创建线程池,否则创建缓存线程池。background=!isAndroidRuntime()?java.util.concurrent.Executors.newCachedThreadPool():AndroidExecutors.newCachedThreadPool();scheduled主要用于任务间的延时操作,并不真正执行任务。scheduled=Executors.newSingleThreadScheduledExecutor();immediate主要用于简化不指定运行线程的方法。默认情况下,任务在当前线程中执行,ThreadLocal用于保存每个线程调用栈的深度。如果深度不超过15,则在当前线程Execute中执行,否则委托给backgroud执行。privatestaticfinalExecutorIMMEDIATE_EXECUTOR=BoltsExecutors.immediate();//关键方法@Overridepublicvoidexecute(Runnablecommand){intdepth=incrementDepth();try{if(depth<=MAX_DEPTH){command.run();}else{BoltsExecutors.background().execute(command)}}finally{decrementDepth();}}uiThread是专门为Android设计的,在主线程上执行任务。publicstaticfinalExecutorUI_T??HREAD_EXECUTOR=AndroidExecutors.uiThread();privatestaticclassUIThreadExecutorimplementsExecutor{@Overridepublicvoidexecute(Runnablecommand){newHandler(Looper.getMainLooper()).post(command);}}核心类Task,核心类,每个子任务都是一个Task,他们负责您需要执行的任务。每个Task有3种状态Result、Error和Cancel,分别代表成功、异常和取消。Continuation是一个接口,它就像一把锁,链接着子任务的各个环节,将独立的任务链接在一起。一个完整的任务链以Task-Continuation-Task-Continuation...的形式组成,并在各自的线程中依次执行。创建一个Task根据Task的3种状态创建一个简单的Task,并复用已有的任务对象Taskcancelled()使用delay方法延迟执行,创建一个TaskpublicstaticTaskdelay(longdelay)publicstaticTaskdelay(longdelay,CancellationTokencellationToken)使用whenAny方法执行多个任务,当任意一个任务返回结果时,保存结果publicstaticTask>whenAnyResult(Collection>tasks)publicstaticTask>whenAny(Collection>tasks)使用whenAll方法执行多个任务,所有任务执行完毕后返回结果publicstaticTaskwhenAll(Collection>tasks)publicstaticTask>whenAllResult(finalCollection>tasks)使用call方法在执行任务的同时,创建TaskpublicstaticTaskcall(finalCallablecallable,Executorexecutor,finalCancellationTokenct)链接子任务。使用continueWith方法链接子任务。如果之前的任务已经执行完,则立即执行当前任务,否则加入队列等待。publicTaskcontinuationWith(finalContinuationcontinuation,finalExecutorexecutor,finalCancellationTokenct)使用continueWithTask方法在当前任务之后链接另一个任务链。这种做法是为了满足将一些任务组合在一起的需要,作为一个公共的任务场景,他接受在当前执行的任务上追加另外一个完全独立的任务链。publicTaskcontinueWithTask(finalContinuation>continuation,finalExecutorexecutor,finalCancellationTokenct)使用continueWhile方法链接子任务,与continueWith的区别在于它有一个谓词表达式,只有当表达式是true,将添加子任务。这是为了在执行任务之前做一个拦截操作,也是为了不破坏链式调用的整体风格。publicTaskcontinueWhile(finalCallablepredicate,finalContinuation>continuation,finalExecutorexecutor,finalCancellationTokenct)使用onSuccess和onSuccessTask将单个任务链接到任务链。与continueWith的区别在于onSuccess方法,如果前面的任务失败了,后面的任务会直接失败,不会再执行,但是continueWith的子任务之间是没有联系的,即使前面的任务失败了,后续任务也将被执行。publicTaskonSuccess(finalContinuationcontinuation,Executorexecutor,finalCancellationTokenct)取消任务CancellationTokenSourcecancellationTokenSource=newCancellationTokenSource();CancellationTokentoken=cancellationTokenSource.getToken();Task.call((Callable)()->null,Task.BACKGROUND_EXECUTOR,token);//取消任务cancellationTokenSource.cancel();异常处理关于异常处理,整个机制,每个任务作为一个独立的单元,异常会被统一捕获,所以不需要单独处理任务中的方法。如果使用continueWith链接任务,那么当前任务的异常信息会保存在当前Task中,并在下行任务中处理,下行任务也可以不处理异常直接执行任务,则异常停止这里,不会再向下传递,也就是说只有下行任务才会知道当前任务的结果,是成功还是异常。当然,如果任务之间有关系,由于上行任务的异常很可能会导致当前任务的异常,所以会再次向下传递当前任务异常的信息,但是上行的异常任务到此结束。如果使用onSuccess等方法,如果上行任务异常,下行任务根本不会执行,直接将异常向下传递,直到处理完毕。任务的分离与组合我们可以将一个完整的操作细分为多个任务,每个任务遵循职责单一的原则,尽可能简单,这样可以在任务之间穿插新的任务,也可以将一些分离的任务组合在一起等等在。可扩展性我们可以在不影响上下行任务的情况下,在两个细分的任务之间添加一个新的操作,比如文章开头的需求中更新UI前保存Bitmap到本地。Task.forResult(URL)//Download.onSuccess(task->downloadBitmap(task.getResult()),Task.BACKGROUND_EXECUTOR)//保存到本地.onSuccess(task->saveBitmapToFile(task.getResult()),Task.Background_EXECUTOR)//UpdateUI.onSuccess(task->updateUI(task.getResult()),Task.UI_THREAD_EXECUTOR)...复用一些常用的操作可以单独拆分成新的任务,当需要类似操作的时候,这个部分功能可以重用。比如下载图片和更新UI、保存状态和弹出提示这两个功能,可以作为普通任务分离出来。//下载图片->更新UIpublicContinuation>downloadImageAndUpdateUI(){returntask->Task.call(()->downloadBitmap(task.getResult()),Task.BACKGROUND_EXECUTOR).continueWith(taskWithBitmap->updateUI(taskWithBitmap.getResult()),Task.UI_THREAD_EXECUTOR);}//保存状态->提示信息publicContinuation>saveStateAndToast(){returntask->Task.call(()->saveUIState(task.getResult()),Task.BACKGROUND_EXECUTOR).continueWith(taskWithPath->toastMsg("saveto"+taskWithPath.getResult()));}使用分离任务Task.forResult(URL).continueWithTask(downloadImageAndUpdateUI()).continueWithTask(saveStateAndToast())...Summary在Task中,有一个continuations,它是在当前任务之后附加的任务列表。当当前任务成功、异常或取消时,将执行列表中的后续任务。通常,我们使用链式调用来构建任务链,结果是一个没有分支的任务链。添加任务时:每添加一个Continuation,就会生成一个Task,添加到上游任务的continuations列表中,等待执行,同时返回当前Task,以便后续任务链接到当前任务。执行任务时:当前任务执行完后,可能有3个结果,都会保存在当前Task中,然后在continuations列表中查看后续任务,将当前Task作为参数传递给后续的链接任务,让后续的任务知道上行任务的结果。