Android | App Startup 可能比你想象中要簡(jiǎn)單

點(diǎn)贊關(guān)注,不再迷路,你的支持對(duì)我意義重大!

?? Hi,我是丑丑。本文「Android 路線」| 導(dǎo)讀 —— 從零到無(wú)窮大 已收錄。這里有 Android 進(jìn)階成長(zhǎng)路線筆記 & 博客,歡迎跟著彭丑丑一起成長(zhǎng)。(聯(lián)系方式在 GitHub)

前言

  • 2020 年 10 月 28 日,JetPack | App Startup 1.0.0 終于迎來(lái)正式發(fā)布,正好最近在總結(jié)組件化架構(gòu)專題,所以也專門學(xué)習(xí)下 App Startup 的工作原理;
  • 在這篇文章里,我將帶你總結(jié) App Startup 的使用方法 & 實(shí)現(xiàn)原理 & 源碼分析。如果能幫上忙,請(qǐng)務(wù)必點(diǎn)贊加關(guān)注,這真的對(duì)我非常重要。

目錄


前置知識(shí)

這篇文章的內(nèi)容會(huì)涉及以下前置 / 相關(guān)知識(shí),貼心的我都幫你準(zhǔn)備好了,請(qǐng)享用~


1. 為什么要使用 App Startup?

這一節(jié),我們來(lái)討論為什么要使用 App Startup ,也就是 App Startup 解決了什么問(wèn)題。

在我之前寫(xiě)過(guò)的一篇文章里,我曾經(jīng)講過(guò)一種 基于 ContentProvider 啟動(dòng)機(jī)制實(shí)現(xiàn)的無(wú)侵入獲取 Contex 的方法:《Android | 使用 ContentProvider 無(wú)侵入獲取 Context》。在這里我簡(jiǎn)單復(fù)述一下:

  • 1、在二方庫(kù)或三方庫(kù)中,經(jīng)常需要獲取 Context 進(jìn)行初始化;
  • 2、因?yàn)?ContentProvider 會(huì)在應(yīng)用啟動(dòng)的時(shí)候初始化,所以很多庫(kù)都利用了 ContentProvider 的啟動(dòng)機(jī)制,在 Application#onCreate()中進(jìn)行初始化,例如 LeakCanary 2.4

AppWatcherInstaller.java

internal sealed class AppWatcherInstaller : ContentProvider() {

    internal class MainProcess : AppWatcherInstaller()

    internal class LeakCanaryProcess : AppWatcherInstaller()

    override fun onCreate(): Boolean {
        val application = context!!.applicationContext as Application
        AppWatcher.manualInstall(application)
        return true
    }

    // 其他方法直接 return 
}
  • 3、這種做法的風(fēng)險(xiǎn)是 ContentProvider 過(guò)多,啟動(dòng)過(guò)多的 ContentProvider 會(huì)增加應(yīng)用的啟動(dòng)時(shí)間。
  • 4、AppStartup 的做法是:合并所有用于初始化的ContentProvider ,減少創(chuàng)建 ContentProvider,并提供全局管理。

2. 使用步驟

這一節(jié),我們來(lái)總結(jié) App Startup 的使用步驟,依賴如下:

build.gradle

implementation "androidx.startup:startup-runtime:1.0.0"

2.1 為組件實(shí)現(xiàn) Initializer 接口

Initializer接口是 Startup 封裝的組件接口,用于指定組件的初始化邏輯和初始化順序(也就是依賴關(guān)系)。

Initializer.java

public interface Initializer<T> {

    1、初始化操作,返回的初始化結(jié)果將被緩存
    @NonNull
    T create(@NonNull Context context);

    2、依賴關(guān)系,返回值是一個(gè)依賴組件的列表
    @NonNull
    List<Class<? extends Initializer<?>>> dependencies();
}
  • 1、create(...)初始化操作: 返回的初始化結(jié)果將被緩存,其中context參數(shù)是 Application;
  • 2、dependencies()依賴關(guān)系: 返回值是一個(gè)依賴組件的列表,如果不需要依賴于其它組件,返回一個(gè)空列表。App Startup 在初始化當(dāng)前組件時(shí),會(huì)保證所依賴的組件已經(jīng)完成初始化。

2.2 自動(dòng)初始化

前面提到,App Startup 合并所有用于初始化的 ContentProvider,合并后的 ContentProvider 就是 InitializationProvider,我們需要在AndroidManifest中進(jìn)行聲明,例如:

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

