更高級(jí)的 Android 啟動(dòng)任務(wù)調(diào)度庫(kù)

無標(biāo)題.png

在之前的文章中,我介紹了自研的 Android 啟動(dòng)任務(wù)調(diào)度工具 AndroidStartup思杯。近期郁妈,因?yàn)樵诮M件化項(xiàng)目中運(yùn)用該項(xiàng)目的需要带斑,我對(duì)這個(gè)庫(kù)做了一番升級(jí)。在最新的 2.2 版本中,我新增了一些特性匈睁。相比于目前市面上其他的啟動(dòng)任務(wù)調(diào)度庫(kù)卷哩,使其具備了更多的優(yōu)勢(shì)蛋辈。這里我只介紹下經(jīng)過新的版本迭代之后該項(xiàng)目與其他項(xiàng)目的不同點(diǎn)。對(duì)于其基礎(chǔ)的實(shí)現(xiàn)原理将谊,可以參考我之前的文章 《異步冷溶、非阻塞式 Android 啟動(dòng)任務(wù)調(diào)度庫(kù)》

1尊浓、支持多種線程模型

這是相對(duì)于 Jetpack 的啟動(dòng)任務(wù)庫(kù)的優(yōu)勢(shì)逞频,在指定任務(wù)的時(shí)候,你可以通過 ISchedulerJobthreadMode() 方法指定該任務(wù)執(zhí)行的線程栋齿,當(dāng)前支持主線程(ThreadMode.MAIN)和非主線程(ThreadMode.BACKGROUND)兩種情況苗胀。前者在主線程當(dāng)中執(zhí)行,后者在線程池當(dāng)中執(zhí)行瓦堵,同時(shí)基协,該庫(kù)還允許你自定義自己的線程池。關(guān)于這塊的實(shí)現(xiàn)原理可以參考之前的文章或者項(xiàng)目源碼菇用。

2澜驮、非阻塞的任務(wù)調(diào)度方式

在之前的文章中也提到了,如果說采用 CountDownLatch 等阻塞的方式來實(shí)現(xiàn)任務(wù)調(diào)度惋鸥,雖然不會(huì)占用主線程的 CPU杂穷,但是子線程會(huì)被阻塞鹅龄,一樣會(huì)導(dǎo)致 CPU 空轉(zhuǎn),影響程序執(zhí)行的性能亭畜,尤其啟動(dòng)的時(shí)候大量任務(wù)執(zhí)行時(shí)的情況扮休。所以,在這個(gè)庫(kù)的設(shè)計(jì)中拴鸵,我們使用了通知喚醒的方式進(jìn)行任務(wù)調(diào)度玷坠。也就是,

首先劲藐,它會(huì)將所有的需要執(zhí)行的任務(wù)收集起來八堡;然后,它會(huì)根據(jù)任務(wù)的依賴關(guān)系指定分發(fā)和調(diào)度任務(wù)的子任務(wù)聘芜;最后兄渺,當(dāng)當(dāng)前任務(wù)執(zhí)行完畢,該任務(wù)會(huì)通知所有的子任務(wù)按照順序執(zhí)行汰现。大致實(shí)現(xiàn)邏輯如下挂谍,

override fun execute() {
    val realJob = {
        // 1. Run the task if match given process.
        if (matcher.match(job.targetProcesses())) {
            job.run(context)
        }

        // 2. Then sort children task.
        children.sortBy { child -> -child.order() }

        // 3. No matter the task invoked in current process or not,
        // its children will be notified after that.
        children.forEach { it.notifyJobFinished(this) }
    }

    try {
        if (job.threadMode() == ThreadMode.MAIN) {
            // Cases for main thread.
            if (Thread.currentThread() == Looper.getMainLooper().thread) {
                realJob.invoke()
            } else {
                mainThreadHandler.post { realJob.invoke() }
            }
        } else {
            // Cases for background thread.
            executor.execute { realJob.invoke() }
        }
    } catch (e: Throwable) {
        throw SchedulerException(e)
    }
}

3、非 Class 的依賴方式

之前在本項(xiàng)目中瞎饲,以及其他的項(xiàng)目中可能采用了基于 Class 的形式進(jìn)行任務(wù)依賴口叙。這種使用方式存在一些問題,即在組件化開發(fā)的時(shí)候嗅战,Class 之間需要直接進(jìn)行引用妄田。這導(dǎo)致各個(gè)組件之間的強(qiáng)耦合。這顯然不是我們希望的驮捍。

