深入探索Android 启动优化(七) - JetPack App Startup 使用及源码浅析

本文首发我的微信公众号:徐公,想成为一名优秀的 Android 开发者,需要一份完备的 知识体系,在这里,让我们一起成长,变得更好~。
前言
前一阵子,写了几篇 Android 启动优化的文章,主要是从两个方面论述的。
- Application 多线程异步加载,以及怎么解决多线程任务依赖的问题
- 首页布局优化,从常规的布局嵌套优化到渐进式加载,再到异步加载。
Android 启动优化(二) - 拓扑排序的原理以及解题思路
Android 启动优化(三)- AnchorTask 开源了
Android 启动优化(四)- AnchorTask 是怎么实现的
Android 启动优化(五)- AnchorTask 1.0.0 版本正式发布了
发布在掘金之后,几篇文章都被推荐上了掘金首页,深得大家的喜欢,阅读量也挺不错的。
有不少公众号粉丝在后台问我 JetPack App Startup 是什么,跟我开源的 AnchorTask 有什么区别?
今天,就让我们来聊一聊 JetPack App Startup。
目录大概是这样的
1 什么是 JetPack App Startup 2 JetPack App Startup 能解决什么问题 3 JetPack App Startup 基本使用 4 JetPack App Startup 进阶使用 5 JetPack App Startup 源码浅析 6 小结
什么是 JetPack App Startup
我们先来看一下官方的解释,官方地址:developer.android.com/topic/libra…
The App Startup library provides a straightforward, performant way to initialize components at application startup. Both library developers and app developers can use App Startup to streamline startup sequences and explicitly set the order of initialization.
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.
翻译过来就是:
- App Startup 这个库提供了一个组件,可以在应用程序启动的时候初始化。
- 开发人员可以使用这个组件精简启动序列和显式地设置初始化的顺序。
- 我们不需要为每个组件定义单独的 ContentProvider,App Startup 允许您定义的所有组件化共享一个内容提供者。这样可以极大地减少高应用程序的启动时间
JetPack App Startup 能解决什么问题
听了上面的介绍,是不是还有点懵?
App Startup 能减少高应用程序的启动时间,它是怎么做到的?
做过 Android 启动优化的,可能都知道,Android 的启动流程是这样的。

从 Application#attachBaseContext 到 ContentProvider#onCreate,到 Application#onCreate 再到 MainActivity#onCreate。
而 App Startup 设计的初衷,正是为了收拢 ContentProvider。有不少第三方的 SDk,为了使用者不必手动调用 SDK#init 方法,使用了 ContentProvider 这一个骚操作。
在 AndroidManifest 里面注册了自己的 xxSDkProvider,然后在 xxSDkProvider 的 onCreate 方面里面进行初始化,确实调用者不需要自己初始化了,可却增加了启动耗时,如果要作优化,还得自己剔除 ContentProvider 的初始化,值不值得,我是感觉没必要,这操作是真的骚。
<application ...>
<provider
android:name=".xxSDkProvider"
android:authorities="${applicationId}.xxSDkProvider"
android:exported="false" />
</application>
class XXSDKProvider : ContentProvider() {
override fun onCreate(): Boolean {
Log.d(TAG, "XXSDKProvider create()")
XXSDK.init()
return true
}
.....
}
同时,这里给做启动优化的同学提供了一种思路。打开你的 Apk,看一下 AndroidManiest 里面有多少 provider,看一下是否有这样的骚操作。如果有,改一下,说不定启动优化,一下子就减少了 100 多 毫秒。
接下来,我们来看一下 AppStartUp 怎么使用
AppStartUp 基本使用
简单来说,分为三步
- gradle 文件引入App Startup 库。
- 自定义一个用于初始化的 Initializer。
- 将自定义 Initializer 配置到 AndroidManifest.xml 当中。
第一步,在 build.gradle 文件添加依赖
dependencies {
implementation "androidx.startup:startup-runtime:1.0.0"
}
第二步:自定义实现 Initializer 类
主要有两个方法
T create(@NonNull Context context)初始化一个组件,返回给 ApplicationList<Class<? extends Initializer<?>>> dependencies()当前的 Initializer 依赖于哪些 Initializers,通过这个可以确定先后启动的顺序
我们以官方的例子来讲解
// 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()
}
}
WorkManagerInitializer 返回一个 WorkManager,它不需要依赖于其他的 Initializer,直接返回 emptyList() 即可。
如果需要依赖其他的 Initializer,重写 dependencies 方法,返回即可。如下面的 ExampleLoggerInitializer 依赖于 WorkManagerInitializer
// 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)
}
}
class ExampleLogger(val workManager: WorkManager){
}
第三步:在 AndroidManifest 里面配置自定义的 InitializationProvider
<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.xj.anchortask.appstartup.ExampleLoggerInitializer"
android:value="androidx.startup" />
</provider>
它是有固定格式的,配置者只需要配置 meta-data 中的 name 即可。 android:name="com.xj.anchortask.appstartup.ExampleLoggerInitializer" 这里的 name 是我们自定义的 Initializer 全路径。
程序运行跑起来,可以看到以下输出结果,符合我们的预期
2021-04-17 17:48:42.049 28059-28059/com.xj.anchortask I/AnchorTaskApplication: attachBaseContext: 2021-04-17 17:48:42.077 28059-28059/com.xj.anchortask I/AnchorTaskApplication: create: WorkManagerInitializer init 2021-04-17 17:48:42.077 28059-28059/com.xj.anchortask I/AnchorTaskApplication: create: ExampleLoggerInitializer init 2021-04-17 17:48:42.084 28059-28059/com.xj.anchortask I/AnchorTaskApplication: onCreate:
AppStartUp 进阶使用
手动初始化
上面我们讲解了 AppStartUp 的基本使用步骤,如果我们不像在 Application onCreate 之前执行我们的 ExampleLoggerInitializer,要怎么使用呢?
其实很简单,
- 第一步,在 AndroidManifest InitializationProvider 中移除 移除
<meta-data标签 - 在代码中调用 AppInitializer initializeComponent 方法初始化
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
</provider>
AppInitializer.getInstance(context).initializeComponent(ExampleLoggerInitializer::class.java)
App start up 源码分析
我们首先来看一下他的结构,只有简单的几个类