要點(diǎn)如下:

  • 1、組件名必須是androidx.startup.InitializationProvider;
  • 2、需要聲明android:exported="false",以限制其他應(yīng)用訪問(wèn)此組件;
  • 3、要求android:authorities在整個(gè)手機(jī)唯一,通常使用${applicationId}作為前綴;
  • 4、需要聲明tools:node="merge",確保manifest merger tool能夠正確解析沖突的節(jié)點(diǎn);
  • 5、meta-data name為組件的 Initializer 實(shí)現(xiàn)類全限定名,valueandroidx.startup。

提示: 為什么要將androidx.startup設(shè)置為value,而不是name?因?yàn)殒I值對(duì)中,name是唯一的,而value是允許重復(fù)的。

關(guān)于AndroidManifest中聲明組件后,App Startup 是如何自動(dòng)執(zhí)行初始化的,我在 第 3 節(jié)說(shuō)

2.3 手動(dòng)初始化

在組件需要進(jìn)行懶加載時(shí)(耗時(shí)任務(wù)),可以進(jìn)行手動(dòng)初始化。需要手動(dòng)初始化的 Initializer 不需要在AndroidManifest中進(jìn)行聲明,也不應(yīng)該被其它組件依賴。調(diào)用以下方即可進(jìn)行手動(dòng)初始化:

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

需要注意的是,App Startup 中會(huì)緩存初始化后的結(jié)果,重復(fù)調(diào)用initializeComponent()不會(huì)導(dǎo)致重復(fù)初始化。關(guān)于 App Startup 手動(dòng)執(zhí)行初始化部分的源碼分析,我在 第 3 節(jié)說(shuō)。

2.4 取消自動(dòng)初始化

假如有些庫(kù)已經(jīng)使用 第 2.2 節(jié) 的方法配置了自動(dòng)初始化,而我們又希望進(jìn)行懶加載時(shí),就需要利用manifest merger tool的合并規(guī)則來(lái)移除這個(gè)庫(kù)對(duì)應(yīng)的 Initializer。具體如下:

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

2.5 禁止自動(dòng)初始化

假如需要禁止 App Startup 自動(dòng)初始化,同樣也需要利用manifest merger tool的合并規(guī)則:

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

3. 源碼分析

3.1 InitializationProvider 分析

前面我們提到,在AndroidManifest文件中配置的組件名必須為androidx.startup.InitializationProvider,現(xiàn)在我們來(lái)看這個(gè)類的源碼:

InitializationProvider.java

已簡(jiǎn)化

public final class InitializationProvider extends ContentProvider {

    @Override
    public boolean onCreate() {
        Context context = getContext();
        if (context != null) {
            初始化
            AppInitializer.getInstance(context).discoverAndInitialize();
        } else {
            throw new StartupException("Context cannot be null");
        }
        return true;
    }


    @Override
    public Cursor query(...) {
        throw new IllegalStateException("Not allowed.");
    }

    @Override
    public String getType(...) {
        throw new IllegalStateException("Not allowed.");
    }

    @Nullable
    @Override
    public Uri insert(...) {
        throw new IllegalStateException("Not allowed.");
    }

    @Override
    public int delete(...) {
        throw new IllegalStateException("Not allowed.");
    }

    @Override
    public int update(...) {
        throw new IllegalStateException("Not allowed.");
    }
}

可以看到:

  • 1、InitializationProvider其實(shí)也是利用了 ContentProvider 的啟動(dòng)機(jī)制,在ContentProvider#onCreate(...)中執(zhí)行初始化;
  • 2、由于 ContentProvider 的其他方法是沒(méi)有意義的,所以都拋出了IllegalStateException。

3.2 自動(dòng)初始化源碼分析

從一節(jié)可以看到,App Startup 在 ContentProvider 中調(diào)用了AppInitializer#discoverAndInitialize()執(zhí)行自動(dòng)初始化。AppInitializer是 App StartUp 框架的核心類,整個(gè) App Startup 框架的代碼其實(shí)非常少,其中很大部分核心代碼都在 AppInitializer 類中。

AppInitializer.java

final Set<Class<? extends Initializer<?>>> mDiscovered;

