Android架構(gòu)進階之深入理解AppStartup原理
前言
Android Startup提供一種在應(yīng)用啟動時能夠更加簡單、高效的方式來初始化組件。開發(fā)人員可以使用Android Startup來簡化啟動序列,并顯式地設(shè)置初始化順序與組件之間的依賴關(guān)系;
今天我們就來聊聊
一、使用步驟簡單介紹
使用 AndroidX App Startup 來運行所有依賴項的初始化有兩種方式:
自動初始化;
手動初始化(也是延遲初始化);
1、自動初始化
在 build.gradle 文件內(nèi)添加依賴;
- implementation "androidx.startup:startup-runtime:1.0.0-alpha01"
實現(xiàn) Initializer 接口,并重寫兩個方法,來初始化組件;
- public class MvpInitializer implements Initializer<Void> {
- @NonNull
- @Override
- public Void create(@NonNull Context context) {
- MvpManager.init(context);
- return null;
- }
- @NonNull
- @Override
- public List<Class<? extends Initializer<?>>> dependencies() {
- return new ArrayList<>();
- }
- }
- ......
- }
create(Context): 這里進行組件初始化工作;
dependencies(): 返回需要初始化的列表,同時設(shè)置 App 啟動時依賴庫運行的順序;
在 AndroidManifest.xml 文件中注冊 InitializationProvider;
- <application>
- <provider
- android:authorities="${applicationId}.androidx-startup"
- android:name="androidx.startup.InitializationProvider"
- android:exported="false"
- tools:node="merge" >
- <!-- 自動初始化 -->
- <meta-data android:name="com.test.Initializer" android:value="androidx.startup"/>
- </provider>
- </application>
App 啟動的時 App Startup 會讀取 AndroidManifest.xml 文件里面的 InitializationProvider 下面的
2、手動初始化(也是延遲初始化)
在 build.gradle 文件內(nèi)添加依賴;
創(chuàng)建一個類 LibaryD 實現(xiàn) Initializer 接口,并重寫兩個方法,來初始化組件;
在 AndroidManifest.xml 文件中注冊 InitializationProvider
- <application>
- <provider
- android:name="androidx.startup.InitializationProvider"
- android:authorities="${applicationId}.androidx-startup"
- android:exported="false"
- tools:node="merge">
- <!-- 手動初始化(也是延遲初始化) -->
- <meta-data
- android:name="com.test.Initializer"
- android:value="androidx.startup"
- tools:node="remove" />
- </provider>
- </application>
- 只需要在
標(biāo)簽內(nèi)添加 tools:node="remove" 清單合并工具會將它從清單文件中刪除; - 在需要的地方進行初始化,調(diào)用以下代碼進行初始化;
- Initializer.getInstance(context).initializeComponent(Initializer::class.java);
- 如果組件初始化之后,再次調(diào)用 AppInitializer.initializeComponent() 方法不會再次初始化;
- 手動初始化(也是延遲初始化)是非常有用的,組件不需要在 App 啟動時運行,只需要在需要它地方運行,可以減少 App 的啟動時間,提高啟動速度;
二、源碼分析
1、InitializationProvider
在AndroidManifest文件中配置的組件名必須為androidx.startup.InitializationProvider,現(xiàn)在我們來看這個類的源碼;
- InitializationProvider.java
- 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.");
- }
- }
InitializationProvider其實也是利用了 ContentProvider 的啟動機制,在ContentProvider#onCreate(...)中執(zhí)行初始化;
ContentProvider 的其他方法是沒有意義的,所以都拋出了IllegalStateException;
2、自動初始化分析
App Startup 在 ContentProvider 中調(diào)用了AppInitializer#discoverAndInitialize()執(zhí)行自動初始化;
AppInitializer是 App StartUp 框架的核心類,整個 App Startup 框架的代碼其實非常少,其中很大部分核心代碼都在 AppInitializer 類中;
2.1.AppInitializer.java discoverAndInitialize
- final Set<Class<? extends Initializer<?>>> mDiscovered;
- void discoverAndInitialize() {
- 獲取 androidx.startup.InitializationProvider 組件信息
- ComponentName provider = new ComponentName(mContext.getPackageName(), InitializationProvider.class.getName());
- ProviderInfo providerInfo = mContext.getPackageManager().getProviderInfo(provider, GET_META_DATA);
- androidx.startup 字符串
- String startup = mContext.getString(R.string.androidx_startup);
- 獲取組件信息中的 meta-data 數(shù)據(jù)
- Bundle metadata = providerInfo.metaData;
- 遍歷 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);
- 判斷 meta-data 數(shù)據(jù)中,value 為 androidx.startup 的鍵值對
- if (startup.equals(value)) {
- Class<?> clazz = Class.forName(key);
- 檢查指定的類是 Initializer 接口的實現(xiàn)類
- if (Initializer.class.isAssignableFrom(clazz)) {
- Class<? extends Initializer<?>> component = (Class<? extends Initializer<?>>) clazz;
- 將 Class 添加到 mDiscovered Set 中
- mDiscovered.add(component);
- 初始化此組件
- doInitialize(component, initializing);
- }
- }
- }
- }
- }
- mDiscovered 用于判斷組件是否已經(jīng)自動啟動
- public boolean isEagerlyInitialized(@NonNull Class<? extends Initializer<?>> component) {
- return mDiscovered.contains(component);
- }
- 獲取androidx.startup.InitializationProvider組件信息(在各個 Module 中聲明的組件信息,會在manifest merger tool的處理下合并);
- androidx.startup字符串;
- 獲取組件信息中的 meta-data 數(shù)據(jù);
- 遍歷 meta-data 數(shù)據(jù);
- 判斷 meta-data 數(shù)據(jù)中,value 為 androidx.startup 的鍵值對;
- 檢查指定的類是 Initializer 接口的實現(xiàn)類;
- 將 Class 添加到 mDiscovered Set 中,這將用于后續(xù) 判斷組件是否已經(jīng)自動啟動;
- 初始化此組件;
2.2.AppInitializer.java AppInitializer.java
- private static final Object sLock = new Object();
- 緩存每個組件的初始化結(jié)果;
- final Map<Class<?>, Object> mInitialized;
- 初始化此組件
- <T> T doInitialize(Class<? extends Initializer<?>> component, Set<Class<?>> initializing) {
- 對 sLock 加鎖
- Object result;
- 判斷 initializing 中存在當(dāng)前組件,說明存在循環(huán)依賴
- if (initializing.contains(component)) {
- String message = String.format("Cannot initialize %s. Cycle detected.", component.getName());
- throw new IllegalStateException(message);
- }
- 檢查當(dāng)前組件是否已初始化
- if (!mInitialized.containsKey(component)) {
- 當(dāng)前組件未初始化
- 記錄正在初始化
- initializing.add(component);
- 通過反射實例化 Initializer 接口實現(xiàn)類
- 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) {
- 如果所依賴的組件未初始化,遞歸執(zhí)行初始化
- if (!mInitialized.containsKey(clazz)) {
- doInitialize(clazz, initializing); 注意:這里將 initializing 作為參數(shù)傳入
- }
- }
- }
- 初始化當(dāng)前組件
- result = initializer.create(mContext);
- 移除正在初始化記錄
- initializing.remove(component);
- 緩存初始化結(jié)果
- mInitialized.put(component, result);
- } else {
- 當(dāng)前組件已經(jīng)初始化,直接返回
- result = mInitialized.get(component);
- }
- return (T) result;
- }
- 對 sLock 加鎖;
- 判斷 initializing 中存在當(dāng)前組件,說明存在循環(huán)依賴(這是因為遞歸初始化所依賴的組件時,會將 initializing 作為參數(shù)傳入,如果 initializing 中存在當(dāng)前組件,說明依賴關(guān)系形成回環(huán),如果不拋出異常,將形成無限遞歸);
- 檢查當(dāng)前組件是否已初始化;
- 記錄正在初始化;
- 通過反射實例化 Initializer 接口實現(xiàn)類;
- 遍歷所依賴的組件,如果所依賴的組件未初始化,遞歸調(diào)用doInitialize(...)執(zhí)行初始化;
- 初始化當(dāng)前組件;
- 移除正在初始化記錄;
- 緩存初始化結(jié)果;
3、手動初始化源碼分析
手動初始化(懶加載)的源碼分析:
- AppInitializer.java
- public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) {
- 調(diào)用 doInitialize(...) 方法:
- return doInitialize(component, new HashSet<Class<?>>());
- }
其實非常簡單,就是調(diào)用上一節(jié)的doInitialize(...)執(zhí)行初始化。需要注意的是,這個方法是允許在子線程調(diào)用的,換句話說,自動初始化與手動初始化是存在線程同步問題的,那么 App Startup 是如何解決的呢?
前面有一個sLock沒有說嗎?其實它就是用來保證線程同步的鎖:
- <T> T doInitialize(Class<? extends Initializer<?>> component, Set<Class<?>> initializing) {
- 對 sLock 加鎖
- synchronized (sLock) {
- ...
- }
- }
總結(jié)
App Startup 是 Jetpack 的新成員,是為了解決因 App 啟動時運行多個 ContentProvider 會增加 App 的啟動時間的問題;
使用了一個 InitializationProvider 管理多個依賴項,消除了每個庫單獨使用 ContentProvider 成本,減少初始化時間;
App Startup 允許你自定義組件初始化順序;
App Startup 提供了一種延遲初始化組件的方法,減少 App 初始化時間;