Initializer 这个接口就没有必要说了,很简单,只有两个方法。
InitializationProvider 继承了 ContentProvider,借助了 ContentProvider 会在 Application onCreate 之前执行的特点。来执行一些初始化操作。
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;
}
----
}
我们可以看到在 onCreate 方法中调用 AppInitializer discoverAndInitialize 方法进行初始化。
- 找到 AndroidManifest InitializationProvider 下的 meta 便签
- 判断 meta 便签下 value 的值是不是 androidx.startup
- 判断是不是实现 Initializer 接口,是的话,执行 doInitialize 方法
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);
// 找到 metadata 标签
if (metadata != null) {
Set<Class<?>> initializing = new HashSet<>();
Set<String> keys = metadata.keySet();
for (String key : keys) {
String value = metadata.getString(key, null);
// 判断 value 的值是不是 androidx.startup
// 判断是不是实现了 Initializer 接口,是的话,反射初始化
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();
}
}
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();
}
}
}
可以看到在执行初始化的时候,先判断了是否有依赖项,有的话先执行依赖项的初始化
小结
- App start up,我觉得他的设计初衷应该是为了收拢 ContentProvider,实际上对启动优化的帮助不是很大。
- 如果你的项目都是同步初始化的话,并且使用到了多个ContentProvider,App Startup可能有一定的优化空间,毕竟统一到了一个ContentProvider中,同时支持了简单的顺序依赖。
- ContentProvider 初始化的这个思想,目前有挺多 SDK 这么做的,像 FaceBook 广告 SDK,友盟 SDk 等。我们在启动优化的时候,是不是可以去掉相应的 ContentProvider,减少创建 Provider 的时间
- 实际项目中 启动优化,大多数啊都会使用多线程异步加载,这时候 App start up 就显得很鸡肋了,没用
参考博客: Jetpack新成员,App Startup一篇就懂
本文收录于 github.com/gdutxiaoxu/… 「Android学习+面试指南」一份涵盖大部分 Android 程序员所需要掌握的核心知识。准备 Android 面试,首选 AndroidGuide!微信公众号:程序员徐公
如果你觉得对你有所帮助,给我点个赞+关注支持一下吧~~
找到我
这篇文章,加上一些 Demo,足足花了我几个晚上的时间,我是站在巨人的肩膀上成长起来的,同样,我也希望成为你们的巨人。觉得不错的话可以关注一下我的微信公众号程序员徐公,在此感谢各位大佬们。主要分享
1.Android 开发相关知识:包括 java,kotlin, Android 技术。
2.面试相关分享:包括常见的面试题目,大厂面试真题、面试经验套路分享。
3.算法相关学习笔记:比如怎么学习算法,leetcode 常见算法总结,跟大家一起学习算法。
4.时事点评:主要是关于互联网的,比如小米高管屌丝事件,拼多多女员工猝死事件等
希望我们可以成为朋友,成长路上的忠实伙伴!