已簡(jiǎn)化
void discoverAndInitialize() {
    1、獲取 androidx.startup.InitializationProvider 組件信息
    ComponentName provider = new ComponentName(mContext.getPackageName(), InitializationProvider.class.getName());
    ProviderInfo providerInfo = mContext.getPackageManager().getProviderInfo(provider, GET_META_DATA);

    2、androidx.startup 字符串
    String startup = mContext.getString(R.string.androidx_startup);

    3、獲取組件信息中的 meta-data 數(shù)據(jù)
    Bundle metadata = providerInfo.metaData;
    
    4、遍歷 meta-data 數(shù)據(jù)
    if (metadata != null) {
        Set<Class<?>> initializing = new HashSet<>();
        Set<String> keys = metadata.keySet();
        for (String key : keys) {
            String value = metadata.getString(key, null);

            4.1 判斷 meta-data 數(shù)據(jù)中,value 為 androidx.startup 的鍵值對(duì)
            if (startup.equals(value)) {
                Class<?> clazz = Class.forName(key);

                4.2 檢查指定的類是 Initializer 接口的實(shí)現(xiàn)類
                if (Initializer.class.isAssignableFrom(clazz)) {
                    Class<? extends Initializer<?>> component = (Class<? extends Initializer<?>>) clazz;

                    4.3 將 Class 添加到 mDiscovered Set 中
                    mDiscovered.add(component);

                    4.4 初始化此組件
                    doInitialize(component, initializing);
                }
            }
        }
    }
}

-> 4.3 mDiscovered 用于判斷組件是否已經(jīng)自動(dòng)啟動(dòng)
public boolean isEagerlyInitialized(@NonNull Class<? extends Initializer<?>> component) {
    return mDiscovered.contains(component);
}

上面的代碼已經(jīng)非常簡(jiǎn)化了,主要關(guān)注以下幾點(diǎn):

  • 1、獲取androidx.startup.InitializationProvider組件信息(在各個(gè) Module 中聲明的組件信息,會(huì)在manifest merger tool的處理下合并);
  • 2、androidx.startup字符串
  • 3、獲取組件信息中的 meta-data 數(shù)據(jù)
  • 4、遍歷 meta-data 數(shù)據(jù)
    • 4.1 判斷 meta-data 數(shù)據(jù)中,value 為 androidx.startup 的鍵值對(duì)
    • 4.2 檢查指定的類是 Initializer 接口的實(shí)現(xiàn)類
    • 4.3 將 Class 添加到 mDiscovered Set 中,這將用于后續(xù) 判斷組件是否已經(jīng)自動(dòng)啟動(dòng)
    • 4.4 初始化此組件

AppInitializer.java

private static final Object sLock = new Object();

緩存每個(gè)組件的初始化結(jié)果
final Map<Class<?>, Object> mInitialized;

-> 4.4 初始化此組件
已簡(jiǎn)化
<T> T doInitialize(Class<? extends Initializer<?>> component, Set<Class<?>> initializing) {
    1、對(duì) sLock 加鎖,我后文再說(shuō)。

    Object result;
    
    2、判斷 initializing 中存在當(dāng)前組件,說(shuō)明存在循環(huán)依賴
    if (initializing.contains(component)) {
        String message = String.format("Cannot initialize %s. Cycle detected.", component.getName());
        throw new IllegalStateException(message);
    }

    3、檢查當(dāng)前組件是否已初始化
    if (!mInitialized.containsKey(component)) {
        3.1 當(dāng)前組件未初始化

        3.1.1 記錄正在初始化
        initializing.add(component);

        3.1.2 通過(guò)反射實(shí)例化 Initializer 接口實(shí)現(xiàn)類
        Object instance = component.getDeclaredConstructor().newInstance();
        Initializer<?> initializer = (Initializer<?>) instance;

        3.1.3 遍歷所依賴的組件
        List<Class<? extends Initializer<?>>> dependencies = initializer.dependencies();
        if (!dependencies.isEmpty()) {
            for (Class<? extends Initializer<?>> clazz : dependencies) {
                
                如果所依賴的組件未初始化,遞歸執(zhí)行初始化
                if (!mInitialized.containsKey(clazz)) {
                    doInitialize(clazz, initializing); 注意:這里將 initializing 作為參數(shù)傳入
                }
            }
        }
        
        3.1.4 (到這里,所依賴的組件已經(jīng)初始化完成)初始化當(dāng)前組件
        result = initializer.create(mContext);
        
        3.1.5 移除正在初始化記錄
        initializing.remove(component);

        3.1.6 緩存初始化結(jié)果
        mInitialized.put(component, result);
    } else {
        3.2 當(dāng)前組件已經(jīng)初始化,直接返回
        result = mInitialized.get(component);
    }
     return (T) result;
}

