天天看點

Android Jetpack Startup庫分析簡介使用方法Startup庫原了解析Startup庫優點

簡介

App Startup 庫提供了一種在應用程式啟動時初始化元件的簡單、高效的方法。庫開發人員和應用程式開發人員都可以使用 App Startup 來簡化啟動順序并明确設定初始化順序。

App Startup 允許您定義共享單個内容提供者的元件初始值設定項,而不是為您需要初始化的每個元件定義單獨的内容提供程式。這可以顯着縮短應用程式啟動時間。

使用方法

單個Initializer

通過下面的配置在項目中引入Startup庫:

dependencies {
    implementation("androidx.startup:startup-runtime:1.0.0")
}
           

要完成某些初始化工作,我們需要自定義一個Initializer類,比如如下代碼:

package com.yb.testjetpack

import android.content.Context
import android.util.Log
import androidx.startup.Initializer

// 自定義Initializer,必須實作Initializer接口
class MyInitializer : Initializer<Unit> {
    companion object {
        private const val TAG = "MyInitializer"
    }

    // 在這裡完成某些初始化工作
    override fun create(context: Context): Unit {
        Log.d(TAG, "create: do some init...")
    }

    // 該方法傳回MyInitializer初始化前依賴的其他Initializer,這些Initializer會先執行
    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
        return arrayListOf()
    }
}
           

然後在項目的AndroidManifest.xml檔案中注冊這個Initializer:

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

通過以上簡單的3步即可将Startup庫應用在項目中,我們運作項目時,

MyInitializer

類中的

onCreate

方法會自動得到調用。

多個Initializer

以上介紹的是項目中隻有一個Initializer的情況,其實項目中也可以定義多個不同的Initializer。

在上面的代碼中,我們可以發現

Initializer

接口中有個

dependencies

方法,這個方法的傳回值是一個數組,代表目前Initializer的初始化依賴的其他Initializer,下面舉個例子說明應用中存在多個不同Initializer時的處理方法:

假設目前應用中有三個不同的Initializer:

  • MyInitializer

  • FirstInitializer

  • SecondInitializer

這三個Initializer的初始化有一些特定的順序:

SecondInitializer

的初始化依賴

FirstInitializer

,而

FirstInitializer

的初始化依賴

MyInitializer

,即

MyInitializer

最先執行,其次是

FirstInitializer

執行,最後是

SecondInitializer

,那麼這三個Initializer的寫法如下:

// MyInitializer
class MyInitializer : Initializer<Unit> {
    companion object {
        private const val TAG = "MyInitializer"
    }

    // 在這裡完成某些初始化工作
    override fun create(context: Context): Unit {
        Log.d(TAG, "create: do some init...")
    }

    // 該方法傳回MyInitializer初始化前依賴的其他Initializer,這些Initializer會先執行
    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
    	// MyInitializer的初始化不依賴其他任何Initializer,傳回一個空清單即可
        return arrayListOf()
    }
}

// FirstInitializer
class FirstInitializer : Initializer<String> {

    companion object {
        private const val TAG = "FirstInitializer"
    }

    override fun create(context: Context): String {
        Log.d(TAG, "create: init FirstInitializer...")
        return "FirstInitializer"
    }

    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
    	// FirstInitializer的初始化依賴MyInitializer
        return arrayListOf(MyInitializer::class.java)
    }

}

// SecondInitializer
class SecondInitializer : Initializer<Unit> {

    companion object {
        private const val TAG = "SecondInitializer"
    }

    override fun create(context: Context) {
        Log.d(TAG, "create: init SecondInitializer...")
    }

    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
        // SecondInitializer的初始化依賴FirstInitializer
        return arrayListOf(FirstInitializer::class.java)
    }
}
           

然後在AndroidManifest.xml檔案中注冊Initializer。

你可以将這三個Initializer都注冊,像這樣:

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

也可以隻注冊最後一個被調用的Initializer

比如以上三個Initializer的調用順序是:

MyInitializer

FirstInitializer

SecondInitializer

那麼在AndroidManifest.xml檔案中,注冊

SecondInitializer

即可:

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

Initializer懶加載

以上介紹的單一Initializer和多個Initializer在注冊到AndroidManifest.xml檔案中後,不需要在代碼中顯式調用,App啟動時即可自動完成Initializer中

onCreate

方法的執行,其原理會在後面詳細說明。

某些時候我們可能并不需要子產品在App啟動時就立刻初始化,而是希望通過調用Java代碼完成初始化。

如果你隻想禁用單個Initializer的自動初始化,可以這麼做:

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

注意

meta-data

中的

tools:node="remove"

屬性,這個配置并非簡單的删除條目,而是確定合并工具也從所有其他合并的清單檔案中删除條目。

如果你想禁用所有的Initializer的自動初始化,可以這麼做:

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

