Jetpack 之App Startup

在看Jetpack的官網(wǎng)時(shí)焚挠,發(fā)現(xiàn)在Jetpack中新加了一個(gè)App Startup組件膏萧,查了一下是最近和Hilt、Paging 3一起更新的蝌衔。

為什么需要App Startup呢榛泛?

在我們實(shí)際的開發(fā)工作中,一些第三方庫需要在App啟動(dòng)的時(shí)候初始化并不少見噩斟,比如WorkManager和LifeCycle在App啟動(dòng)時(shí)通過ContentProvider進(jìn)行初始化曹锨。通過ContentProvider,一旦App冷啟動(dòng)后剃允,在調(diào)用Application.onCreate( )之前沛简,ContentProvider就可以自動(dòng)執(zhí)行初始化齐鲤。

App Startup是什么?

Google定義:App Startup這個(gè)庫在App啟動(dòng)的時(shí)候提供了一個(gè)直接椒楣、高效的方式來初始化組件给郊。所有的library開發(fā)者和app開發(fā)者能夠使用App Startup這個(gè)組件來簡化啟動(dòng)順序并且顯示地設(shè)置初始化順序。通過App Startup這個(gè)組件撒顿,您無需為需要初始化的每個(gè)組件定義單獨(dú)的content providers丑罪,而是允許通過共享一個(gè)content provider來定義組件初始化器,從而提高應(yīng)用的啟動(dòng)速度.

由上可知凤壁,App Startup提供了一個(gè)ContentProvider來完成項(xiàng)目需要的一些組件的初始化吩屹,避免每個(gè)第三方的庫(比如友盟統(tǒng)計(jì)、埋點(diǎn)等)單獨(dú)通過ContentProvider 進(jìn)行初始化拧抖。我們可以通過通過App Startup這個(gè)組件將所有第三方需要在Application中初始化的一些庫都通過ContentProvider來初始化煤搜,有點(diǎn)將第三方庫初始化這個(gè)過程進(jìn)行了封裝的意味。

如何使用App Startup唧席?

我們可以通過定義組件初始化器完成組件的初始化擦盾,那么如何定義組件初始化器呢?Android為我們提供了Initializer接口淌哟,通過實(shí)現(xiàn)接口并實(shí)現(xiàn)接口中的兩個(gè)方法就可以實(shí)現(xiàn)組件初始化器的定義了迹卢。

我們來看下面這兩個(gè)方法:

  • create() : 包含了初始化組件,并且返回T的實(shí)例的所有必要操作徒仓;

  • dependencies() : 此方法返回一個(gè)初始化程序依賴的其他Initializer對(duì)象的列表腐碱。可以使用此方法來控制應(yīng)用程序啟動(dòng)時(shí)初始化的順序掉弛。

在沒有使用App Startup的時(shí)候症见,如何保證content providers的初始化順序呢?其實(shí)很簡單殃饿,在配置清單中將先啟動(dòng)的content provider的標(biāo)簽放在前面即可谋作。

下面看下如何進(jìn)行初始化。

通過App Startup來運(yùn)行依賴項(xiàng)的初始化有兩種方式:

  • 自動(dòng)初始化(automatic initialization)

  • 手動(dòng)初始化(manually initialization)

無論是自動(dòng)初始化還是手動(dòng)初始化都需要在app或者library中的build.gradle文件中添加如下依賴:

dependencies {
    implementation " androidx. startup:star tup- runtime:1.0.0-alpha01"
}
實(shí)現(xiàn)自動(dòng)初始化

假設(shè)APP依賴了WorkManager乎芳,并且需要在程序一開始啟動(dòng)時(shí)就初始化WorkManager遵蚜,定義一個(gè)WorkManagerInitializer類并且實(shí)現(xiàn)Initializer接口:

// Initializes WorkManager.
class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        val configuration = Configuration.Builder().build()
        WorkManager.nitialize(context, configuration)
        return WorkManager.getInstance(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        //No dependencies on other l ibraries.
        return emptyList()
    }
}

