App Startup

2021-05-09  本文已影响0人  慎独静思

App Startup是用来在应用进程启动的时候初始化其他组件的。

一般我们初始化的操作都放在Application的onCreate方法中或者ContentProvider的onCreate方法中,因为ContentProvider的onCreate方法会在进程创建的时候调用。
但是ContentProvider的创建成本比较高,并且它的onCreate方法会延长应用的启动时间,所以如果应用中或三方库中定义了多个ContentProvider,势必会对应用的性能产生影响。
App Startup 就是为了解决这个问题,它可以是应用的所有初始化操作(包括三方库和应用自定义内容)共用同一ContentProvider,并且可以控制初始化的顺序。

首先,需要在gradle中添加依赖

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

其次,实现接口Initializer<T>,并提供它的两个方法的实现。

Initializer的源码如下:

/**
 * {@link Initializer}s can be used to initialize libraries during app startup, without
 * the need to use additional {@link android.content.ContentProvider}s.
 *
 * @param <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();
}

在create方法中需要初始化并返回组件的实例。
dependencies方法中指定了初始化当前组件需要依赖哪些其他的组件,这个方法决定了组件初始化的顺序。比如B组件依赖于A组件,那A在B之前初始化。
可以看一下WorkManagerInitializer的源码

/**
 * Initializes {@link androidx.work.WorkManager} using {@code androidx.startup}.
 */
public final class WorkManagerInitializer implements Initializer<WorkManager> {

    private static final String TAG = Logger.tagWithPrefix("WrkMgrInitializer");

    @NonNull
    @Override
    public WorkManager create(@NonNull Context context) {
        // Initialize WorkManager with the default configuration.
        Logger.get().debug(TAG, "Initializing WorkManager with default configuration.");
        WorkManager.initialize(context, new Configuration.Builder().build());
        return WorkManager.getInstance(context);
    }

    @NonNull
    @Override
    public List<Class<? extends androidx.startup.Initializer<?>>> dependencies() {
        return Collections.emptyList();
    }
}

假设,我们的应用同样依赖一个叫做ExampleLogger的三方库,并且ExampleLogger依赖于WorkManager,那我们可以定义一个ExampleLoggerInitializer

// Initializes ExampleLogger.
class ExampleLoggerInitializer implements Initializer<ExampleLogger> {

    @Override
    public ExampleLogger create(Context context) {
        // WorkManager.getInstance() is non-null only after
        // WorkManager is initialized.
        return ExampleLogger(WorkManager.getInstance(context));
    }

    @Override
    public List<Class<Initializer<?>>> dependencies() {
        // Defines a dependency on WorkManagerInitializer so it can be
        // initialized after WorkManager is initialized.
        return Arrays.asList(WorkManagerInitializer.class);
    }
}

这样就能确保 WorkManager在 ExampleLogger之前初始化。

最后,需要在manifest中进行声明

App Startup中包含一个InitializationProvider,它是ContentProvider的子类,用来发现和调用我们定义的initializers。App Startup会首先检查manifest中定义的<meta-data>,然后初始化已经发现的initializers的依赖组件。
比如,我们上边定义的WorkManagerInitializer和ExampleLoggerInitializer,我们只需要在<meta-data>中定义ExampleLoggerInitializer,因为ExampleLoggerInitializer依赖于WorkManagerInitializer,所以App Startup可以找到WorkManagerInitializer并先进行初始化。

<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>

tools:node="merge"可以确保merge工具可以正确的处理冲突。

我们也可以手动的初始化组件。

首先,要先关闭自动初始化。
对单个组件,删除对应的<meta-data>即可。

<meta-data android:name="com.example.ExampleLoggerInitializer"
              tools:node="remove" />

对全部的组件,可以删掉provider的声明。

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

然后,在需要使用组件的地方手动调用初始化方法。

AppInitializer.getInstance(context)
    .initializeComponent(ExampleLoggerInitializer.class);

从源码中分析原理

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

我们可以看到InitializationProvider继承了ContentProvider,并在其onCreate方法中调用了AppInitializer. discoverAndInitialize方法

@SuppressWarnings("unchecked")
    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;
                            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();
        }
    }
@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();
            }
        }
    }

代码比较简单明了,大家可以自行查看。

缺点

1、App Startup对于直接在Application.onCreate方法中进行初始化的方法优势不是很明显。
2、对于每一个需要初始化的组件都要定义一个Initializer,是不是太繁琐了?

上一篇下一篇

猜你喜欢

热点阅读