Jetpack 全家桶之 App Startup 看完源码后真不是你们说的那样
Posted 工匠若水
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack 全家桶之 App Startup 看完源码后真不是你们说的那样相关的知识,希望对你有一定的参考价值。
背景
我们都知道,Application 初始化一直是安卓开发中被诟病最多的问题之一,尤其是 app 支持多进程且航母级应用场景下。随着业务迭代,初始化代码控制不到位的情况下是灾难性的,后人不敢随意挪动位置,或者说因为时机太早且为 app 启动必经之路,每次修改的影响面都很难评估,造成的启动性能影响也很严重。
此时可能很多小伙伴觉得 Jetpack 的 App Startup 库就是解决上面这段话里的问题的,因为他们觉得官方库介绍里说:The App Startup library provides a straightforward, performant way to initialize components at application startup.
,我想说你被带偏了,请和我一起给 App Startup 洗脱冤屈。
不要再神话或误解 AppStartup
国内很多开发者博客也存在误导,都在大肆宣扬认为 App Startup 完美的解决了启动初始化问题。我想说,求你们不要再误导了,因为实质真的不是这样,首先官方也说了:Instead of defining separate content providers for each component you need to initialize, App Startup allows you to define component initializers that share a single content provider. This can significantly improve app startup time.
,他们主要解决的是多 SDK 多模块各自一个 ContentProvider 带来的性能问题,而不是初始化托管框架问题。简单说,AppStartup 只解决了下图所示问题:
所以,直白点说就是,官方做了一套约束规则,各家 SDK、各个模块想要提供业务方无感知的初始化时不要再自己内部通过自己各自独有的 ContentProvider 实现,而是继承Initializer<T>
实现一个自己的约定即可,最终 App module 会打包进一个共享的 ContentProvider,这样就避免了多个 ContentProvider 的问题,这才是 App Startup 解决的核心问题,至于提供的 dependencies 依赖能力只是其附属特性而已。
还是理解不了的话,你就想想这么一个典型场景:
我是 SDK 开发者,最终给你的是 aar 依赖,我为了减少你的接入成本,我会将 SDK 初始化不对你显式提供,而是通过 SDK 内部的 ContentProvider onCreate 方法实现(因为 ContentProvider 是由 ActivityThread 负责启动的,ActivityThread 对应应用进程的主线程,即在应用进程启动时会将 ContentProvider 启动起来,所以其时机位于 Application 的 attachBaseContext 与 onCreate 之间。),也只有这样我才能做到对你无感知,但是带来的坏处就是如果你接入的十个 SDK 都是类似我这样给你实现的,你启动时不就被调用了十个 ContentProvider,这代价就有点大了。
但是思来想去,我没有别的对你无感知还能初始化时这么巧妙的时机借用啊!怎么办?App Startup 就解决了这个问题,他巧妙的发扬了面向接口编程的特性与 App 构建清单文件自动 merge 的特性使这个问题得到了解决,就如上图那样。
使用进阶
一般自动用法
继承 Initializer 实现两个 SDK 初始化,ExampleLogger 初始化依赖 WorkManager,如下:
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober】
// Initializes WorkManager.
class WorkManagerInitializer : Initializer<WorkManager>
override fun create(context: Context): WorkManager
val configuration = Configuration.Builder().build()
WorkManager.initialize(context, configuration)
return WorkManager.getInstance(context)
override fun dependencies(): List<Class<out Initializer<*>>>
// No dependencies on other libraries.
return emptyList()
// Initializes ExampleLogger.
class ExampleLoggerInitializer : Initializer<ExampleLogger>
override fun create(context: Context): ExampleLogger
// WorkManager.getInstance() is non-null only after
// WorkManager is initialized.
return ExampleLogger(WorkManager.getInstance(context))
override fun dependencies(): List<Class<out Initializer<*>>>
// Defines a dependency on WorkManagerInitializer so it can be
// initialized after WorkManager is initialized.
return listOf(WorkManagerInitializer::class.java)
清单文件声明(被依赖的其他 Initializer 可以不用显式声明,lint 检查会 check 这项):
<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>
一般手动用法
除过上面自动用法,我们还可以手动使用,手动使用需要先将清单 meta 中的 remove,然后在你想初始化的地方如下调用:
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober】
AppInitializer.getInstance(context)
.initializeComponent(ExampleLoggerInitializer::class.java)
清单文件移除写法如下:
<!-- 移除所有 -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="$applicationId.androidx-startup"
tools:node="remove" />
<!-- 移除指定的 Initializer -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="$applicationId.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.ExampleLoggerInitializer"
tools:node="remove" />
</provider>
特殊用法
有时你可能会有这么一种需求,三方 SDK 提供了 AarExampleInitializer(dependencies 为空),其 aar 的清单文件也声明了类似如下:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="$applicationId.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.AarExampleInitializer"
android:value="androidx.startup" />
</provider>
你想在你用这个 aar 的模块中让 AarExampleInitializer 初始化依赖其他 SDK 初始化,那你需要在你自己的模块中写一个OverAarExampleInitializer extends AarExampleInitializer
(AarExampleInitializer 一定可以被继承,因为 App Startup 要求 Initializer 是 public 的),然后重写其 dependencies 方法为你想依赖的,接着在你自己主模块的清单中如下如下操作:
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober】
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="$applicationId.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.OverAarExampleInitializer"
android:value="androidx.startup" />
<meta-data android:name="com.example.AarExampleInitializer"
tools:node="remove" />
......
如上即可实现需求。反之你想让其他模块初始化依赖 AarExampleInitializer,则只用在其他模块中依赖声明即可。
源码分析
App Startup 包的整体源码结构如下:
下面我们分析下其源码,首先按照使用来说,我们需要先实现的是各个 SDK 的Initializer<T>
初始化约定,如下:
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober】
/**
* 想要被初始化 SDK 的初始化包装接口约定
*/
public interface Initializer<T>
/**
* 该方法实现 SDK 的初始化,返回 SDK API 对象,一般是单例的
* 返回值 NonNull 非空
*/
@NonNull
T create(@NonNull Context context);
/**
* 该 SDK 初始化依赖的其他 SDK 初始化(Initializer)列表
* 返回值 NonNull 非空
*/
@NonNull
List<Class<? extends Initializer<?>>> dependencies();
当我们实现了Initializer<T>
后如果采用自动用法,则 app startup 巧妙的利用了 ContentProvider 初始化时机实现,其源码清单中如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="androidx.startup" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="30" />
<application>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="$applicationId.androidx-startup"
android:exported="false"
tools:node="merge" />
</application>
</manifest>
可以看到,其巧妙的利用了 merge node 进行多个模块的合并,并且其提供了唯一的androidx.startup.InitializationProvider
进行统管。我们每个Initializer<T>
都是其节点内部的一个 meta-data 项,如下:
<meta-data android:name="com.example.OverAarExampleInitializer"
android:value="androidx.startup" />
这些 meta-data 最终都会在androidx.startup.InitializationProvider
中进行解析调用,由于 app 启动时 InitializationProvider 会被自动调用,所以接下来我们将流程转到 InitializationProvider,如下:
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober】
/**
* InitializationProvider 用来在 onCreate 中扫描遍历发现 Initializer
* @hide
*/
@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;
......
//query、getType、insert、delete、update 实现都是抛出 IllegalStateException("Not allowed.");,不再给出。
很显然,我们需要将视野挪到AppInitializer.getInstance(context).discoverAndInitialize();
的实现源码,AppInitializer 类的代码也就是 App Startup 框架的精华了,如下:
public final class AppInitializer
private static final String SECTION_NAME = "Startup";
private static volatile AppInitializer sInstance;
private static final Object sLock = new Object();
//已初始化 Initializer 集合
final Map<Class<?>, Object> mInitialized;
//被自动发现的 Initializer 列表
final Set<Class<? extends Initializer<?>>> mDiscovered;
final Context mContext;
/**
* 非 public 构造方法,让 context 赋值为 getApplicationContext
*/
AppInitializer(@NonNull Context context)
mContext = context.getApplicationContext();
mDiscovered = new HashSet<>();
mInitialized = new HashMap<>();
/**
* double check 单例模式
* 对外使用 AppInitializer.getInstance(ctx) 获取 单例的 AppInitializer 实例对象
*/
@NonNull
@SuppressWarnings("UnusedReturnValue")
public static AppInitializer getInstance(@NonNull Context context)
if (sInstance == null)
synchronized (sLock)
if (sInstance == null)
sInstance = new AppInitializer(context);
return sInstance;
/**
* 初始化一个指定 Initializer<T> 的 SDK,返回值为 Initializer<T> 接口中 create 方法的返回值
*/
@NonNull
public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component)
return doInitialize(component, new HashSet<Class<?>>());
/**
* 判断指定的 Initializer<T> 实现 SDK 是否被饥汉式初始化过(即是否被自动初始化方式初始化过)。
* 这里的饥汉式初始化和单例模式道理一样,即一开始不管三七二十一先把对象 new 出来,
* 而不是第一次使用时才 new;对应到这里就是是否有被 AppInitializer.getInstance(context).discoverAndInitialize() 处理过。
*/
public boolean isEagerlyInitialized(@NonNull Class<? extends Initializer<?>> component)
// If discoverAndInitialize() was never called, then nothing was eagerly initialized.
return mDiscovered.contains(component);
/**
* 真正的初始化实现方法
* 第一个参数为 Initializer 接口的实现 class 对象;
* 第二个参数为一个初始化临时集合,用来递归去重对比,防止初始化存在循环依赖导致死循环;
*/
@NonNull
<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;
//如果这次递归依赖 Initializer 从来没被初始化过,则开始初始化
if (!mInitialized.containsKey(component))
//先加入这次递归依赖初始化的临时 set 中
initializing.add(component);
try
//实例化一个对应的 Initializer 对象
Object instance = component.getDeclaredConstructor().newInstance();
Initializer<?> initializer = (Initializer<?>) instance;
//得到对应的 Initializer 的 dependencies 方法的返回值
List<Class<? extends Initializer<?>>> dependencies =
initializer.dependencies();
//这里没判断 null,所以 Initializer 的 dependencies 方法一定不能返回 null,即便没依赖也得返回一个 emptyList 集合。
if (!dependencies.isEmpty())
//遍历当前 Initializer 的 dependencies 依赖 Initializer 列表
for (Class<? extends Initializer<?>> clazz : dependencies)
//如果依赖的 Initializer 还没有被初始化过,则递归调用初始化
if (!mInitialized.containsKey(clazz))
doInitialize(clazz, initializing);
if (StartupLogger.DEBUG)
StartupLogger.i(String.format("Initializing %s", component.getName()));
//调用当次 Initializer 的 create
result = initializer.create(mContext);
if (StartupLogger.DEBUG)
StartupLogger.i(String.format("Initialized %s", component.getName()));
//递归回退 Initializer 的 dependencies 和 create 被调用后可以从临时集合移除。
initializing.remove(component);
//将初始化过的 Initializer 放入单例全局的已初始化集合中,方便下一次快速使用
mInitialized.put(component, result);
catch (Throwable throwable)
throw new StartupException(throwable);
else
//单例对象中已经记录过对应 Initializer 的初始化实例则直接返回已初始化对象
result = mInitialized.get(component);
return (T) result;
finally
Trace.endSection();
/**
* 被 androidx.startup.InitializationProvider 的 onCreate 自动调用。
* 去清单文件扫描 meta-data 信息然后初始化。
*/
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;
//startup="androidx.startup",所以每个 InitializationProvider 里的 meta-data 的 value 必须是这个值。
String startup = mContext.getString(R.string.androidx_startup);
//获取 AndroidManifest.xml 的 InitializationProvider 中 meta-data 信息
if (metadata != null)
Set<Class<?>> initializing = new HashSet<>();
//得到 InitializationProvider 里面所有的 android:name="com.example.OverAarExampleInitializer" 等注册的 Initializer 实现类
Set<String> keys = metadata.keySet();
for (String key : keys)
String value = metadata.getString(key, null);
//确保每个 meta-data 都是 android:value="androidx.startup"
if (startup.equals(value))
//得到每一个 Initializer 的 class 对象,譬如 com.example.OverAarExampleInitializer.class
Class<?> clazz = Class.forName(key);
if (Initializer.class.isAssignableFrom(clazz))
Class<? extends Initializer<?>> component =
(Class<? extends Initializer<?>>) clazz;
//把每一个 meta-data 发现的 Initializer 实现添加到 mDiscovered Set 集合中。
mDiscovered.add(component);
if (StartupLogger.DEBUG)
StartupLogger.i(String.Vue全家桶之VueX