前言 
应用程序启动库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