Android Jetpack 之 App Startup

1. 概述

最近這幾天關注的幾個公衆號陸續推送關於 App Startup 的相關文章,就知道又要學習新知識了。App Startup  和 Lifecycle 、DataBinding 一樣都是 Jetpack 的組件之一。目前 App Startup 還處於 alpha 版本。官網地址:https://developer.android.google.cn/topic/libraries/app-startup?hl=en

2. App Startup 定義

The App Startup library provides a straightforward, performant way to initialize components at application startup. Both library developers and app developers can use App Startup to streamline startup sequences and explicitly set the order of initialization.

Instead of defining separate content providers for each component you need to initialize, App Startup allows you to define component initializers that share a single content provider. This can significantly improve app startup time.

翻譯如下:

App Startup 庫提供了在應用程序啓動時用於初始化組件的簡單、高效的方式。開發人員可以使用 App Startup 來簡化啓動序列,並顯式地設置初始化順序。

App Startup 允許您定義共享單個內容提供程序的組件初始化器,而不是爲每個需要初始化的組件定義單獨的 contentProvider。這可以顯著提高應用程序的啓動時間。

3. ContentProvider 中進行初始化

當定義一個 ContentProvider,在 App 的啓動階段,它是在什麼時候進行初始化的?看下這個 log:

2020-07-02 14:38:16.905 6928-6928/cn.zzw.template.startupdemo E/StartUpDemo: Application    attachBaseContext
2020-07-02 14:38:16.907 6928-6928/cn.zzw.template.startupdemo E/StartUpDemo: ContentProvider    onCreate
2020-07-02 14:38:16.908 6928-6928/cn.zzw.template.startupdemo E/StartUpDemo: Application    onCreate

在這裏可以看到 ContentProvider 的 onCreate 方法執行時間是介於 Application 的 attachBaseContext 和 onCreate 之間。所以很多庫會選擇創建 ContentProvider 進行初始化。關於用 ContentProvider 進行初始化的介紹可以參考下此篇文章:使用ContentProvider初始化你的Library,這裏不進行過多的介紹,App Startup 就是建立在此基礎上的。

4. App Startup 的導入

目前 App Startup 還處於 alpha 版本:

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

5. App Startup 的使用

假設當前有三個庫需要進行初始化 SdkA、SdkB、SdkC,這裏只寫展示 SdkA 的代碼,SdkB、SdkC 和 SdkA 結構相似:

class SdkA {
    companion object {
        fun getInstance(): SdkA {
            return Instance.instance
        }
    }

    private object Instance {
        val instance = SdkA()
    }
}

創建對應的初始化對象,必須接口 Initializer<T>,接口 Initializer 的代碼如下:

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();
}

create 方法用於進行對象的初始化;

 dependencies 方法用於定義需要在當前對象初始化之前進行初始化的對象對應的 Initializer。

SdkA 的對應的 SdkAInitializer:

class SdkAInitializer : Initializer<SdkA> {
    override fun create(context: Context): SdkA {
        Log.e("StartUpDemo", "SdkAInitializer    create");
        return SdkA.getInstance();
    }

    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
        Log.e("StartUpDemo", "SdkAInitializer    dependencies");
        return Collections.emptyList()
    }
}

SdkA 的初始化不需要其他庫的依賴,所以這裏 dependencies 方法返回一個空列表。

SdkB 的對應的 SdkBInitializer:

class SdkBInitializer : Initializer<SdkB> {
    override fun create(context: Context): SdkB {
        Log.e("StartUpDemo", "SdkBInitializer    create");
        return SdkB.getInstance();
    }

    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
        Log.e("StartUpDemo", "SdkBInitializer    dependencies");
        return mutableListOf(SdkAInitializer::class.java)
    }
}

這裏假設 SdkA 必須在 SdkB 之前進行初始化,所以 dependencies 返回 SdkA 對應的 SdkAInitializer。

SdkC 的對應的 SdkCInitializer:

class SdkCInitializer : Initializer<SdkC> {
    override fun create(context: Context): SdkC {
        Log.e("StartUpDemo", "SdkCInitializer    create");
        return SdkC.getInstance();
    }

    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
        Log.e("StartUpDemo", "SdkCInitializer    dependencies");
        return mutableListOf(SdkBInitializer::class.java)
    }
}

這裏假設 SdkB 必須在 SdkC 之前進行初始化,所以 dependencies 返回 SdkB 對應的 SdkBInitializer。

接着在 manifest 中定義 InitializationProvider:

        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            android:exported="false"
            tools:node="merge">
            <meta-data
                android:name="cn.zzw.template.startupdemo.initializer.SdkCInitializer"
                android:value="androidx.startup" />
        </provider>

在上面的三個 sdk中,C 依賴 B,B 依賴 A,A 不依賴其他。在 <meta-data> 標籤中只需要定義 SdkCInitializer。

運行後的 log 如下:

2020-07-02 17:01:11.446 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: Application    attachBaseContext
2020-07-02 17:01:11.549 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: ContentProvider    onCreate
2020-07-02 17:01:11.577 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: SdkCInitializer    dependencies
2020-07-02 17:01:11.600 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: SdkBInitializer    dependencies
2020-07-02 17:01:11.601 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: SdkAInitializer    dependencies
2020-07-02 17:01:11.601 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: SdkAInitializer    create
2020-07-02 17:01:11.601 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: SdkBInitializer    create
2020-07-02 17:01:11.605 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: SdkCInitializer    create
2020-07-02 17:01:11.647 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: Application    onCreate

