前言
应用程序启动库StartUp
提供了一种在应用程序启动时初始化组件的简单、高效的方法。库开发者和应用开发者都可以使用app Startup来简化启动序列,并显式设置初始化顺序。
不同于为每个需要初始化的组件定义单独的内容提供程序,App Startup允许你定义共享单个内容提供程序的组件初始化程序。这可以显著提高应用程序的启动时间。
此组件主要解决的痛点问题是:简化各个外部引用SDK初始化操作,并优化SDK初始化时机,在确保启动速度的情况下以更优更稳定的方式按需初始化SDK(当然,也可以利用它的按需按序执行的功能来初始化某些类)。
【本系列文章演示案例均存储在github存储库ArchitecturalComponentExample中】
如何使用 引入依赖 在app/build.gradle
文件的dependencies
中加入以下内容:
1 implementation "androidx.startup:startup-runtime:1.0.0"
若获取依赖包报错,请检查项目目录下build.gradle
中是否包含以下代码:
1 2 3 4 5 6 allprojects { repositories { google() jcenter() } }
示例项目结构说明 本项目Java版本地址
本项目Kotlin版本地址
最简实践请参考官方开发文档
本项目示例主要结构如下:
图中②部分代表用来初始化各个组件SDK的初始化器。
图中①部分代表模拟的需初始化的三方SDK,SDK之间存在”有向无环”的相互依赖关系 ,依赖关系如下:
上述图中表述的依赖关系为:
Cache 依赖于 DatabaseProxy 及 Logger
DatabaseProxy 依赖于 DatabaseHelper 及 Logger
DatabaseHelper 及 TXMap 均依赖于 Logger
创建初始化器
通过创建一个实现了初始化器接口的类来定义每个组件初始化器。这个接口定义了两个重要的方法:
create()方法包含初始化组件所需的所有操作,并返回T的实例。
dependencies()方法,它返回初始化器所依赖的其他初始化器对象的列表。你可以使用这个方法来控制应用程序在启动时运行初始化器的顺序。
依照上述规则,我们来编写Logger的初始化类LoggerInitializer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class LoggerInitializer implements Initializer <Logger > { @NonNull @Override public Logger create (@NonNull Context context) { Log.i("loglog" , "create: LoggerInitializer" ); Logger.initialize(); return Logger.getInstance(); } @NonNull @Override public List<Class<? extends Initializer<?>>> dependencies() { return Collections.emptyList(); } }
首先初始化器需要实现Initializer>
接口,并传入需初始化的对象的泛型,思考如果不传或传入Void会怎样?
实现create方法,此方法中完成组件初始化,如有需要可返回实例
实现dependencies方法,此方法中声明当前初始化组件依赖于其他组件,并返回被依赖组件的初始化器,此处Logger未依赖其他组件,故返回空数组,思考如果返回null会怎样?
类似的,我们根据组件依赖关系依次定义出各个组件的初始化器,对比上述无依赖的Logger,我们再来看看多个依赖的DatabaseProxy的初始化器是如何定义的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class DatabaseProxyInitializer implements Initializer <DatabaseProxy > { @NonNull @Override public DatabaseProxy create (@NonNull Context context) { Log.i("loglog" , "create: DatabaseProxyInitializer" ); DatabaseProxy.initialize(DatabaseHelper.getInstance()); return DatabaseProxy.getInstance(); } @NonNull @Override public List<Class<? extends Initializer<?>>> dependencies() { return Arrays.asList(LoggerInitializer.class, DatabaseHelperInitializer.class); } }
我们看到在dependencies
中声明了DatabaseProxy依赖于Logger和DatabaseHelper。
接下来,我们尝试一下上述的两个思考题。
当Initializer<?>泛型传入Void会怎样?
此场景类似于:当我们仅单纯的想执行某组件的初始化而没有返回实例的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class SomethingInitializer implements Initializer <Void > { @NonNull @Override public Void create (@NonNull Context context) { Log.i("loglog" , "create: SomethingInitializer" ); return null ; } @NonNull @Override public List<Class<? extends Initializer<?>>> dependencies() { return Collections.emptyList(); } }
此时,我们让TXMap的初始化器去依赖这SomethingInitializer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MapInitializer implements Initializer <TXMap > { @NonNull @Override public TXMap create (@NonNull Context context) { Log.i("loglog" , "create: MapInitializer" ); return new TXMap(); } @NonNull @Override public List<Class<? extends Initializer<?>>> dependencies() { return Arrays.asList(LoggerInitializer.class, SomethingInitializer.class); } }
让我们看看其执行结果 (当然,仅编写初始化器的代码仍不能正常工作,还未在AndroidManifest中声明初始化组件,此处我们暂只关注结果,下一节将介绍声明初始化组件) :
1 2 3 4 5 I/tag: create: LoggerInitializer I/tag: Logger initialized I/tag: create: SomethingInitializer I/tag: create: MapInitializer I/tag: Map initialized
我们可以看到,其依旧正常按需执行了初始化操作,先调用了被TXMap依赖的Logger及Something的初始化器,最终完成Map的初始化。说明,Initializer<?>中有无传入泛型及传入Void泛型,其均可正常执行,正常适用于仅执行初始化操作,无返回实例的情景。
如果dependencies返回值为null会发生什么?
我们将LoggerInitializer的依赖由return Collections.emptyList()
修改为return null
然后执行 (仅编写初始化器的代码仍不能正常工作,还未在AndroidManifest中声明初始化组件,此处我们只关注结果,下一节将介绍声明初始化组件) :
1 2 3 4 5 // 报错日志 java.lang.RuntimeException: Unable to get provider androidx.startup.InitializationProvider: androidx.startup.StartupException: androidx.startup.StartupException: java.lang.NullPointerException: Attempt to invoke interface method 'boolean java.util.List.isEmpty()' on a null object reference
我们发现程序报错了,故dependencies返回值不能为null,至于原因将在原理篇中具体说明,记住此处【伏笔1】。
注册初始化器提供者 上文提到,仅编写初始化器并不能完成组件初始化,显然的,我们没有配置其执行的时机。
应用程序启动包括一个叫做InitializationProvider的特殊内容提供程序,它用来发现和调用你的组件初始化器。应用启动时通过首先检查InitializationProvider清单项下的项来发现组件初始化器。然后,App Startup为它已经发现的任何初始化器调用dependencies()方法。 这意味着,为了让组件初始化器在应用启动时被发现,必须满足以下条件之一:
组件初始化器在InitializationProvider清单项下有一个对应的项。
组件初始化器在dependencies()方法中列出,它来自一个已经可以发现的初始化器。
此案例中,为了确保应用程序启动时可以发现这些初始化器,请将以下内容添加到清单文件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 <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.tinlone.startupexamplejava.initializers.CacheInitializer" android:value="androidx.startup" /> <meta-data android:name="com.tinlone.startupexamplejava.initializers.MapInitializer" android:value="androidx.startup" /> </provider>
上述代码将初始化器传递给初始化提供者。
若初始化链有多个,类似本示例中包含:
Logger -> TXMap
Logger -> DatabaseHelper -> DatabaseProxy
以上两条独立的链,故此处为每条独立的链的链尾初始化器传递给InitializationProvider,为何要这样写呢?我们也将在源码篇中具体探讨,记住此处【伏笔2】.
手动初始化组件 当然,如果你不想InitializationProvider自动初始化某些组件,你也可以手动调用初始化流程,此时你应该这样做:
1 2 <meta-data android:name="com.example.ExampleLoggerInitializer" tools:node="remove" />
在需要初始化的时机调用以下代码,以Logger为例:
1 2 AppInitializer.getInstance(context) .initializeComponent(LoggerInitializer.class);
您在条目中使用工具:node=”remove”,而不是简单地删除条目,以确保合并工具也从所有其他合并的清单文件中删除条目。
注意:禁用组件的自动初始化也会禁用该组件的依赖项的自动初始化。
若你想完全禁止组件自动初始化,你可以将InitializationProvider
组件声明为remove
:
1 2 3 4 <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" tools:node="remove" />
并在需要初始化的时机调用以下代码:
1 2 AppInitializer.getInstance(context) .initializeComponent(ExampleLoggerInitializer.class);
执行 StartUp的使用仅需完成上述两个步骤即可,让我们来看一下器执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 I/loglog: create: LoggerInitializer I/loglog: Logger initialized I/loglog: create: SomethingInitializer I/loglog: create: MapInitializer I/loglog: Map initialized I/loglog: create: DatabaseHelperInitializer I/loglog: DatabaseHelper initialized I/loglog: create: DatabaseProxyInitializer I/loglog: DatabaseHelper initialized I/loglog: DatabaseProxy initialized I/loglog: create: CacheInitializer I/loglog: Cache initialized
我们在AndroidManifest中声明的是链尾的初始化器CacheInitializer
及MapInitializer
, 他却是从依赖链的头开始执行的初始化,他是如何做到完全符合依赖链顺序执行的呢? 让我们来简单看一下其原理。
源码分析 上面使用篇中我们埋下了一些伏笔和问题:
为什么dependencies返回值不能为null?
为什么要将每条独立的链的链尾初始化器传递给InitializationProvider?
他是如何做到完全符合依赖链顺序执行的?
为解答这些问题,我们先从初始化的入口类InitializationProvider
看起。
InitializationProvider 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @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 ; } }
InitializationProvider
内容相对简单,其作为内容提供者在初始化时调用了 AppInitializer.getInstance(context).discoverAndInitialize()
去扫描初始化器配置并执行后续操作。
AppInitializer AppInitializer 维持了一个单例结构,主要包含两个重要方法discoverAndInitialize
及doInitialize
, 分别负责发现初始化器及执行初始化器,我们一一看来。
discoverAndInitialize() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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(); } }
由代码可知,其主要做了以下事情:
创建组件标识符,获取InitializationProvider信息
根据组件标识符从包管理器中取得组件,并获取组件元数据
取得元数据, 取出元数据中 key的集合
遍历元数据集合(ArrayMap)中的数据,校验是否为androidx_startup元数据
根据元数据key中的全类名生成字节码对象,校验字节码对象是否是 Initializer 的实现类
将Initializer 的实现类 字节码对象加入变量mDiscovered的Set中备用
执行初始化
doInitialize() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <T> T doInitialize( @NonNull Class<? extends Initializer<?>> component, @NonNull Set<Class<?>> initializing) { synchronized (sLock) { boolean isTracingEnabled = Trace.isEnabled(); try { if (isTracingEnabled) { // 这里使用simpleName,因为否则节名会太大。 Trace.beginSection(component.getSimpleName()); } // 保证初始化链无环,initializing为执行记录集合 // 从discoverAndInitialize中传递的initializing值为空,故此判断不会执行 // 从本函数递归执行传递的是已包含执行的初始化器,若此时初始化器有重复,说明相互依赖关系成环了,应抛错 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(); // 若此初始化器有相关依赖 // 是否记得【伏笔1】有提到为何dependencies不能返回null吗? // 此处dependencies取得的值并未作空判断,故 null.isEmpty()会报空指针异常 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(); } } }
由代码可知,其主要做了以下事情:
首先根据单链执行记录判断此链中是否有依赖关系成环,成环则抛错
根据初始换执行完成的记录判断(被多次依赖的)依赖是否已被初始化,已初始化则跳过
将需要执行的初始化器字节码对象加入执行记录中备用,用以判断依赖链中是否成环
调用 initializer.dependencies() 获取初始化器依赖关系
若此初始化器有其他依赖则遍历依赖递归调用 doInitialize() 执行至依赖链的链头的初始化
链执行完毕,则移除此链的执行记录
链执行完毕,将元数据中声明的初始化器加入至已完成集合中,用以避免重复初始化
那么至此,我们似乎可以解释此节初始留下的三个伏笔问题了。
为什么dependencies()返回值不能为null?
答:因为在执行初始化前,会判断当前初始化器的dependencies是否为空,但此时使用的时 List.isEmpty() 并未对dependencies判空,故而,若dependencies()返回null,会导致null.isEmpty()的调用,导致空指针异常。
为什么要将每条独立的链的链尾初始化器传递给InitializationProvider?
答:因为可以根据链尾的初始化器可以通过遍历递归dependencies()获取该链所有以来的初始化器,从而找到链首的初始化器,然后从头至尾折叠递归运算,同时也保证了依赖链顺序执行
他是如何做到完全符合依赖链顺序执行的?
答同问题2