然後使用Java或Kotlin代碼完成某個Initializer手動的初始化,比如下面的代碼展示了在點選TextView後初始化MyInitializer:

findViewById<TextView>(R.id.text).setOnClickListener {
    AppInitializer.getInstance(this).initializeComponent(MyInitializer::class.java)
}
           

Startup庫原了解析

你是不是和我一樣好奇為啥Startup庫不需要顯式的執行初始化代碼即可自動幫我們完成create方法中代碼的執行?

通過Startup庫的源碼可以找到答案:Startup庫使用了ContentProvider,而ContentProvider在注冊到AndroidManifest.xml檔案中後,ContentProvider中的

onCreate

方法會在Application的

attachBaseContext

方法之後,

onCreate

方法之前被調用。本篇不詳細讨論ContentProvider的啟動流程,僅僅解析Startup庫的源碼。

Startup庫的源碼非常少,僅有5個類,如下圖所示:

Android Jetpack Startup庫分析簡介使用方法Startup庫原了解析Startup庫優點

其中Initializer是一個接口,其中定義了兩個方法:

create()

dependencies()

,源碼如下:

/**
 * Initializers can be used to initialize libraries during app startup, without the need to use additional android.content.ContentProviders.
 * Type parameters:
 * <T> – The instance type being initialized
 */
public interface Initializer<T> {

    /**
     * Initializes and a component given the application {@link Context}
     *
     * @param context The application context.
     */
    @NonNull
    T create(@NonNull Context context);

    /**
     * @return A list of dependencies that this {@link Initializer} depends on. This is
     * used to determine initialization order of {@link Initializer}s.
     * <br/>
     * For e.g. if a {@link Initializer} `B` defines another
     * {@link Initializer} `A` as its dependency, then `A` gets initialized before `B`.
     */
    @NonNull
    List<Class<? extends Initializer<?>>> dependencies();
}
           

根據源碼上的注釋可以知道,T表示即将被初始化的執行個體類型。

Startup庫之是以可以在App啟動時自動被執行,主要的類還是

InitializationProvider

,這個類繼承了ContentProvider,其onCreate方法源碼如下:

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

這裡的關鍵代碼是

AppInitializer.getInstance(context).discoverAndInitialize();

discoverAndInitialize()

方法會去AndroidManifest.xml檔案中查找

<meta-data>

标簽,并過濾出值為

androidx.startup

的标簽,接着通過反射去執行個體化我們自定義的Initializer,代碼如下:

void discoverAndInitialize() {
    try {
        Trace.beginSection(SECTION_NAME);
        ComponentName provider = new ComponentName(mContext.getPackageName(),
                InitializationProvider.class.getName());
        ProviderInfo providerInfo = mContext.getPackageManager()
                .getProviderInfo(provider, GET_META_DATA);
        Bundle metadata = providerInfo.metaData;
        // startup為字元串"androidx.startup"
        String startup = mContext.getString(R.string.androidx_startup);
        if (metadata != null) {
            Set<Class<?>> initializing = new HashSet<>();
            // 擷取AndroidManifest.xml中所有meta-data标簽
            Set<String> keys = metadata.keySet();
            for (String key : keys) {
                String value = metadata.getString(key, null);
                // 比對到"androidx.startup",表示這個标簽是自定義的Initializer
                if (startup.equals(value)) {
                	// 通過反射執行個體化自定義Initializer
                    Class<?> clazz = Class.forName(key);
                    if (Initializer.class.isAssignableFrom(clazz)) {
                        Class<? extends Initializer<?>> component =
                                (Class<? extends Initializer<?>>) clazz;
                        mDiscovered.add(component);
                        if (StartupLogger.DEBUG) {
                            StartupLogger.i(String.format("Discovered %s", key));
                        }
                        // 初始化
                        doInitialize(component, initializing);
                    }
                }
            }
        }
    } catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) {
        throw new StartupException(exception);
    } finally {
        Trace.endSection();
    }
}

<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();
					// 如果依賴不為空,則先初始化依賴的Initializer
                    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()));
                    }
                    // 執行自定義Initializer中的create方法
                    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();
        }
    }
}
           

寫到這裡你應該已經知道Startup庫為甚麼能自動執行初始化操作了吧,再想想我們使用LeakCanary庫時,隻是在build.gradle檔案中添加了依賴:

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
           

再不需要其他代碼的調用,LeakCanary就能自動檢測記憶體洩漏問題,這種隐藏式的初始化,其原理和Startup庫是一樣的,都使用了ContentProvider。

Startup庫優點

使用Startup庫在某些情況下可以加速我們App的啟動過程,具體可以檢視如下兩篇文章:

  • 應用啟動性能 | 介紹 App Startup 庫
  • Android 應用啟動性能 | 延遲初始化