美文网首页js css html
Jetpack-Startup解析和应用

Jetpack-Startup解析和应用

作者: 就叫汉堡吧 | 来源:发表于2022-11-23 09:51 被阅读0次
    • Startup

      添加依赖:

      dependencies {
          implementation("androidx.startup:startup-runtime:1.1.1")
      }
      

      manifest中加入:

      <provider
          android:name="androidx.startup.InitializationProvider"
          android:authorities="${applicationId}.androidx-startup"
          android:exported="false"
          tools:node="merge">
          <meta-data
              android:name="com.mph.review.startup.CCInitializer"
              android:value="androidx.startup" />
      </provider>
      

      merge属性可以确保和其他manifest合并后消除冲突;exported表示该content provider是私有的,provider中的其他属性都是固定的,authorities属性是根据applicationId确定的。

      meta-data标签中就是自定义Initializer放置的位置,可以定义多个meta-data标签声明多个Initializer,name是完整类名,value是固定的。

      class CCInitializer : Initializer<User> {
          override fun create(context : Context) : User {
              val uu = User("CC")
              uu.name = "CC"
              print("CC")
              return uu
          }
      
          override fun dependencies() : MutableList<Class<out Initializer<*>>> {
              return mutableListOf(PPInitializer::class.java)
          }
      }
      

      其中dependencies中定义的是该Initializer所依赖的其他Initializer,在初始化当前Initializer之前会先初始化它所有依赖的Initializer,比如这里的PPInitializer会比CCInitializer优先初始化,至于dependencies中返回的Initializer集合则会按照集合的顺序从头开始初始化,下面解析代码的时候会看到。

    • 解析

      在InitializationProvider创建的时候会调用它的onCreate方法:

      @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;
      }
      

      这里会调用AppInitializer的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;
                              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();
          }
      }
      

      首先会调用PackageManager的getProviderInfo方法获取meta-data信息,mContext.getString(R.string.androidx_startup)得到的字面值就是“androidx.startup”,遍历所有的meta-data,找出所有value是“androidx.startup”的,取它们的key,也就是manifest中meta-data中name属性的值作为类名,把它的Class对象存入mDiscovered集合,然后调用doInitialize方法进行初始化:

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

      结合代码我们看到,这里会先把当前Initializer的Class存入initializing中,然后通过反射构造当前Initializer,在调用它的create方法之前,会先调用它的dependencies方法,然后对它返回的集合进行递归doInitialize调用,这就是为什么依赖的Initializer会先初始化的原因。因为dependencies中返回的是List,所以这里的循环会按照List的顺序从头开始,所以dependencies集合中越靠前的会更先调用create方法初始化。

      注意,在discoverAndInitialize方法中调用doInitialize方法时传入的是一个空的Set集合,然后在doInitialize方法中递归传递的也都是这个Set,你可能会说Set不是无序的嘛,其实这里的Set只是为了确保不添加重复的数据,因为整个过程中只调用了它的add和remove方法,没有取过数据,所以Set的无序性是无影响的,这里的用处只是暂存待初始化的Initializer而已,你可以把它换成一个类变量,是一样的作用,因为doInitialize方法是在discoverAndInitialize中开启调用的,所以递归初始化完成后最终还是会回到discoverAndInitialize方法,所以这里使用一个方法内变量作为共享变量是更合理的,使用类变量就有些重量级了。

      AppInitializer.getInstance(this).initializeComponent(CCInitializer::class.java)
      

      最后,调用AppInitializer实例的initializeComponent方法就可以获取启动时已初始化好的相关实例了,在上面的流程中,初始化的实力会保存到mInitialized中,所以调用initializeComponent方法获取的时候会优先从mInitialized取,如果mInitialized中没有的话则会再次执行上述初始化流程,只不过此时执行的初始化只是针对单个的Initializer而已,可见,你也可以手动调用AppInitializer来完成初始化工作。

    • 应用场景

      为什么要使用Startup?

      因为ContenProvider的 onCreate 调用在 Application.attachBaseContext()Application.onCreate() 之间发生,所以很多在应用启动之后就要发挥作用的库都会选择通过在ContenProvider的onCreate方法中进行初始化,但是ContenProvider作为一个重量级组件,它本身的启动和初始化是需要一定时间的,所以如果ContenProvider数量过多会造成一定的性能损耗,所以Startup的目的就是把所有的用于初始化的ContenProvider都合并成一个,这样就减少了大量的ContenProvider创建性能损耗和内存占用。

      但是,Startup无法作用于你所依赖的第三方库,因为你没有权限去修改第三方库的manifest文件,所以第三方库中的ContenProvider还是会存在,Startup只能使你自定义的初始化工作合并到一个ContenProvider中去,所以它的场景是:

      在你需要创建一个需要在启动时初始化的类库,或者你的app在启动时就需要很多本地初始化工作的时候,你可以选择使用Startup框架来处理,而且你可以通过Startup让初始化业务分门别类、顺序化。

      总之,Startup会在项目庞大或者有类库需要的时候才会有用武之地。

    相关文章

      网友评论

        本文标题:Jetpack-Startup解析和应用

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