美文网首页
App Startup

App Startup

作者: 慎独静思 | 来源:发表于2021-05-09 23:47 被阅读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,是不是太繁琐了?

    相关文章

      网友评论

          本文标题:App Startup

          本文链接:https://www.haomeiwen.com/subject/zmbvdltx.html