上面的代碼已經(jīng)非常簡(jiǎn)化了,主要關(guān)注以下幾點(diǎn):

  • 1、對(duì) sLock 加鎖,我后文再說(shuō)。
  • 2、判斷 initializing 中存在當(dāng)前組件,說(shuō)明存在循環(huán)依賴(這是因?yàn)檫f歸初始化所依賴的組件時(shí),會(huì)將 initializing 作為參數(shù)傳入,如果 initializing 中存在當(dāng)前組件,說(shuō)明依賴關(guān)系形成回環(huán),如果不拋出異常,將形成無(wú)限遞歸。)
  • 3、檢查當(dāng)前組件是否已初始化,如果已經(jīng)初始化過(guò),則直接返回(3.2),否則:
    • 3.1.1 記錄正在初始化
    • 3.1.2 通過(guò)反射實(shí)例化 Initializer 接口實(shí)現(xiàn)類
    • 3.1.3 遍歷所依賴的組件,如果所依賴的組件未初始化,遞歸調(diào)用doInitialize(...)執(zhí)行初始化
    • 3.1.4 (到這里,所依賴的組件已經(jīng)初始化完成)初始化當(dāng)前組件
    • 3.1.5 移除正在初始化記錄
    • 3.1.6 緩存初始化結(jié)果

3.3 手動(dòng)初始化源碼分析

現(xiàn)在我們來(lái)看手動(dòng)初始化(懶加載)的源碼分析:

AppInitializer.java

public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) {
    調(diào)用 doInitialize(...) 方法:
    return doInitialize(component, new HashSet<Class<?>>());
}

其實(shí)非常簡(jiǎn)單,就是調(diào)用上一節(jié)的doInitialize(...)執(zhí)行初始化。需要注意的是,這個(gè)方法是允許在子線程調(diào)用的,換句話說(shuō),自動(dòng)初始化與手動(dòng)初始化是存在線程同步問(wèn)題的,那么 App Startup 是如何解決的呢?

還記得我們前面有一個(gè)sLock沒(méi)有說(shuō)嗎?其實(shí)它就是用來(lái)保證線程同步的鎖:

AppInitializer.java

<T> T doInitialize(Class<? extends Initializer<?>> component, Set<Class<?>> initializing) {
    1、對(duì) sLock 加鎖
    synchronized (sLock) {
        ... 
    }
}

4. 總結(jié)

  • 優(yōu)點(diǎn):使用 App Startup 框架,可以簡(jiǎn)化啟動(dòng)序列并顯式設(shè)置初始化依賴順序,在簡(jiǎn)單、高效這方面,App Startup 基本滿足需求。

  • 不足:App Startup 框架的不足也是因?yàn)樗?jiǎn)單了,提供的特性太過(guò)簡(jiǎn)單,往往并不能完美契合商業(yè)化需求。例如以下特性 App Startup 就無(wú)法滿足:

    • 缺乏異步等待:同步等待指的是在當(dāng)前線程先初始化所依賴的組件,再初始化當(dāng)前組件,App Startup 是支持的,但是異步等待就不支持了。舉個(gè)例子,所依賴的組件需要執(zhí)行一個(gè)耗時(shí)的異步任務(wù)才能完成初始化,那么 App Startup 就無(wú)法等待異步任務(wù)返回;
    • 缺乏依賴回調(diào):當(dāng)前組件所依賴的組件初始化完成后,未發(fā)出回調(diào)。

參考資料


創(chuàng)作不易,你的「三連」是丑丑最大的動(dòng)力,我們下次見(jiàn)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 通過(guò)這篇文章你將學(xué)習(xí)到以下內(nèi)容:App Startup 是什么?App Startup 為我們解決了什么問(wèn)題?為什...
    灬佐手邊閱讀 612評(píng)論 0 1
  • 通過(guò)這篇文章你將學(xué)習(xí)到以下內(nèi)容: App Startup 是什么? App Startup 為我們解決了什么問(wèn)題?...
    夜沐下的星雨閱讀 2,883評(píng)論 0 1
  • 久違的晴天,家長(zhǎng)會(huì)。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí),離放學(xué)已經(jīng)沒(méi)多少時(shí)間了。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,788評(píng)論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開(kāi)了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    余生動(dòng)聽(tīng)閱讀 10,798評(píng)論 0 11
  • 可愛(ài)進(jìn)取,孤獨(dú)成精。努力飛翔,天堂翱翔。戰(zhàn)爭(zhēng)美好,孤獨(dú)進(jìn)取。膽大飛翔,成就輝煌。努力進(jìn)取,遙望,和諧家園。可愛(ài)游走...
    趙原野閱讀 3,443評(píng)論 1 1

友情鏈接更多精彩內(nèi)容