所以疟呐,為了更好地支持組件化,在該庫(kù)的新版本中东且,我們?cè)试S通過 name() 方法執(zhí)行任務(wù)的名稱启具,以及通過 dependencies() 方法指定該任務(wù)依賴的其他任務(wù)的名稱。name() 默認(rèn)使用任務(wù) Class 的全限定名苇倡。這樣富纸,當(dāng)多個(gè)組件之間進(jìn)行相互依賴的時(shí)候囤踩,只需要通過字符串指定名稱而無需引用具體的類旨椒。

比如,一個(gè)任務(wù)在一個(gè)組件中定義如下堵漱,

@StartupJob class BlockingBackgroundJob : ISchedulerJob {

    override fun name(): String = "blocking"

    override fun threadMode(): ThreadMode = ThreadMode.BACKGROUND

    override fun dependencies(): List<String> = emptyList()

    override fun run(context: Context) {
        Thread.sleep(5_000L) // 5 seconds
        L.d("BlockingBackgroundJob done! ${Thread.currentThread()}")
        toast("BlockingBackgroundJob done!")
    }
}

在另一個(gè)組件中的另一個(gè)任務(wù)需要依賴上述任務(wù)的時(shí)候综慎,定義如下,

@StartupJob class SubModuleTask : ISchedulerJob {

    override fun dependencies(): List<String> = listOf("blocking")

    override fun run(context: Context) {
        Log.d("SubModuleTask", "runed ")
    }
}

這樣我們就實(shí)現(xiàn)組件化場(chǎng)景中的依賴關(guān)系了勤庐。

4示惊、支持任務(wù)的優(yōu)先級(jí)

在實(shí)際開發(fā)中好港,我們可能會(huì)遇到需要為所有的根任務(wù)或者一個(gè)任務(wù)的所有的子任務(wù)指定執(zhí)行的先后順序的場(chǎng)景∶追#或者在組件化中钧汹,存在依賴關(guān)系,但是我們希望某個(gè)根任務(wù)優(yōu)先執(zhí)行录择,但是不想為每個(gè)子任務(wù)都執(zhí)行依賴關(guān)系的時(shí)候拔莱,我們可以通過指定這個(gè)任務(wù)的優(yōu)先級(jí)為最高來使其最先被執(zhí)行。你可以通過 priority() 方法傳遞一個(gè) 0 到 100 的整數(shù)來指定任務(wù)的優(yōu)先級(jí)隘竭。

@StartupJob class TopPriorityJob : ISchedulerJob{

    override fun priority(): Int = 100

    override fun run(context: Context) {
        L.d("Top level job done!")
    }
}

優(yōu)先級(jí)局限于依賴關(guān)系相同的任務(wù)塘秦,所以是依賴關(guān)系的補(bǔ)充,不會(huì)造成歧義动看。

5尊剔、支持指定任務(wù)執(zhí)行的進(jìn)程,可自定義進(jìn)程匹配策略

如果我們的項(xiàng)目支持多進(jìn)程菱皆,而我們希望某些啟動(dòng)任務(wù)只在某個(gè)進(jìn)程中執(zhí)行而其他進(jìn)程不需要執(zhí)行须误,以此避免沒必要的任務(wù)來提升任務(wù)執(zhí)行的性能的時(shí)候,我們可以通過指定任務(wù)執(zhí)行的進(jìn)程來進(jìn)行優(yōu)化仇轻。你可以通過 targetProcesses() 傳遞一個(gè)進(jìn)程的列表來指定該任務(wù)執(zhí)行的所有進(jìn)程霹期。默認(rèn)列表為空,表示運(yùn)行在所有的進(jìn)程拯田。

對(duì)于進(jìn)程的匹配历造,我們提供了 IProcessMatcher 這個(gè)接口,

interface IProcessMatcher {
    fun match(target: List<String>): Boolean
}

你可以通過指定這個(gè)接口來自定義線程的匹配策略船庇。

6吭产、支持注解形式的組件化調(diào)用

在之前的版本中,通過 ContentProvider 的形式我們一樣可以實(shí)現(xiàn)所有組件內(nèi)任務(wù)的收集和調(diào)用鸭轮。但是使用 ContentProvider 存在一些不便之處臣淤,比如 ContentProvider 的初始化實(shí)際在 Application 的 attachBaseContext(),如果我們的任務(wù)中一些操作需要放到 Application 的 onCreate() 中執(zhí)行的時(shí)候窃爷,通過 ContentProvider 默認(rèn)裝載任務(wù)的調(diào)度方式就存在問題邑蒋。而通過基于注解 + APT的形式,我們可以隨意指定任務(wù)收集按厘、整理和執(zhí)行的時(shí)機(jī)医吊,靈活性更好。

