前言
之前我们说了启动优化的一些常用方法,但是有的小伙伴就很不屑了:
“这些方法很久之前就知道了,不知道说点新东西?比如App Startup?能对启动优化有帮助吗?”
ok,既然你诚心诚意的发问了,那我就大发慈悲的告诉你:俺也不知道
。
走吧,一起瞅瞅这个App Startup
吧,是不是真的能给我们的启动带来优化呢?
(想看结果的可以直接跳到最后的实践
和总结
阶段)
Contentprovider中初始化
想必大家都了解,很多三方库都需要在Application
中进行初始化,并顺便获取到Application
的上下文。
但是也有的库不需要我们自己去初始化,它偷偷摸摸就给初始化了,用到的方法就是使用ContentProvider
进行初始化,定义一个ContentProvider
,然后在onCreate拿到上下文,就可以进行三方库自己的初始化工作了。而在APP的启动流程中,有一步就是要执行到程序中所有注册过的ContentProvider
的onCreate方法,所以这个库的初始化就默默完成了。
这种做法确实给集成库的开发者们带来了很大的便利,现在很多库都用到了这种方法,比如Facebook,Firebase
,这里拿Facebook
举例看看他的ContentProvider:
public final class FacebookInitProvider extends ContentProvider {
private static final String TAG = FacebookInitProvider.class.getSimpleName();
@Override
@SuppressWarnings("deprecation")
public boolean onCreate() {
try {
FacebookSdk.sdkInitialize(getContext());
} catch (Exception ex) {
Log.i(TAG, "Failed to auto initialize the Facebook SDK", ex);
}
return false;
}
//...
}
可以看到,在Fackbook的sdk中,定义了一个FacebookInitProvider
,并且在onCreate
中进行了初始化。所以我们才无需单独对Facebook的sdk进行初始化。
虽然更方便了,但是这种做法有给启动优化带来什么好处吗?我们一起再回顾下之前的启动流程研究下,截取一部分:
- ...
- attachBaseContext
- Application attach
- installContentProviders
- Application onCreate
- Looper.loop
- Activity onCreate,onResume
这其中installContentProviders
方法就是用来启动并执行各个ContentProvider
的onCreate
方法的,它会在Application
的onCreate
方法之前执行。
所以这些库只是把Application
的三方库初始化工作提前放到ContentProvider
中了,并不会减少启动耗时,反而会增加启动耗时。
怎么说呢?因为不同的库就定义了不同的ContentProvider
类,多了这么多ContentProvider
,ContentProvider
作为四大组件之一,启动也是耗时的,自然也就增加App启动消耗的时间了。
这时候就需要App Startup
来对此情况进行优化了~
官网简介
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.
主要说了两点特性:
- 可以共享单个Contentprovider。
- 可以明确地设置初始化顺序。
可以共享单个Contentprovider
这一点功能就能解决刚才的问题了,不同的库不再需要去启动多个Contentprovider
了,而是共享同一个Contentprovider
。
这样就至少不会增加启动耗时了。
怎么操作呢?假如我们是FacebookSDK
设计者,我们就来改一下刚才的FacebookSDK
,集成App Startup
:
//导入库
implementation "androidx.startup:startup-runtime:1.0.0"
// Initializes facebooksdk.
class FacebookSDKInitializer : Initializer {
private val TAG = "FacebookSDKInitializer"
override fun create(context: Context): Unit {
try {
FacebookSdk.sdkInitialize(context)
} catch (ex: Exception) {
Log.i(TAG, "Failed to auto initialize the Facebook SDK", ex)
}
}
override fun dependencies(): List>> {
return emptyList()
}
}
//AndroidManifest.xml中定义
实现了Initializer
接口,然后在onCreate方法中进行初始化即可,只要所有的库都按照这个标准来初始化,而不是自己单独自定义ContentProvider
,那么确实可以减少启动耗时。
其中,tools:node="merge"
标签就是用来合并所有申明了InitializationProvider
的ContentProvider
。
等等,Initializer
接口还有一个方法dependencies,这又是干啥的呢?
可以明确地设置初始化顺序
这也就是App Startup的第二个特性了,可以设置初始化顺序。
可以想象,按照上述做法,所有库都这样设定了,那么都会在同一个ContentProvider
也就是androidx.startup.InitializationProvider
中初始化,但是如果我需要设定不同库的初始化顺序怎么办呢?
比如上述的facebook
初始化,我需要设定在另一个库WorkManager
之后运行,那么我们就可以重写dependencies
方法:
class FacebookSDKInitializer : Initializer {
private val TAG = "FacebookSDKInitializer"
override fun create(context: Context): Unit {
try {
FacebookSdk.sdkInitialize(context)
} catch (ex: Exception) {
Log.i(TAG, "Failed to auto initialize the Facebook SDK", ex)
}
}
override fun dependencies(): List>> {
return listOf(WorkManagerInitializer::class.java)
}
}
不错吧,这样设定之后,三方库的初始化顺序就变成了:
WorkManager初始化 -> FacebookSDK初始化。
实践出真理
说了这么多,从理论上来说,确实App Startup
减少了耗时,毕竟将多个ContentProvider
融合成了一个,那么我们秉着“实践才是检验真理的唯一标准”,就来实践看看耗时减少了多少。
该怎么统计这个启动时间呢?一般有以下几个方案:
如果是Application和Activity的时间可以通过
TraceView、systrace
等 的方式进行时间统计,但是ContentProvider
的初始化在Application之前,不适用我们这次实践。Android官方提供了一个可以统计线上应用启动时间的工具——
Android Vitals
,它可以在GooglePlay管理中心显示应用启动过长情况的启动时间,很显然这个也不适用于我们,这个必须上线到Googleplay
。视频录制。如果是线下的app,我们可以采用
视频录制
的方法准确测量启动时间,也就是通过判定视频的每一帧截图来知晓什么时候app启动了,然后统计这个启动时间。具体做法就是使用adb shell screenrecord
命令进行屏幕录制然后分析视频,有兴趣的小伙伴可以网上找找资料,这里就不细说了。最后,就是用系统自带的统计时间
TotalTime
。
这个时间是Android源码中帮我们计算的,可统计到Activity的启动时间,如果我们在Home页执行命令,也就能得到一个冷启动的时间。虽然这个时间不是很准确,但是我只需要比较App StartUp使用的的前后时间大小,所以也够用了,开干。
1)测试2个ContentProvider
第一次,我们测试2个ContentProvider
的情况。
安装到手机后,打开应用,Terminal
中输入命令:
adb shell am start -W -n packagename/packageName.MainActivity
由于每次启动时间不一,所以我们运行五次,取平均值:
TotalTime: 927
TotalTime: 938
TotalTime: 948
TotalTime: 934
TotalTime: 937
平均值:936.8
然后注释刚才的ContentProvider
注册代码,添加App startup代码,并注册:
运行App,并执行命令,得出启动时间:
TotalTime: 931
TotalTime: 947
TotalTime: 937
TotalTime: 940
TotalTime: 932
平均值:937.4
咦??我手机坏了吗?怎么跟预想的不一样啊,结果耗时还增加了?
按道理来说原来有两个ContentProvider
,用了App startup
,集成为一个,耗时不应该减少么。
其实这就涉及到ContentProvider
的实际耗时了,我在网上找到一张图,关于ContentProvider
耗时,是Google
官方做的统计,图片来源于郭神的博客:
可以看到这里统计的1个ContentProvider
耗时2ms
左右,10ContentProvider
耗时6ms左右。
所以我们只减少了一个ContentProvider的耗时,几乎可以忽略不计。再加上我们用到的App Startup库中InitializationProvider
的一些任务也会产生耗时,比如:
- 会去遍历所有
metadata
标签的组件 - 会通过反射获取每个组件的
Initializer
接口,并获取相应的依赖项,并进行排序
。
这些操作也是耗时的,也就是集成App Startup
库之后增加的耗时时间。所以就有可能会发生上面的情况了,集成App Startup库之后启动耗时反而增多。
那难道这个库就没用了吗?肯定不是的,当ContentProvider的数量变多,它的作用就体现出来了,再试下10个ContentProvider
的情况。
2)10个ContentProvider
首先写好10个ContentProvider
,并在AndroidManifest.xml中注册:
运行五次,取平均值:
TotalTime: 1758
TotalTime: 1759
TotalTime: 1733
TotalTime: 1737
TotalTime: 1747
平均值:1746.8
然后注释刚才的ContentProvider
注册代码,添加App startup代码,并注册:
运行App,并执行命令,得出启动时间:
TotalTime: 1741
TotalTime: 1755
TotalTime: 1722
TotalTime: 1739
TotalTime: 1730
平均值:1737.4
可以看到,这里App Startup的作用就体现了出来,在使用App Startup
之前的启动耗时是1746.8ms
,使用之后启动耗时是1737.4ms
,减少了9.4ms
。
所以得出结论,当集成的库使用的ContentProvider
达到一定个数之后,确实能减少耗时,但是减少的不多,比如这里我们是10个ContentProvider
集成App Startup
后能减少的耗时在10ms
左右,再结合上图官方的统计时间来看,一般一个项目集成了十几个使用ContentProvider的库,耗时减少应该能在20ms之内。
所以我们的App Startup
解决的就是这个耗时时间,虽然不多,但是也确实有减少耗时的功能。
思考
虽然这个库能解决一定的三方库初始化
耗时问题,但是我觉得还是有很大的局限性
,比如这些问题:
-
本身依赖的库就不多
。如果我们的项目本身依赖就不多,那么有没有必要去集成这个呢?极端情况下,只依赖了一个库,那么还要专门提供一个InitializationProvider,是不是又变相的增加了耗时呢? -
延时初始化
。上次我们说过,有些库并不需要一开始就初始化,那么我们最好将其延迟初始化,进行懒加载。 -
异步初始化
。同样,有些库不需要在主线程进行初始化,那么我们可以对其进行异步初始化,从而减少启动耗时。 -
多个异步任务依赖关系
。如果有些任务需要异步执行的同时还有互相的依赖关系,该怎么办呢。
如果我们在使用App Startup
的时候,有以上需求,那么有没有解决办法呢?
- 没有,也可以说有,就是关闭
App Startup
的初始化动作,然后自己进行初始化任务管理。
这可不是开玩笑,App Startup
的目的只是解决一个问题,就是多个ContentProvider
创建的问题,通过一个统一的ContentProvider
来形成规范,减少耗时。所以它的用法应该是针对各个三方库的设计者,当你设计一个库的时候,如果想静默初始化,就可以接入App Startup。当尽量多的库遵循这个要求,都接入App Startup
的时候,开发者的启动耗时自然就降低了。
但是如果我们有其他的需求,比如上述说到的延迟初始化,异步初始化等问题,我们就要关闭部分库或者所有库的App Startup
的功能,然后自己单独对任务进行初始化工作,比如通过启动器
来处理各个初始化任务的关系。
如果一个库已经集成了App Startup
功能,我们该怎么关闭呢?这就用到tools:node="remove"
标签了。
这样FacebookSDK
就不会自动进行初始化了,需要我们手动调用初始化方法。
总结
1)App Startup
的设计是为了解决一个问题:
- 即不同的库使用不同的ContentProvider进行初始化,导致ContentProvider太多,管理杂乱,影响耗时的问题。
2)App Startup
具体能减少多少耗时时间:
- 上面也实践过了,如果二三十个三方库都集成了App Startup,减少的耗时大概在20ms以内。
3)App Startup
的使用场景应该是:
- 针对三方库的设计者或者组件化的场景。当你设计一个库或者一个组件的时候,就可以接入App Startup。当尽量多的库遵循这个标准,都接入App Startup的时候,就能形成一种规范,App的启动耗时自然就降低了。
4)如果想解决多个库初始化任务太多导致的启动耗时
问题:
- 请左转前往各种启动器,比如alibaba/alpha
参考
Google文档
App Startup-郭霖
Android启动时间—siyu8023
App Startup源码—叶志陈
拜拜
感谢大家的阅读,有一起学习的小伙伴可以关注下我的公众号——码上积木❤️❤️
每日三问知识点/面试题,积少成多。
这里有一群很好的Android小伙伴,欢迎大家加入~