深入探索Android 启动优化(七) - JetPack App Startup 使用及源码浅析的更多相关文章
- Android Studio 一个完整的APP实例(附源码和数据库)
前言: 这是我独立做的第一个APP,是一个记账本APP. This is the first APP, I've ever done on my own. It's a accountbook APP ...
- Android开发之Theme、Style探索及源码浅析
1 背景 前段时间群里有伙伴问到了关于Android开发中Theme与Style的问题,当然,这类东西在网上随便一搜一大把模板,所以关于怎么用的问题我想这里也就不做太多的说明了,我们这里把重点放在理解 ...
- Android源码浅析(四)——我在Android开发中常用到的adb命令,Linux命令,源码编译命令
Android源码浅析(四)--我在Android开发中常用到的adb命令,Linux命令,源码编译命令 我自己平时开发的时候积累的一些命令,希望对你有所帮助 adb是什么?: adb的全称为Andr ...
- Android源码浅析(二)——Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境
Android源码浅析(二)--Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境 接着上篇,上片主要是介绍了一些安装工具的小知识点Andr ...
- Android中Canvas绘图基础详解(附源码下载) (转)
Android中Canvas绘图基础详解(附源码下载) 原文链接 http://blog.csdn.net/iispring/article/details/49770651 AndroidCa ...
- Android源码浅析(五)——关于定制系统,如何给你的Android应用系统签名
Android源码浅析(五)--关于定制系统,如何给你的Android应用系统签名 今天来点简单的我相信很多定制系统的同学都会有一些特定功能的需求,比如 修改系统时间 静默安装 执行某shell命令 ...
- Android源码浅析(一)——VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置
Android源码浅析(一)--VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置 最近地方工作,就是接触源码的东西了,所以好东西还是要分享,系列开了这 ...
- Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析
Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...
- Android 手势识别类 ( 三 ) GestureDetector 源码浅析
前言:上 篇介绍了提供手势绘制的视图平台GestureOverlayView,但是在视图平台上绘制出的手势,是需要存储以及在必要的利用时加载取出手势.所 以,用户绘制出的一个完整的手势是需要一定的代码 ...
- Android手势源码浅析-----手势绘制(GestureOverlayView)
Android手势源码浅析-----手势绘制(GestureOverlayView)
随机推荐
- 文心一言 VS 讯飞星火 VS chatgpt (138)-- 算法导论11.4 2题
二.用go语言,试写出HASH-DELETE 的伪代码;修改 HASH-INSERT,使之能处理特殊值DELETED. 文心一言: 首先,让我们定义一个基本的哈希表数据结构.这个结构将包括一个存储键值 ...
- C语言已知四位数3025具有一个特殊性质:它的前两位数字30与后两位数字25之和是55,而55的平方正好等于3025。编程找出所有具有这种性质的四位数。
#include<stdio.h> void main() { int n, i, j; for (n = 1000; n < 10000; n++) { i = n / 100; ...
- Sealos 云操作系统私有化部署教程
Sealos 私有云已经正式发布了,它为企业用云提供了一种革命性的新方案.Sealos 的核心优势在于,它允许企业在自己的机房中一键构建一个功能与 Sealos 公有云完全相同的私有云.这意味着企业可 ...
- 旺店通·企业奇门与用友BIP旺店通销售出库单对接销售订单
源系统平台:旺店通·企业奇门 源系统接口: 查询销售出库单wdt.stockout.order.query.trade 目标系统平台: 用友BIP目标系统接口: 销售订单单个保存/yonbip/sd/ ...
- Kylin Linux Advanced Server V10 上安装 Nacos详细步骤
要在 Kylin Linux Advanced Server V10 上安装 Nacos,可以按照以下进行操作:1.安装 Java JDK:首先确保已在 Kylin Linux Advanced Se ...
- [AGC038E] Gachapon
Problem Statement Snuke found a random number generator. It generates an integer between $0$ and $N- ...
- 启发式搜索(heuristic search)———A*算法
在宽度优先和深度优先搜索里面,我们都是根据搜索的顺序依次进行搜索,可以称为盲目搜索,搜索效率非常低. 而启发式搜索则大大提高了搜索效率,由这两张图可以看出它们的差别: (左图类似与盲搜,右图为启发式搜 ...
- 使用jvm工具排查系统问题
java-jvm-tool Jstatd 远程连接(推荐) 不用重启项目 远程机配置 [demo@localhost jvmtest]$ vi jstatd.all.policy# 内容grant c ...
- Feign源码解析:初始化过程(二)
背景 上一篇介绍了Feign源码初始化的一部分,内容主要是,@EnableFeignClients.@FeignClient这些注解,都支持设置一些自定义的配置类: A custom @Configu ...
- 11 个步骤完美排查Linux服务器是否被入侵
文章来源公众号:LemonSec 随着开源产品的越来越盛行,作为一个Linux运维工程师,能够清晰地鉴别异常机器是否已经被入侵了显得至关重要,个人结合自己的工作经历,整理了几种常见的机器被黑情况供参考 ...