如圖中所示,dependencies()方法返回了一個(gè)空列表奈惑,意思是我WorkManager實(shí)例化誰也不需要依賴谬晕,我自己個(gè)就能行。

假設(shè)我們的應(yīng)用依賴了另一個(gè)叫做ExampleLogger的庫携取,這個(gè)庫依賴于WorkManager。這也就意味著帮孔,初始化這個(gè)庫必須先確保WorkManager的實(shí)例已經(jīng)被初始化了才可以雷滋。那么如何做呢不撑?我們看下面代碼:

// Initializes ExampleLogger ,
class ExampleLoggerInitializer : Initializer<ExampleLogger> {
    override fun create(context: Context): ExampleLogger {
        // WorkManager.getInstance() is non-null only after
        // WorkManager is initialized .
        return ExampleLogger(WorkManager.getInstance(context))
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        // Defines a dependency on Wor kManagerInitializer so it can be
        // initialized after WorkManager is initialized.
        return listOf(WorkManagerInitializer::class.java)
    }
}

代碼中定義了一個(gè)ExampleLoggerInitializer類并且實(shí)現(xiàn)了Initializer接口。這個(gè)時(shí)候我們看到dependencies()方法返回的就不是空列表了晤斩,而是包含了WorkManagerInitializer的一個(gè)列表焕檬,這樣ExampleLogger要想初始化,必須先初始化WorkManager澳泵。

提示:如果App中之前使用content providers來初始化應(yīng)用程序中的組件实愚,請(qǐng)確保使用App Startup時(shí)刪除這些content providers

App Startup包含了一個(gè)名為InitializationProvider的特殊的content provider,它用來找到并且調(diào)用你的組件初始化器兔辅。那么這個(gè)過程是什么樣的呢腊敲?

  • 首先,通過檢查InitializationProvider清單標(biāo)簽下的標(biāo)簽维苔,找到組件初始化器碰辅;

  • App Startup調(diào)用它找到的所有組件初始化器的dependencies()方法。

這就意味著如果想要讓App Startup找到組件初始化器介时,必須滿足下面的一個(gè)條件:

  • 組件初始化器在InitializationProvider清單標(biāo)簽下配置了相應(yīng)的標(biāo)簽没宾;

  • 組件初始化器在一個(gè)已被找到的組件初始化器的dependencies()方法中被列出;

從上面寫過的WorkManagerInitializer和ExampleLoggerInitializer這兩個(gè)例子中來說沸柔,為了確保初始化器能被實(shí)現(xiàn)循衰,需要在清單文件中配置如下代碼:

<provider
     android:name="androidx.startup.InitializationProvider"
     android:authorities="${applicationId}.androidx-startup"
     android:exported="false"
     tools:node="merge">
     <!-- This entry makes ExampleLoggerInitializer discoverable. -->
     <meta-data
         android:name="com.example.ExampleLoggerInitializer"
         android:value="androidx.startup" />
 </provider>

我們只配置了ExampleLoggerInitializer的標(biāo)簽,因?yàn)樗且蕾囉赪orkManager的褐澎,并且滿足了第二條:dependencies()方法中列出了它依賴于WorkManager会钝,如果ExampleLoggerInitializer能被找到,那么WorkManagerInitializer一定也能被找到乱凿。

tools:node="merge"屬性是為了確保清單合并工具可能造成的沖突問題

App Startup庫包含了一系列的lint規(guī)則顽素,通過這些規(guī)則,你能夠檢查是否正確定義了組件初始化器徒蟆。你可以在終端通過./gradlew :app:lintDebug命令執(zhí)行l(wèi)int檢查胁出。

實(shí)現(xiàn)手動(dòng)初始化

通常來說,當(dāng)你使用App Startup時(shí)段审,InitializationProvider對(duì)象就會(huì)使用AppInitializer在App啟動(dòng)時(shí)來自動(dòng)尋找并且運(yùn)行初始化器全蝶。然而,你也可以直接調(diào)用寺枉。AppInitializer來手動(dòng)初始化不需要在啟動(dòng)時(shí)就調(diào)用的組件初始化器抑淫。這個(gè)操作被稱作懶初始化,它能夠減少程序啟動(dòng)的時(shí)間姥闪。要想實(shí)現(xiàn)手動(dòng)初始化始苇,必須先禁止掉你想要手動(dòng)初始化的組件的自動(dòng)初始化功能。

