上一篇文章我介绍了一些使用安卓多线程框架们的一些误区,那既然已经介绍了那么多坑,这一篇我就来详细说说一些方案。一样的,这些总结下来的方案都是我本身我的的心得体会,本人水平有限,有什么不对或者意见不一样的欢迎你们讨论或者吐槽。java
今天我想先说一个英文单词,叫Trade Off。 中文翻译过来能够说叫权衡,妥协,可是这么干巴巴的翻译可能不能体现这个词的牛逼之处,我来举个例子。好比迪丽热巴和谢娜同时追求我,虽然迪丽热巴颜值更高,可是考虑到谢娜在湖南台的地位以及和她在一块儿以后能给我带来的曝光度,我选择了谢娜。。。。(以上纯属段子)android
Anyway。。。这就是Trade Off,一个很艰难的选择,可是最后人都是趋于本身的利益最大化作出最后的决定。 Trade Off这个词贯穿了软件开发的全部流程,在多线程的选择下面也是有同样的体现。数据库
谷歌官方在18年的IO大会上放了这么一张图编程
我先来翻译翻译这张图。api
横轴从左往右分别是Best-Effort(能够理解为尽力而为)还有Guaranteed Execution(保证执行). 竖轴从上往下分别是Exact Timing(准确的时间点)还有Deferrable(能够被延迟). 这张图分别从在多线程下执行的代码的可执行性和执行时间来把框架分红了四个维度。其中我想先说说我的的理解: 对于安卓里面的里面的任何代码,都逃不开生命周期这个话题。由于安卓的四大组件有两个都是有生命周期的,并且对于用户来讲,可见的Activity或者Fragment才是他们最关心app的部分。因此一段代码,在保证没有内存泄漏的状况下,能不能在异步框架下执行完毕,就得取决于代码所在载体(Activity/Fragment)的生命周期了。好比上一期咱们说到的RxJava的例子:多线程
@Override
protected void onDestroy() {
super.onDestroy();
//onDestroy 里面对RxJava stream进行unsubscribe,防止内存泄漏
subscription.unsubscribe();
}
复制代码
这段代码有可能会阻止咱们在Observable里面的API进行调用。app
那么在安卓的生命周期的背景下,这段代码就是Best Effort
,尽力而为了。能跑就跑,要是activity没了,那就拉倒。。。框架
因此把以上例子中的代码换成图中的ThreadPool想必你就理解了。less
那么Guaranteed Execution呢
? 很显然在图中是用Foreground service来作。不像Activity或者Fragment,Service虽然也有生命周期,可是他的生命周期不像前二者是被用户操控。 Service的生命周期能够由开发者来决定,所以咱们可使用Foreground service + ThreadPool,来保证代码必定能够被执行。用Foreground Service是由于Android在Oreo以后修改了Service的优先级,在app 进入后台idle超过一分钟以后会自动杀死任何后台Service。可是,使用Foreground Service,要求开发者必定要开启一个Notification。异步
@Override
public void onCreate() {
super.onCreate();
startForeground(1, notification);
Log.d(TAG_FOREGROUND_SERVICE, "My foreground service onCreate().");
}
复制代码
这下好了,虽然保证程序正常运行了,咱们的UX却变了,你还得和设计狮们苦口婆心的解释,这都是安卓谷歌的锅!我也不想有个突兀的图标出如今状态栏里。。。我还记得我去年在修改咱们产品下载音乐的Service时候,为了让Service不被销毁把Notification变成Foreground service 的notification,咱们的产品经理还跑来问我为啥这个notification不能划掉。。。。也是花了很长时间来给产品经理科普。
你看这就是Trade Off,从尽力而为到想保证代码必须运行。中间有这么一个须要权衡的地方。
那么咱又开始琢磨了,既然Foreground Service这么蛋疼,能不能要一个能够保证执行,可是不改变咱app的UX的框架呢。
当当当当!WorkManager闪亮登场。
提及这个框架就屌了。使用它能够轻松的实现异步任务的调度,运行。固然仅仅是普通的执行异步任务好像没那么吸引人,毕竟不少其余的优秀异步框架也能够实现。咱们看看官方的解释: The WorkManager API makes it easy to schedule deferrable, asynchronous tasks that are expected to run even if the app exits or device restarts.
划重点,even if the app exits or device restarts
,意思是即便app退出或者重启,也能够保证你的异步任务完整的执行完毕。这个就完美的解决了咱们用Foreground Service或者ThreadPool的问题,它既能够保证任务完整执行,也不须要觉得启动前台服务而致使须要UX的改变!
我这里就不详细解释WorkManager的实现细节和源码了。咱们直接以上次的youtube 取消订阅的例子说话(这个例子用kotlin由于我懒得从新写一个java版本的了。。。)! 咱们先定义一个Worker:
class MakeSubscriptionWorker : Worker{
constructor(context: Context, parameterName: WorkerParameters):super(context,parameterName)
override fun doWork(): Result {
//unsubscribe 的API call在这里作
val api = API()
var response = api.unSubscribe()
if(response != null){
return Result.success(response)
}
else{
return Result.failure()
}
}
}
复制代码
Worker里面其实就是执行咱们的取消订阅的API call。
接着监听咱们取消订阅的成功与否
//1. 建立咱们Worker的实例而且开始执行!
WorkManager.getInstance().enqueue(OneTimeWorkRequest.Builder(MakeSubscriptionWorker::class.java!!)
.addTag(MakeSubscriptionWorker::class.simpleName!!)
.build())
//2. 把API call的结果转化成Jetpack里面的LiveData,而且开始监听结果
WorkManager.getInstance().getWorkInfosByTagLiveData(MakeSubscriptionWorker::class.simpleName!!).observe(this,purchaseObservaer)
//3. 若是用户退出了Activity,那么中止监听结果
WorkManager.getInstance().getWorkInfosByTagLiveData(MakeSubscriptionWorker::class.simpleName!!).removeObserver(purchaseObservaer)
复制代码
重点在第三步,虽然咱们中止监听了,可是不表明这个异步任务会取消。它还会继续执行。
但是这和咱们用线程池+非匿名内部类Runnable好像没啥本质区别,毕竟在上面的例子里面,kotlin的内部class自己就是静态的。不存在内存泄漏。
回到开头我说的,WorkManager能够保证任务必定执行,即便你把app退出!
WorkManager会把你的任务序执行id和相关信息保存在一个数据库中,在App从新打开以后会根据你在任务中设置的限制(好比有的任务限制必须在Wifi下执行,WorkManager提供这样的API)来从新开启你未完成任务。
也就是说,即便咱们在点击取消订阅以后立刻把App强行关闭,下一次打开的时候WorkManager也能够从新启动这个任务!!!
那。。。这么屌的功能为啥咱们不立刻开始使用呢????
还记得我反复提到的Trade Off这个词么,WorkManager也有它须要取舍的地方。
首先官方虽然重点说到了保证任务执行,但同时也提到了:
WorkManager is intended for tasks that are deferrable—that is, not required to run immediately
也就是说,WorkManager主要目的是为了那些容许/能够忍受延迟的异步任务而设计的。这个能够忍受延迟就很玩味了。有谁会想要无目的的延迟本身想要运行的异步任务的?这个问题的答案其实也是安卓用户一直关心的电池续航。
安卓在经历了初期的大开大方以后,开始愈来愈关心用户体验。既然App的开发者不遵照游戏规则(没错我说的就是那些不要脸的xx保活app),那么谷歌就本身制定规则,在新的操做系统中,谷歌进一步缩减后台任务能够执行的条件。
上图中,简洁的来讲,当APP进入后台以后,异步任务被限制的很死。那么做为谷歌本身研制的WorkManager,一个号称app关掉以后还能重启异步任务的这么吊炸天的框架固然也要遵循这个规则。
因此,所谓的延迟,并非那么的吓人,笔者亲测,在App还在前台的时候执行WorkManager,异步任务基本上仍是立刻会进入调度执行的,可是当app进入后台以后,WorkManager就会尝试暂停任务。因此在咱们上面的例子里面,WorkManager也是可使用的。
可是!Trade Off又来了。虽然WorkManager和Activity的生命周期无关了,可是却和整个App的先后台状态相关了。app的退出能够暂停WorkManager里面的任务,也就是说控制他可否执行的这个钥匙,又从开发者手中跑到用户的手里了。。。。
这说了大半章节的WorkManager,怎么又绕回来了呢。说了这么多,从ThreadPool到Foreground Service,再到WorkManager。咱们好像每次都在解决一个问题以后又遇到了新的问题,好像没有完美的方案。
没错,这些就是Trade Off,权衡,软件开发本就没有完美的答案,silver bullet只在杀吸血鬼的时候存在,软件开发?不存在的。。。
上面的篇幅我都在从谷歌官方的解释,也就是从执行时间,和可否保证任务完整执行的维度来审视咱们现有的解决方案。接下来我想从代码的复杂角度来聊聊。
我在2015年开始接触RxJava,刚开始学习RxJava的时候的确有点难懂,尤为是flatMap这个操做符消耗了我整整一周的时间去消化。可是在愈来愈熟悉以后,我就渐渐的爱上了RxJava。那个时候我就以为,函数式编程的操做符实在太屌了,酷炫的操做符叠在一块儿,简直是狂炫酷霸拽有没有,加上团队中懂RxJava的人很少,你们有问题都会找我,个人虚荣心也迅速膨胀到了月球。。。我记得当时我在重构一个app冷启动的任务调度的代码。
当时任务的依赖图大概长这个样子:
当个人队友还在用LacthCoundown,焦头烂额的时候。我轻松的用RxJava的mergeWith和ConcatMap解决了:
B
.mergeWith(C)
.concatMap(E)
.concatMap(F)
.mergeWith(A
.concatMap(D))
复制代码
啥也不说了,屌就一个字! 这更加坚决了我RxJava就是世界上最好的异步任务框架的信念了。。。。
直到我从创业公司来到Amazon Music,从一个只有3我的的安卓团队到了一个四个大组同时作一个产品的Org。我忽然发现,推广RxJava的时间成本,还有团队学习的成本,已经不能和之前在创业公司同日而语了。刚开始的时候,每次看到队友的code review我都喜欢插上一嘴:"you know , if we use RxJava here......", 直到团队的Senior有一次和我问我:"Why RxJava is better?"的时候,我才意识到,我好像历来没有系统性的总结过RxJava的优缺点,一时间有点语塞。我甚至发现, 有时候一些简单的集合处理,用RxJava反而还显得复杂了,何况RxJava的可读性仍是在基于团队都熟悉的条件下,更不说由于学习成本致使产品迭代的减速了。那一刻,我仿佛丢了灵魂,我引觉得傲的RxJava居然被贬的一文不值!!!
不对啊,咱们RxJava明明对异步任务的组合,链接有强大的支持!mergeWith,concatMap,这么牛逼的操做符,不就是使用RxJava最好的理由么!我这样和Senior反击到。。。
直到我看到了Coroutine。。。。
Coroutine的操做符也能够一样的实现上面的例子,还更容易理解和阅读。。。
若是想实现上面的四个异步任务同时执行,下面的伪代码能够轻松实现。
//Dispatch code in Main thread , unless we swithc to antoehr
var job = GlobalScope.launch(Dispatchers.Main) {
//force task A B C D to run in IO thread
var A = async (Dispatchers.IO){//do something in IO thread pool }
var B = async (Dispatchers.IO){//do something in IO thread pool }
var C = async (Dispatchers.IO){//do something in IO thread pool }
var D = async (Dispatchers.IO){//do something in IO thread pool }
//join 4 tasks (similar to merge concept in RxJava),
A.await()
B.await()
C.await()
D.await()
复制代码
这一刻我崩溃了,这个世界上居然还有除了RxJava以外的框架能够作到组合链接。
也可能我高估了本身的预判能力,在学习WorkManager以后,我发现,WorkManager也有一样的功能。。。 好比下面的串行执行异步任务
WorkManager.getInstance()
.beginWith(OneTimeWorkRequest.Builder(MakeSubscriptionWorker::class.java!!).build())
.then(OneTimeWorkRequest.Builder(MakeSubscriptionWorker::class.java!!).build())
.then(OneTimeWorkRequest.Builder(MakeSubscriptionWorker::class.java!!).build())
复制代码
RxJava -> Coroutine -> WorkManager
这三个框架对异步任务的链接,合并等等逻辑操做从强大到功能有所局限整齐的排列着,但一样的,实现的复杂度也从高到底排列。
这又回到了咱们开头讲的Trade Off。怎么样从团队,代码复杂度和功能的强大与否直接作权衡。
写到最后我想稍微解释一下,可能自己这两篇文章有些许的标题党,号称最全的选型指南,这也是我想吸引眼球的一种方式,结果到最后也没有给读者一个结论到底用哪一个框架,若是有读者由于这个缘由感受被欺骗了,那在此我想说一声抱歉。不过我相信,在读完这篇文章以后,你可能也会发现选型这个问题须要先了解框架自己使用的Trade Off。不能由于我喜欢,或者我以为就轻易的作决定或者尝试说服你的反对者或者老板。若是个人文章可让你稍微对多线程作技术选型的时候能多作一丢丢的思考,我想我也就达到了我写这两篇文章的初衷。