為了支持組件化逮京,我們?cè)谥暗捻?xiàng)目上做了一些拓展卿堂。之前的項(xiàng)目雖然也是基于注解發(fā)現(xiàn)機(jī)制,但是在組件化的應(yīng)用中存在問題。在新的版本中草描,我們只是處理了組件化應(yīng)用場(chǎng)景中的問題览绿,但是使用方式上面完全兼容,只不過你需要為每個(gè)組件在 gradle.build 中增加一個(gè)行信息來指定組件的名稱(就像 ARouter 一樣)穗慕,

javaCompileOptions {
    annotationProcessorOptions {
        arguments = [STARTUP_MODULE_NAME: project.getName()]
    }
}

也就是說你還是通過 @StartupJob 注解將任務(wù)標(biāo)記為啟動(dòng)任務(wù)饿敲,然后通過

launchStartup(this) {
    scanAnnotations()
}

這行代碼啟動(dòng)掃描并執(zhí)行任務(wù)。

在新的版本中逛绵,所有生產(chǎn)的代碼會(huì)被統(tǒng)一放到包 me.shouheng.startup.hunter 下面诀蓉,然后通過 JobHunter$$組件名 的形式為每個(gè)組件生成自己的類,然后在掃描任務(wù)的時(shí)候通過加載這個(gè)包名之下的所有的代碼來找到所有要執(zhí)行的任務(wù)暑脆。如果你對(duì)組件化感興趣可以直接閱讀這塊的源碼實(shí)現(xiàn)渠啤。

總結(jié)

啟動(dòng)任務(wù)調(diào)度庫(kù)的設(shè)計(jì)不算復(fù)雜,但是我卻在之前的面試中兩次被問到如何設(shè)計(jì)添吗。這種類型的問題能很好地考察代碼設(shè)計(jì)能力沥曹。相信閱讀這個(gè)庫(kù)的代碼之后,此類的問題再也難不倒你碟联。如果你對(duì) APT+注解 的組件化實(shí)現(xiàn)方式等感興趣一樣可以閱讀這個(gè)庫(kù)的代碼妓美。

以上介紹了這個(gè)庫(kù)的一些特性和優(yōu)勢(shì),沒用過多地介紹其源碼實(shí)現(xiàn)鲤孵,感興趣的同學(xué)可以直接閱讀項(xiàng)目的源碼壶栋,相信你能夠從代碼中學(xué)到一些東西。對(duì)于示例項(xiàng)目普监,除了閱讀這個(gè)項(xiàng)目的示例贵试,還可以參考 Android-VMLib 這個(gè)項(xiàng)目。該項(xiàng)目地址:https://github.com/Shouheng88/AndroidStartup凯正。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毙玻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子廊散,更是在濱河造成了極大的恐慌桑滩,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件允睹,死亡現(xiàn)場(chǎng)離奇詭異运准,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)缭受,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門胁澳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人贯涎,你說我怎么就攤上這事听哭÷螅” “怎么了塘雳?”我有些...
    開封第一講書人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵陆盘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我败明,道長(zhǎng)隘马,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任妻顶,我火速辦了婚禮酸员,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘讳嘱。我一直安慰自己幔嗦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開白布沥潭。 她就那樣靜靜地躺著邀泉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪钝鸽。 梳的紋絲不亂的頭發(fā)上汇恤,一...
    開封第一講書人閱讀 52,196評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音拔恰,去河邊找鬼因谎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛颜懊,可吹牛的內(nèi)容都是我干的财岔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼河爹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼使鹅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起昌抠,我...
    開封第一講書人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤患朱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后炊苫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裁厅,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年侨艾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了执虹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唠梨,死狀恐怖袋励,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤茬故,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布盖灸,位于F島的核電站,受9級(jí)特大地震影響磺芭,放射性物質(zhì)發(fā)生泄漏赁炎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一钾腺、第九天 我趴在偏房一處隱蔽的房頂上張望徙垫。 院中可真熱鬧,春花似錦放棒、人聲如沸姻报。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逗抑。三九已至,卻和暖如春寒亥,著一層夾襖步出監(jiān)牢的瞬間邮府,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工溉奕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留褂傀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓加勤,卻偏偏與公主長(zhǎng)得像仙辟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鳄梅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容