為了禁止掉單個(gè)組件的自動(dòng)初始化功能筐喳,可以在清單文件中移除那個(gè)組件的標(biāo)簽催式,舉例來說函喉,我們想禁止ExampleLogger的自動(dòng)初始化:

<provider
     android:name="androidx.startup.InitializationProvider"
     android:authorities="${applicationId}.androidx-startup"
     android:exported="false"
     tools:node="merge">
     <!-- This entry makes ExampleLoggerInitializer discoverable. -->
     <meta-data
         android:name="com.example.ExampleLoggerInitializer"
         tools:node="remove" />
 </provider>

使用tools:node="remove"而不是直接移除這個(gè)標(biāo)簽是為了確保清單合并工具能夠移除所有合并文件的這個(gè)標(biāo)簽。

提示: 禁用組件的自動(dòng)初始化也會(huì)禁用該組件的依賴項(xiàng)的自動(dòng)初始化荣月,比如我禁用了ExampleLogger管呵,而ExampleLogger依賴了WorkManager,那么WorkManager也不會(huì)自動(dòng)初始化了哺窄。

我們現(xiàn)在知道如何禁止單個(gè)組件的自動(dòng)初始化捐下,那么如何禁止全部組件的自動(dòng)初始化,轉(zhuǎn)而手動(dòng)初始化呢萌业?

<provider
    android:name="androidx.startup.InitializationProvider "
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove" />

禁用自動(dòng)初始化后坷襟,你可以使用AppInitializer手動(dòng)初始化組件和它的依賴。

AppInitializer.getInstance(context)
    .initializeComponent(ExampleLoggerInitializer::class.java)

通過上述代碼咽白,App Startup也對(duì)WorkManager進(jìn)行了初始化啤握,因?yàn)镋xampleLogger依賴了WorkManager。

源碼分析

App Startup包中代碼并不多晶框,只有五個(gè)類

image.png

其中最核心的類就是InitializationProvider排抬,它是繼承了ContentProvider,這樣我們就懂了授段,在onCreate()方法中蹲蒲,可以看到它其實(shí)是調(diào)用了AppInitializer這個(gè)類中的discoverAndInitialize()方法,我們簡單看下這個(gè)代碼:

   @NonNull
   @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
    <T> T doInitialize(
            @NonNull Class<? extends Initializer<?>> component,
            @NonNull Set<Class<?>> initializing) {
        synchronized (sLock) {
            boolean isTracingEnabled = Trace.isEnabled();
            try {
                if (isTracingEnabled) {
                    // Use the simpleName here because section names would get too big otherwise.
                    Trace.beginSection(component.getSimpleName());
                }
                if (initializing.contains(component)) {
                    String message = String.format(
                            "Cannot initialize %s. Cycle detected.", component.getName()
                    );
                    throw new IllegalStateException(message);
                }
                Object result;
                if (!mInitialized.containsKey(component)) {
                    initializing.add(component);
                    try {
                        Object instance = component.getDeclaredConstructor().newInstance();
                        Initializer<?> initializer = (Initializer<?>) instance;
                        List<Class<? extends Initializer<?>>> dependencies =
                                initializer.dependencies();

                        if (!dependencies.isEmpty()) {
                            for (Class<? extends Initializer<?>> clazz : dependencies) {
                                if (!mInitialized.containsKey(clazz)) {
                                    doInitialize(clazz, initializing);
                                }
                            }
                        }
                        if (StartupLogger.DEBUG) {
                            StartupLogger.i(String.format("Initializing %s", component.getName()));
                        }
                        result = initializer.create(mContext);
                        if (StartupLogger.DEBUG) {
                            StartupLogger.i(String.format("Initialized %s", component.getName()));
                        }
                        initializing.remove(component);
                        mInitialized.put(component, result);
                    } catch (Throwable throwable) {
                        throw new StartupException(throwable);
                    }
                } else {
                    result = mInitialized.get(component);
                }
                return (T) result;
            } finally {
                Trace.endSection();
            }
        }
    }

