/ 今日科技快讯 /
微软昨日宣布,将以75亿美元的现金收购游戏开发商Bethesda Softworks母公司ZeniMax Media。ZeniMax Media是世界上最大的私人持股游戏开发商和发行商之一。Bethesda Softworks广受游戏界好评,代表作包括角色扮演游戏《上古卷轴》和《辐射》等。
/ 作者简介 /
本篇文章来自叶志陈的投稿,分享了他对Startup框架的使用以及理解,或许会对大家有一定的借鉴意义。同时也感谢作者贡献的精彩文章!
叶志陈的博客地址:
https://juejin.im/user/923245496518439
/ 前言 /
Google Jetpack官网上新增了一个名为App Startup的组件。官方地址为:
https://developer.android.com/topic/libraries/app-startup
根据官方文档的介绍,App Startup提供了一种直接、高效的方式用来在应用程序启动时对多个组件进行初始化,开发者可以依靠它来显式地设置多个组件间的初始化顺序并优化应用的启动时间。
本文内容均基于App Startup当前最新的alpha版本:
implementation "androidx.startup:startup-runtime:1.0.0-alpha01"
/ AppStartup的意义 /
App Startup允许Library开发者和App开发者共享同一个ContentProvider来完成各自的初始化逻辑,并支持设置组件之间的初始化先后顺序,避免为每个需要初始化的组件都单独定义一个ContentProvider,从而大大缩短应用的启动时间。
目前很多第三方依赖库为了简化使用者的使用成本,就选择通过声明一个ContentProvider来获取Context对象并自动完成初始化过程。例如Lifecycle组件就声明了一个ProcessLifecycleOwnerInitializer用于获取context对象并完成初始化。而在AndroidManifest.xml文件中声明的每一个ContentProvider,在Application的onCreate()函数被调用之前就会预先被执行并调用内部的onCreate()方法。应用每构建并执行一个ContentProvider都是有着内存和时间的消耗成本,如果应用的ContentProvider过多,无疑会大大增加应用的启动时间。
因此,App Startup的存在,无疑是可以为很多依赖项(应用自身的组件和第三方组件)提供一个统一的初始化入口,当然这也需要等到App Startup发布 release 版本并被大多数三方依赖组件采用之后了。
/ 如何使用 /
假设我们的项目中一共有三个Library需要进行初始化。当中,Library A依赖于Library B,Library B依赖于Library C,Library C不需要其它依赖项,则此时可以分别为三个Library建立三个Initializer实现类。
Initializer是Startup提供的用于声明初始化逻辑和初始化顺序的接口,在 create(context: Context)方法中完成初始化过程并返回结果值,在dependencies()中指定初始化此Initializer前需要先初始化的其它 Initializer。
class InitializerA : Initializer<A> {
//在此处完成组件的初始化,并返回初始化结果值
override fun create(context: Context): A
{
return A.init(context)
}
//获取在初始化自身之前需要先初始化的 Initializer 列表
//如果不需要依赖于其它组件,则可以返回一个空列表
override fun dependencies(): List<Class<out Initializer<*>>>
{
return listOf(InitializerB::class.java)
}
}
class InitializerB : Initializer<B>
{
override fun create(context: Context): B
{
return B.init(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return listOf(InitializerC::class.java)
} }
class InitializerC : Initializer<C> {
override fun create(context: Context): C {
return C.init(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return listOf()
}
}
Startup提供了两种初始化方法,分别是自动初始化和手动初始化(延迟初始化)。
自动初始化
在AndroidManifest文件中对Startup提供的InitializationProvider进行声明,并且用meta-data标签声明Initializer实现类的包名路径,value必须是 "androidx.startup"。在这里我们只需要声明InitializerA即可,因为InitializerB和InitializerC均可以通过InitializerA的dependencies()方法的返回值链式定位到。
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="leavesc.lifecyclecore.core.InitializerA"
android:value="androidx.startup" />
</provider>
只要完成以上步骤,当应用启动时,Startup就会自动按照我们规定的顺序依次进行初始化。需要注意的是,如果Initializer之间不存在依赖关系,且都希望由InitializationProvider为我们自动初始化的话,此时所有的Initializer就必须都进行显式声明,且Initializer的初始化顺序会和在provider中的声明顺序保持一致。
手动初始化
大部分情况下自动初始化的方式都能满足我们的要求,但在某些情况下并不适用,例如:组件的初始化成本(性能消耗或者时间消耗)较高且该组件最终未必会使用到,此时就可以将之改为在使用到的时候再来对其进行初始化了,即懒加载组件。
手动初始化的Initializer不需要在AndroidManifest中进行声明,只需要通过调用以下方法进行初始化即可。
val result = AppInitializer.getInstance(this).initializeComponent(InitializerA::class.java)
由于Startup内部会缓存Initializer的初始化结果值,所以重复调用initializeComponent方法不会导致多次初始化,该方法也可用于自动初始化时获取初始化结果值。如果应用内的所有Initializer都不需要进行自动初始化的话,也可以不在AndroidManifest中声明InitializationProvider。
/ 注意事项 /
移除Initializer
假设我们在项目中引入的某个第三方依赖库自身使用到了Startup进行自动初始化,我们希望将之改为懒加载的方式,但我们无法直接修改第三方依赖库的AndroidManifest文件,此时就可以通过AndroidManifest的合并规则来移除指定的Initializer。
假设第三方依赖库的Initializer的包名路径是xxx.xxx.InitializerImpl,在主项目工程的AndroidManifest文件中主动对其进行声明,并添加tools:node="remove"语句要求在合并AndroidManifest文件时移除自身,这样Startup就不会自动初始化InitializerImpl了。
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="leavesc.lifecyclecore.mylibrary.TestIn"
android:value="androidx.startup"
tools:node="remove" />
</provider>
禁止自动初始化
如果希望禁止Startup的所有自动初始化逻辑,但又不希望通过直接删除provider声明来实现的话,那么可以通过如上所述的方法来实现此目的。
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
Lint 检查
App Startup包含一组Lint规则,可用于检查是否已正确定义了组件的初始化程序,可以通过运行./gradlew :app:lintDebug来执行检查规则。例如,如果项目中声明的InitializerB没有在AndroidManifest中进行声明,且也不包含在其它Initializer的依赖项列表里时,通过Lint检查就可以看到如下的警告语句:
Errors found:xxxx\leavesc\lifecyclecore\core\InitializerHodler.kt:52:
Error: Every Initializer needs to be accompanied by a corresponding <meta-data> entry in the AndroidManifest.xml file.
[EnsureInitializerMetadata] class InitializerB : Initializer<B> {
/ 源码解析 /
Startup整个依赖库仅包含五个Java文件,整体逻辑比较简单,这里依次介绍下每个文件的作用。
StartupLogger
StartupLogger是一个日志工具类,用于向控制台输出日志。
public final class StartupLogger {
private StartupLogger() {
// Does nothing.
}
/** * The log tag. */
private static final String TAG = "StartupLogger";
/** * To enable logging set this to true. */
static final boolean DEBUG = false;
/** * Info level logging. * * @param message The message being logged */
public static void i(@NonNull String message) {
Log.i(TAG, message); }
/** * Error level logging * * @param message The message being logged * @param throwable The optional {@link Throwable} exception */
public static void e(@NonNull String message, @Nullable Throwable throwable) {
Log.e(TAG, message, throwable); }}
StartupException
StartupException是一个自定义的RuntimeException子类,当Startup在初始化过程中遇到意外之外的情况时(例如,Initializer存在循环依赖、Initializer反射失败等情况),就会抛出StartupException。
public final class StartupException extends RuntimeException {
public StartupException(@NonNull String message) {
super(message);
}
public StartupException(@NonNull Throwable throwable) {
super(throwable);
}
public StartupException(@NonNull String message, @NonNull Throwable throwable) {
super(message, throwable);
}}
Initializer
Initiaizer是Startup提供的用于声明初始化逻辑和初始化顺序的接口,在create(context: Context)方法中完成初始化过程并返回结果值,在dependencies()中指定初始化此Initializer前需要先初始化的其它Initializer。
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();}
InitializationProvider
InitializationProvider就是需要我们主动声明在AndroidManifest.xml文件中的ContentProvider,Startup的整个初始化逻辑都是在这里进行统一触发的。由于InitializationProvider的作用仅是用于统一多个依赖项的初始化入口并获得Context对象,所以除了onCreate()方法会由系统自动调用外,query、getType、insert、delete、update等方法本身是没有意义的,如果开发者调用了这几个方法就会直接抛出异常。
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;
}
@Nullable
@Override
public Cursor query(
@NonNull Uri uri,
@Nullable String[] projection,
@Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable String sortOrder) {
throw new IllegalStateException("Not allowed.");
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
throw new IllegalStateException("Not allowed.");
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
throw new IllegalStateException("Not allowed.");
}
@Override
public int delete(
@NonNull Uri uri,
@Nullable String selection,
@Nullable String[] selectionArgs) {
throw new IllegalStateException("Not allowed.");
} @Override
public int update(
@NonNull Uri uri,
@Nullable ContentValues values,
@Nullable String selection,
@Nullable String[] selectionArgs) {
throw new IllegalStateException("Not allowed.");
}}
AppInitializer
AppInitializer是Startup整个库的核心重点,整体代码量不足两百行,AppInitializer的整体流程是:
由InitializationProvider传入Context对象以此来获得AppInitializer唯一实例,并调用discoverAndInitialize()函数完成所有的自动初始化逻辑
discoverAndInitialize()函数会先对InitializationProvider进行解析,获取到包含的所有metadata,然后按声明顺序依次反射构建每个metadata指向的Initializer对象
当在初始化某个Initializer对象之前,会首先判断其关联的依赖项dependencies是否为空。如果为空的话则直接调用其 create(Context) 函数进行初始化。如果不为空的话则先对dependencies进行初始化,对每个dependency均重复此遍历操作,直到不包含dependencies的Initializer最先初始化完成后才原路返回依次进行初始化,从而保证了Initializer之间初始化顺序的有序性
当存在这几种情况时,Startup会抛出异常:Initializer实现类不包含无参构造函数、Initializer之间存在循环依赖关系、Initializer的初始化过程(create(Context) 函数)抛出了异常
AppInitializer对外开放了getInstance(@NonNull Context context)方法用于获取唯一的静态实例。
public final class AppInitializer {
/**
* 唯一的静态实例
* The {@link AppInitializer} instance.
*/
private static AppInitializer sInstance;
/**
* 同步锁
* Guards app initialization.
*/
private static final Object sLock = new Object();
//用于存储所有已进行初始化了的 Initializer 及对应的初始化结果
@NonNull
final Map<Class<?>, Object> mInitialized;
@NonNull
final Context mContext;
/**
* Creates an instance of {@link AppInitializer}
*
* @param context The application context
*/
AppInitializer(@NonNull Context context) {
mContext = context.getApplicationContext();
mInitialized = new HashMap<>();
}
/**
* @param context The Application {@link Context}
* @return The instance of {@link AppInitializer} after initialization.
*/
@NonNull
@SuppressWarnings("UnusedReturnValue")
public static AppInitializer getInstance(@NonNull Context context) {
synchronized (sLock) {
if (sInstance == null) {
sInstance = new AppInitializer(context);
}
return sInstance;
}
}
··· }
discoverAndInitialize()方法由InitializationProvider进行调用,由其触发所有需要进行默认初始化的依赖项的初始化操作。
@SuppressWarnings("unchecked")
void discoverAndInitialize() {
try {
Trace.beginSection(SECTION_NAME);
//获取 InitializationProvider 包含的所有 metadata
ComponentName provider = new ComponentName(mContext.getPackageName(),
InitializationProvider.class.getName());
ProviderInfo providerInfo = mContext.getPackageManager()
.getProviderInfo(provider, GET_META_DATA);
Bundle metadata = providerInfo.metaData;
//获取到字符串 androidx.startup
//因为 Startup 是以该字符串作为 metaData 的固定 value 来进行遍历的
//所以如果在 AndroidManifest 文件中声明了不同 value 则不会被初始化
String startup = mContext.getString(R.string.androidx_startup);
if (metadata != null) {
//用于标记正在准备进行初始化的 Initializer
//用于判断是否存在循环依赖的情况
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);
//确保 metaData 声明的包名路径指向的是 Initializer 的实现类
if (Initializer.class.isAssignableFrom(clazz)) {
Class<? extends Initializer<?>> component =
(Class<? extends Initializer<?>>) clazz;
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();
}
}
doInitialize()函数是实际调用了Initializer的create(context: Context)的地方,其主要逻辑就是通过嵌套调用的方式来完成所有依赖项的初始化,当判断出存在循环依赖的情况时将抛出异常。
@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 p names would get too big otherwise.
Trace.beginSection(component.getSimpleName());
}
if (initializing.contains(component)) {
//initializing 包含 component,说明 Initializer 之间存在循环依赖
//直接抛出异常
String message = String.format("Cannot initialize %s. Cycle detected.", component.getName());
throw new IllegalStateException(message);
}
Object result;
if (!mInitialized.containsKey(component)) {
//如果 mInitialized 不包含 component
//说明 component 指向的 Initializer 还未进行初始化
initializing.add(component);
try {
//通过反射调用 component 的无参构造函数并初始化
Object instance = component.getDeclaredConstructor().newInstance();
Initializer<?> initializer = (Initializer<?>) instance;
//获取 initializer 的依赖项
List<Class<? extends Initializer<?>>> dependencies =initializer.dependencies();
//如果 initializer 的依赖项 dependencies 不为空
//则遍历 dependencies 每个 item 进行初始化
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()));
}
//将已经进行初始化的 component 从 initializing 中移除掉
//避免误判循环依赖
initializing.remove(component);
//将初始化结果保存起来
mInitialized.put(component, result);
} catch (Throwable throwable) {
throw new StartupException(throwable);
}
} else {
//component 指向的 Initializer 已经进行初始化
//此处直接获取缓存值直接返回即可
result = mInitialized.get(component);
}
return (T) result;
} finally {
Trace.endSection();
}
}
}
/ AppStartup的不足点 /
App Startup 的优点我在上边已经列举了,最后再来列举下它的几个不足点。
InitializationProvider的onCreate()函数是在主线程被调用的,导致我们的每个Initializer默认就都是运行在主线程,这对于某些初始化时间过长,需要运行在子线程的组件来说就不太适用了。且Initializer的create(context: Context) 函数的本意是完成组件的初始化并返回初始化的结果值,如果在此处通过主动new Thread来运行耗时组件的初始化,那么我们就无法返回有意义的结果值,间接导致后续也无法通过AppInitializer获取到缓存的初始化结果值
如果某组件的初始化需要依赖于其它耗时组件(初始化时间过长,需要运行在子线程)的结果值,此时App Startup一样不适用
对于已经使用ContentProvider完成初始化逻辑的第三方依赖库,我们一般也无法直接修改其初始化逻辑(除非clone该项目导到本地直接修改源码),所以在初始阶段App Startup的意义主要在于统一项目本地组件的初始化入口,需要等到App Startup被大多数开发者接受并使用后,才更加具有性能优势
推荐阅读:
欢迎关注我的公众号
学习技术或投稿
长按上图,识别图中二维码即可关注