從log可以看出,dependencies 方法的執行順序是 C → B → A,create 方法的執行順序是 A → B → C。這裏的 create 方法的順序正是對象創建的順序。

5.1 手動初始化

在上面的例子中,當應用運行後,所有的 SDK 都進行了初始化。可是所有的 SDK 都在啓動階段進行初始化,會導致啓動速度的變慢,某些 SDK 如何在需要使用的時候才進行初始化?

再創建一個 SdkD,SdkCInitializer 類如下:

class SdkDInitializer : Initializer<SdkD> {
    override fun create(context: Context): SdkD {
        Log.e("StartUpDemo", "SdkDInitializer    create");
        return SdkD.getInstance();
    }

    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
        Log.e("StartUpDemo", "SdkDInitializer    dependencies");
        return Collections.emptyList()
    }
}

需要在 manifest 中通過 <meta-data> 標籤中只需要定義 SdkDInitializer,並且添加  tools:node="remove":

        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            android:exported="false"
            tools:node="merge">
            <meta-data
                android:name="cn.zzw.template.startupdemo.initializer.SdkCInitializer"
                android:value="androidx.startup" />
            <meta-data
                android:name="cn.zzw.template.startupdemo.initializer.SdkDInitializer"
                android:value="androidx.startup"
                tools:node="remove" />
        </provider>

在 Activity 中對其進行初始化:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        AppInitializer.getInstance(this).initializeComponent(SdkDInitializer::class.java)
    }

運行後 log 如下:

2020-07-02 18:03:21.070 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: Application    attachBaseContext
2020-07-02 18:03:21.194 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: ContentProvider    onCreate
2020-07-02 18:03:21.206 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkCInitializer    dependencies
2020-07-02 18:03:21.213 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkBInitializer    dependencies
2020-07-02 18:03:21.221 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkAInitializer    dependencies
2020-07-02 18:03:21.221 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkAInitializer    create
2020-07-02 18:03:21.221 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkBInitializer    create
2020-07-02 18:03:21.222 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkCInitializer    create
2020-07-02 18:03:21.228 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: Application    onCreate
2020-07-02 18:03:21.729 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkDInitializer    dependencies
2020-07-02 18:03:21.729 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkDInitializer    create

從 log 中可以看到 SdkD 在最後才進行初始化。

6. 源碼分析

在 manifest 中定義 InitializationProvider 是一個 ContentProvider:

@RestrictTo(RestrictTo.Scope.LIBRARY)
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;
    }

    ...
}

只保留它的一個 onCreate 方法,調用了 AppInitializer 的 discoverAndInitialize 方法。

AppInitializer 是一個單例,AppInitializer 的構造方法:

    AppInitializer(@NonNull Context context) {
        mContext = context.getApplicationContext();
        mInitialized = new HashMap<>();
    }

在構造方法中創建一個 HashMap。

discoverAndInitialize 方法:

    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;
            String startup = mContext.getString(R.string.androidx_startup);
            if (metadata != null) {
                Set<Class<?>> initializing = new HashSet<>();
                Set<String> keys = metadata.keySet();
                for (String key : keys) {
                    String value = metadata.getString(key, null);
                    if (startup.equals(value)) {
                        Class<?> clazz = Class.forName(key);
                        if (Initializer.class.isAssignableFrom(clazz)) {
                            Class<? extends Initializer<?>> component =
                                    (Class<? extends Initializer<?>>) clazz;
                            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();
        }
    }

首先獲取 meta-data 標籤,並且遍歷每一個 meta-data 中的 Initializer 對象,並調用 doInitialize 方法。

    <T> T doInitialize(
            @NonNull Class<? extends Initializer<?>> component,
            @NonNull Set<Class<?>> initializing) {
        synchronized (sLock) {
            
            ...

                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();    // 1

                        if (!dependencies.isEmpty()) {
                            for (Class<? extends Initializer<?>> clazz : dependencies) {
                                if (!mInitialized.containsKey(clazz)) {
                                    doInitialize(clazz, initializing);    // 2
                                }
                            }
                        }
                        if (StartupLogger.DEBUG) {
                            StartupLogger.i(String.format("Initializing %s", component.getName()));
                        }
                        result = initializer.create(mContext);    // 3
                        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();
            }
        }
    }

註釋 1 中:先找 需要初始化對應的 Initializer,並判斷其 dependencies 返回是否爲空,如果不爲空,獲取到對應的 Initializer,並繼續執行 doInitialize(註釋 2)。這樣的話,所以的 dependencies 方法會先被執行完畢,只有 dependencies 都執行完了後,纔會執行註釋 3 中的 create 方法。

再來看看延遲加載時候調用的 initializeComponent 方法:

    public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) {
        return doInitialize(component, new HashSet<Class<?>>());
    }

這裏就是調用了 doInitialize 方法。

7. 總結

App Startup 提供了一個 ContentProvider 來運行所有依賴項的初始化,避免了把依賴項寫在 Application 中,也避免每個需要初始化的庫單獨使用 ContentProvider 進行初始化,從而提高了 App 的啓動速度。App Startup 可以自定義需要初始化的依賴的初始化順序。App Startup 中每個需要初始化的依賴都需要實現 Initializer 接口,會導致文件的增多。App Startup 目前還處於 alpha 版本,相信在後續的版本更新中會有更多的亮點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章