代碼很簡單侵贵,就是解析出來metadata中的數(shù)據(jù)届搁,然后遍歷metadata拿到配置的初始化器,然后調(diào)用每個(gè)初始化器的初始化方法窍育,也就是doInitialize()方法卡睦。接下來再看下這個(gè)方法:

    @NonNull
    @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
    <T> T doInitialize(
            @NonNull Class<? extends Initializer<?>> component,
            @NonNull Set<Class<?>> initializing) {
        synchronized (sLock) {
            boolean isTracingEnabled = Trace.isEnabled();
            try {
                if (isTracingEnabled) {
                    // Use the simpleName here because section names would get too big otherwise.
                    Trace.beginSection(component.getSimpleName());
                }
                if (initializing.contains(component)) {
                    String message = String.format(
                            "Cannot initialize %s. Cycle detected.", component.getName()
                    );
                    throw new IllegalStateException(message);
                }
                Object result;
                if (!mInitialized.containsKey(component)) {
                    initializing.add(component);
                    try {
                        Object instance = component.getDeclaredConstructor().newInstance();
                        Initializer<?> initializer = (Initializer<?>) instance;
                        List<Class<? extends Initializer<?>>> dependencies =
                                initializer.dependencies();

                        if (!dependencies.isEmpty()) {
                            for (Class<? extends Initializer<?>> clazz : dependencies) {
                                if (!mInitialized.containsKey(clazz)) {
                                    doInitialize(clazz, initializing);
                                }
                            }
                        }
                        if (StartupLogger.DEBUG) {
                            StartupLogger.i(String.format("Initializing %s", component.getName()));
                        }
                        result = initializer.create(mContext);
                        if (StartupLogger.DEBUG) {
                            StartupLogger.i(String.format("Initialized %s", component.getName()));
                        }
                        initializing.remove(component);
                        mInitialized.put(component, result);
                    } catch (Throwable throwable) {
                        throw new StartupException(throwable);
                    }
                } else {
                    result = mInitialized.get(component);
                }
                return (T) result;
            } finally {
                Trace.endSection();
            }
        }
    }

可以看到在執(zhí)行初始化的時(shí)候,先判斷了是否有依賴項(xiàng)漱抓,有的話先執(zhí)行依賴項(xiàng)的初始化表锻。
掘金鏈接 Android Jetpack組件之App Startup

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市乞娄,隨后出現(xiàn)的幾起案子瞬逊,更是在濱河造成了極大的恐慌,老刑警劉巖仪或,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件确镊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡范删,警方通過查閱死者的電腦和手機(jī)蕾域,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來到旦,“玉大人束铭,你說我怎么就攤上這事廓块。” “怎么了契沫?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長昔汉。 經(jīng)常有香客問我懈万,道長,這世上最難降的妖魔是什么靶病? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任会通,我火速辦了婚禮,結(jié)果婚禮上娄周,老公的妹妹穿的比我還像新娘涕侈。我一直安慰自己,他們只是感情好煤辨,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布裳涛。 她就那樣靜靜地躺著,像睡著了一般众辨。 火紅的嫁衣襯著肌膚如雪端三。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天鹃彻,我揣著相機(jī)與錄音郊闯,去河邊找鬼。 笑死蛛株,一個(gè)胖子當(dāng)著我的面吹牛团赁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谨履,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼欢摄,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了屉符?” 一聲冷哼從身側(cè)響起剧浸,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎矗钟,沒想到半個(gè)月后唆香,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吨艇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年躬它,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片东涡。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冯吓,死狀恐怖倘待,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情组贺,我是刑警寧澤凸舵,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站失尖,受9級(jí)特大地震影響啊奄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掀潮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一菇夸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仪吧,春花似錦庄新、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至人断,卻和暖如春吭从,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恶迈。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國打工涩金, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人暇仲。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓步做,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奈附。 傳聞我的和親對(duì)象是個(gè)殘疾皇子全度,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359