<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          精品!Android 多線(xiàn)程技術(shù)哪家強(qiáng)?

          共 7248字,需瀏覽 15分鐘

           ·

          2020-07-31 23:42

          作者:qing的世界

          鏈接https://juejin.im/post/5d3374cee51d4556bb4cd469

          上一篇文章我介紹了一些使用安卓多線(xiàn)程框架們的一些誤區(qū),那既然已經(jīng)介紹了那么多坑,這一篇我就來(lái)詳細(xì)說(shuō)說(shuō)一些方案。同樣的,這些總結(jié)下來(lái)的方案都是我自己個(gè)人的心得體會(huì),本人水平有限,有什么不對(duì)或者意見(jiàn)不同的歡迎大家討論或者吐槽。


          Trade Off?


          今天我想先說(shuō)一個(gè)英文單詞,叫Trade Off。中文翻譯過(guò)來(lái)可以說(shuō)叫權(quán)衡,妥協(xié),但是這么干巴巴的翻譯可能不能體現(xiàn)這個(gè)詞的牛逼之處,我來(lái)舉個(gè)例子。比如迪麗熱巴和謝娜同時(shí)追求我,雖然迪麗熱巴顏值更高,但是考慮到謝娜在湖南臺(tái)的地位以及和她在一起之后能給我?guī)?lái)的曝光度,我選擇了謝娜。。。。(以上純屬段子)


          Anyway。。。這就是Trade Off,一個(gè)很艱難的選擇,但是最后人都是趨于自己的利益最大化做出最后的決定。Trade Off這個(gè)詞貫穿了軟件開(kāi)發(fā)的所有流程,在多線(xiàn)程的選擇下面也是有一樣的體現(xiàn)。


          谷歌官方在18年的IO大會(huì)上放了這么一張圖

          我先來(lái)翻譯翻譯這張圖:橫軸從左往右分別是Best-Effort(可以理解為盡力而為)還有Guaranteed Execution(保證執(zhí)行). 豎軸從上往下分別是Exact Timing(準(zhǔn)確的時(shí)間點(diǎn))還有Deferrable(可以被延遲).這張圖分別從在多線(xiàn)程下執(zhí)行的代碼的可執(zhí)行性和執(zhí)行時(shí)間來(lái)把框架分成了四個(gè)維度。


          其中我想先說(shuō)說(shuō)個(gè)人的理解:?對(duì)于安卓里面的里面的任何代碼,都逃不開(kāi)生命周期這個(gè)話(huà)題。因?yàn)榘沧康乃拇蠼M件有兩個(gè)都是有生命周期的,而且對(duì)于用戶(hù)來(lái)說(shuō),可見(jiàn)的Activity或者Fragment才是他們最關(guān)心app的部分。所以一段代碼,在保證沒(méi)有內(nèi)存泄漏的情況下,能不能在異步框架下執(zhí)行完畢,就得取決于代碼所在載體(Activity/Fragment)的生命周期了。比如上一期我們說(shuō)到的RxJava的例子:

          @Overrideprotected?void?onDestroy()?{    super.onDestroy();
          //onDestroy 里面對(duì)RxJava stream進(jìn)行unsubscribe,防止內(nèi)存泄漏????subscription.unsubscribe();}

          這段代碼有可能會(huì)阻止我們?cè)贠bservable里面的API進(jìn)行調(diào)用。


          那么在安卓的生命周期的背景下,這段代碼就是Best Effort,盡力而為了。能跑就跑,要是activity沒(méi)了,那就拉倒。。。

          所以把以上例子中的代碼換成圖中的ThreadPool想必你就理解了。


          那么Guaranteed Execution呢?很顯然在圖中是用Foreground service來(lái)做。不像Activity或者Fragment,Service雖然也有生命周期,但是他的生命周期不像前兩者是被用戶(hù)操控。Service的生命周期可以由開(kāi)發(fā)者來(lái)決定,因此我們可以使用Foreground service + ThreadPool,來(lái)保證代碼一定可以被執(zhí)行。用Foreground Service是因?yàn)锳ndroid在Oreo之后修改了Service的優(yōu)先級(jí),在app 進(jìn)入后臺(tái)idle超過(guò)一分鐘之后會(huì)自動(dòng)殺死任何后臺(tái)Service。但是,使用Foreground Service,要求開(kāi)發(fā)者一定要開(kāi)啟一個(gè)Notification。

          @Overridepublic?void?onCreate()?{    super.onCreate();
          startForeground(1, notification);????Log.d(TAG_FOREGROUND_SERVICE,?"My?foreground?service?onCreate().");}



          這下好了,雖然保證程序正常運(yùn)行了,我們的UX卻變了,你還得和設(shè)計(jì)獅們苦口婆心的解釋?zhuān)@都是安卓谷歌的鍋!我也不想有個(gè)突兀的圖標(biāo)出現(xiàn)在狀態(tài)欄里。。。我還記得我去年在修改我們產(chǎn)品下載音樂(lè)的Service時(shí)候,為了讓Service不被銷(xiāo)毀把Notification變成Foreground service 的notification,我們的產(chǎn)品經(jīng)理還跑來(lái)問(wèn)我為啥這個(gè)notification不能劃掉。。。。也是花了很長(zhǎng)時(shí)間來(lái)給產(chǎn)品經(jīng)理科普。


          你看這就是Trade Off,從盡力而為到想保證代碼必須運(yùn)行。中間有這么一個(gè)需要權(quán)衡的地方。


          那么咱又開(kāi)始琢磨了,既然Foreground Service這么蛋疼,能不能要一個(gè)可以保證執(zhí)行,但是不改變?cè)踑pp的UX的框架呢。


          當(dāng)當(dāng)當(dāng)當(dāng)!WorkManager閃亮登場(chǎng)。

          說(shuō)起這個(gè)框架就屌了。使用它可以輕松的實(shí)現(xiàn)異步任務(wù)的調(diào)度,運(yùn)行。當(dāng)然僅僅是普通的執(zhí)行異步任務(wù)好像沒(méi)那么吸引人,畢竟很多其他的優(yōu)秀異步框架也可以實(shí)現(xiàn)。我們看看官方的解釋:

          The?WorkManager?API?makes?it?easy?to?schedule?deferrable,?asynchronous?tasks?that?are?expected?to?run?even?if?the?app?exits?or?device?restarts.

          劃重點(diǎn),even if the app exits or device restarts,意思是即使app退出或者重啟,也可以保證你的異步任務(wù)完整的執(zhí)行完畢。這個(gè)就完美的解決了我們用Foreground Service或者ThreadPool的問(wèn)題,它既可以保證任務(wù)完整執(zhí)行,也不需要以為啟動(dòng)前臺(tái)服務(wù)而導(dǎo)致需要UX的改變!


          我這里就不詳細(xì)解釋W(xué)orkManager的實(shí)現(xiàn)細(xì)節(jié)和源碼了。我們直接以上次的youtube 取消訂閱的例子說(shuō)話(huà)(這個(gè)例子用kotlin因?yàn)槲覒械弥匦聦?xiě)一個(gè)java版本的了。。。)!我們先定義一個(gè)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()???????}????}}


          復(fù)制代碼Worker里面其實(shí)就是執(zhí)行我們的取消訂閱的API call。


          接著監(jiān)聽(tīng)我們?nèi)∠嗛喌某晒εc否

          //1. 創(chuàng)建我們Worker的實(shí)例并且開(kāi)始執(zhí)行!WorkManager.getInstance()          .enqueue(OneTimeWorkRequest.Builder(MakeSubscriptionWorker::class.java!!)??????????.addTag(MakeSubscriptionWorker::class.simpleName!!)??????????.build())//2.?把API?call的結(jié)果轉(zhuǎn)化成Jetpack里面的LiveData,并且開(kāi)始監(jiān)聽(tīng)結(jié)果WorkManager.getInstance()????.getWorkInfosByTagLiveData(MakeSubscriptionWorker::class.simpleName!!)????.observe(this,purchaseObservaer)
          ?//3.?如果用戶(hù)退出了Activity,那么停止監(jiān)聽(tīng)結(jié)果WorkManager.getInstance() .getWorkInfosByTagLiveData(MakeSubscriptionWorker::class.simpleName!!) .removeObserver(purchaseObservaer)


          重點(diǎn)在第三步,雖然我們停止監(jiān)聽(tīng)了,但是不代表這個(gè)異步任務(wù)會(huì)取消。它還會(huì)繼續(xù)執(zhí)行。


          可是這和我們用線(xiàn)程池+非匿名內(nèi)部類(lèi)Runnable好像沒(méi)啥本質(zhì)區(qū)別,畢竟在上面的例子里面,kotlin的內(nèi)部class本身就是靜態(tài)的。不存在內(nèi)存泄漏。


          回到開(kāi)頭我說(shuō)的,WorkManager可以保證任務(wù)一定執(zhí)行,即使你把a(bǔ)pp退出!

          WorkManager會(huì)把你的任務(wù)序執(zhí)行id和相關(guān)信息保存在一個(gè)數(shù)據(jù)庫(kù)中,在App重新打開(kāi)之后會(huì)根據(jù)你在任務(wù)中設(shè)置的限制(比如有的任務(wù)限制必須在Wifi下執(zhí)行,WorkManager提供這樣的API)來(lái)重新開(kāi)啟你未完成任務(wù)。


          也就是說(shuō),即使我們?cè)邳c(diǎn)擊取消訂閱之后馬上把App強(qiáng)行關(guān)閉,下一次打開(kāi)的時(shí)候WorkManager也可以重新啟動(dòng)這個(gè)任務(wù)!??!


          那。。。這么屌的功能為啥我們不馬上開(kāi)始使用呢????

          還記得我反復(fù)提到的Trade Off這個(gè)詞么,WorkManager也有它需要取舍的地方。

          首先官方雖然重點(diǎn)說(shuō)到了保證任務(wù)執(zhí)行,但同時(shí)也提到了:

          WorkManager?is?intended?for?tasks?that?are?deferrable—that?is,?not?required?to?run?immediately

          也就是說(shuō),WorkManager主要目的是為了那些允許/可以忍受延遲的異步任務(wù)而設(shè)計(jì)的。這個(gè)可以忍受延遲就很玩味了。有誰(shuí)會(huì)想要無(wú)目的的延遲自己想要運(yùn)行的異步任務(wù)的?這個(gè)問(wèn)題的答案其實(shí)也是安卓用戶(hù)一直關(guān)心的電池續(xù)航。


          安卓在經(jīng)歷了初期的大開(kāi)大方之后,開(kāi)始越來(lái)越關(guān)心用戶(hù)體驗(yàn)。既然App的開(kāi)發(fā)者不遵守游戲規(guī)則(沒(méi)錯(cuò)我說(shuō)的就是那些不要臉的xx保活app),那么谷歌就自己制定規(guī)則,在新的操作系統(tǒng)中,谷歌進(jìn)一步縮減后臺(tái)任務(wù)可以執(zhí)行的條件。


          具體限制(https://developer.android.com/guide/background/

          上圖中,簡(jiǎn)潔的來(lái)說(shuō),當(dāng)APP進(jìn)入后臺(tái)之后,異步任務(wù)被限制的很死。那么作為谷歌自己研制的WorkManager,一個(gè)號(hào)稱(chēng)app關(guān)掉之后還能重啟異步任務(wù)的這么吊炸天的框架當(dāng)然也要遵循這個(gè)規(guī)則。


          所以,所謂的延遲,并不是那么的嚇人,筆者親測(cè),在App還在前臺(tái)的時(shí)候執(zhí)行WorkManager,異步任務(wù)基本上還是馬上會(huì)進(jìn)入調(diào)度執(zhí)行的,但是當(dāng)app進(jìn)入后臺(tái)之后,WorkManager就會(huì)嘗試暫停任務(wù)。所以在我們上面的例子里面,WorkManager也是可以使用的。


          但是!Trade Off又來(lái)了。雖然WorkManager和Activity的生命周期無(wú)關(guān)了,但是卻和整個(gè)App的前后臺(tái)狀態(tài)相關(guān)了。app的退出可以暫停WorkManager里面的任務(wù),也就是說(shuō)控制他能否執(zhí)行的這個(gè)鑰匙,又從開(kāi)發(fā)者手中跑到用戶(hù)的手里了。。。。

          這說(shuō)了大半章節(jié)的WorkManager,怎么又繞回來(lái)了呢。說(shuō)了這么多,從ThreadPool到Foreground Service,再到WorkManager。我們好像每次都在解決一個(gè)問(wèn)題之后又遇到了新的問(wèn)題,好像沒(méi)有完美的方案。

          沒(méi)錯(cuò),這些就是Trade Off,權(quán)衡,軟件開(kāi)發(fā)本就沒(méi)有完美的答案,silver bullet只在殺吸血鬼的時(shí)候存在,軟件開(kāi)發(fā)?不存在的。。。

          復(fù)雜度的Trade Off?


          上面的篇幅我都在從谷歌官方的解釋?zhuān)簿褪菑膱?zhí)行時(shí)間,和能否保證任務(wù)完整執(zhí)行的維度來(lái)審視我們現(xiàn)有的解決方案。接下來(lái)我想從代碼的復(fù)雜角度來(lái)聊聊。


          我在2015年開(kāi)始接觸RxJava,剛開(kāi)始學(xué)習(xí)RxJava的時(shí)候的確有點(diǎn)難懂,尤其是flatMap這個(gè)操作符消耗了我整整一周的時(shí)間去消化。但是在越來(lái)越熟悉之后,我就漸漸的愛(ài)上了RxJava。那個(gè)時(shí)候我就覺(jué)得,函數(shù)式編程的操作符實(shí)在太屌了,酷炫的操作符疊在一起,簡(jiǎn)直是狂炫酷霸拽有沒(méi)有,加上團(tuán)隊(duì)中懂RxJava的人不多,大家有問(wèn)題都會(huì)找我,我的虛榮心也迅速膨脹到了月球。。。我記得當(dāng)時(shí)我在重構(gòu)一個(gè)app冷啟動(dòng)的任務(wù)調(diào)度的代碼。


          當(dāng)時(shí)任務(wù)的依賴(lài)圖大概長(zhǎng)這個(gè)樣子:

          當(dāng)我的隊(duì)友還在用LacthCoundown,焦頭爛額的時(shí)候。我輕松的用RxJava的mergeWith和ConcatMap解決了:

          B??.mergeWith(C)??.concatMap(E)??.concatMap(F)??.mergeWith(A            .concatMap(D))

          復(fù)制代碼啥也不說(shuō)了,屌就一個(gè)字!這更加堅(jiān)定了我RxJava就是世界上最好的異步任務(wù)框架的信念了。。。。


          直到我從創(chuàng)業(yè)公司來(lái)到Amazon Music,從一個(gè)只有3個(gè)人的安卓團(tuán)隊(duì)到了一個(gè)四個(gè)大組同時(shí)做一個(gè)產(chǎn)品的Org。我突然發(fā)現(xiàn),推廣RxJava的時(shí)間成本,還有團(tuán)隊(duì)學(xué)習(xí)的成本,已經(jīng)不能和以前在創(chuàng)業(yè)公司同日而語(yǔ)了。剛開(kāi)始的時(shí)候,每次看到隊(duì)友的code review我都喜歡插上一嘴:"you know , if we use RxJava here......", 直到團(tuán)隊(duì)的Senior有一次和我問(wèn)我:"Why RxJava is better?"的時(shí)候,我才意識(shí)到,我好像從來(lái)沒(méi)有系統(tǒng)性的總結(jié)過(guò)RxJava的優(yōu)缺點(diǎn),一時(shí)間有點(diǎn)語(yǔ)塞。我甚至發(fā)現(xiàn), 有時(shí)候一些簡(jiǎn)單的集合處理,用RxJava反而還顯得復(fù)雜了,況且RxJava的可讀性還是在基于團(tuán)隊(duì)都熟悉的條件下,更不說(shuō)因?yàn)閷W(xué)習(xí)成本導(dǎo)致產(chǎn)品迭代的減速了。那一刻,我仿佛丟了靈魂,我引以為傲的RxJava竟然被貶的一文不值!??!

          不對(duì)啊,我們RxJava明明對(duì)異步任務(wù)的組合,連接有強(qiáng)大的支持!mergeWith,concatMap,這么牛逼的操作符,不就是使用RxJava最好的理由么!我這樣和Senior反擊到。。。


          直到我看到了Coroutine。。。。


          Coroutine的操作符也可以同樣的實(shí)現(xiàn)上面的例子,還更容易理解和閱讀。。。

          如果想實(shí)現(xiàn)上面的四個(gè)異步任務(wù)同時(shí)執(zhí)行,下面的偽代碼可以輕松實(shí)現(xiàn)。

          //Dispatch?code?in?Main?thread?,?unless?we?swithc?to?antoehrvar job = GlobalScope.launch(Dispatchers.Main) {
          //force?task?A?B?C?D?to?run?in?IO?threadvar?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()


          復(fù)制代碼這一刻我崩潰了,這個(gè)世界上竟然還有除了RxJava之外的框架可以做到組合連接。


          也可能我高估了自己的預(yù)判能力,在學(xué)習(xí)WorkManager之后,我發(fā)現(xiàn),WorkManager也有同樣的功能。。。比如下面的串行執(zhí)行異步任務(wù)

          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


          這三個(gè)框架對(duì)異步任務(wù)的連接,合并等等邏輯操作從強(qiáng)大到功能有所局限整齊的排列著,但同樣的,實(shí)現(xiàn)的復(fù)雜度也從高到底排列。


          這又回到了我們開(kāi)頭講的Trade Off。怎么樣從團(tuán)隊(duì),代碼復(fù)雜度和功能的強(qiáng)大與否直接做權(quán)衡。


          總結(jié)一下


          寫(xiě)到最后我想稍微解釋一下,可能本身這兩篇文章有些許的標(biāo)題黨,號(hào)稱(chēng)最全的選型指南,這也是我想吸引眼球的一種方式,結(jié)果到最后也沒(méi)有給讀者一個(gè)結(jié)論到底用哪個(gè)框架,如果有讀者因?yàn)檫@個(gè)原因感覺(jué)被欺騙了,那在此我想說(shuō)一聲抱歉。不過(guò)我相信,在讀完這篇文章之后,你可能也會(huì)發(fā)現(xiàn)選型這個(gè)問(wèn)題需要先了解框架本身使用的Trade Off。不能因?yàn)槲蚁矚g,或者我覺(jué)得就輕易的做決定或者嘗試說(shuō)服你的反對(duì)者或者老板。如果我的文章可以讓你稍微對(duì)多線(xiàn)程做技術(shù)選型的時(shí)候能多做一丟丟的思考,我想我也就達(dá)到了我寫(xiě)這兩篇文章的初衷。

          瀏覽 26
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91成人视频18 | 国产99久久九九精品无码免费 | 夜夜嗨AV一区二区三区 | 91成人无码 | 777中国盗摄偷拍0000 |