探究 | App Startup真的能减少启动耗时吗
前言
之前我们说了启动优化的一些常用方法,但是有的小伙伴就很不屑了:
“这些方法很久之前就知道了,不知道说点新东西?比如App Startup?能对启动优化有帮助吗?”
ok,既然你诚心诚意的发问了,那我就大发慈悲的告诉你:俺也不知道
。
走吧,一起瞅瞅这个App Startup
吧,是不是真的能给我们的启动带来优化呢?
(想看结果的可以直接跳到最后的实践
和总结
阶段)
Contentprovider中初始化
想必大家都了解,很多三方库都需要在Application
中进行初始化,并顺便获取到Application
的上下文。
但是也有的库不需要我们自己去初始化,它偷偷摸摸就给初始化了,用到的方法就是使用ContentProvider
进行初始化,定义一个ContentProvider
,然后在onCreate拿到上下文,就可以进行三方库自己的初始化工作了。而在APP的启动流程中,有一步就是要执行到程序中所有注册过的ContentProvider
的onCreate方法,所以这个库的初始化就默默完成了。
这种做法确实给集成库的开发者们带来了很大的便利,现在很多库都用到了这种方法,比如Facebook,Firebase
,这里拿Facebook
举例看看他的ContentProvider:
<provider
android:name="com.facebook.internal.FacebookInitProvider"
android:authorities="${applicationId}.FacebookInitProvider"
android:exported="false" />
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<Unit> {
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<Class<out Initializer<*>>> {
return emptyList()
}
}
//AndroidManifest.xml中定义
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.FacebookSDKInitializer"
android:value="androidx.startup" />
</provider>
实现了Initializer
接口,然后在onCreate方法中进行初始化即可,只要所有的库都按照这个标准来初始化,而不是自己单独自定义ContentProvider
,那么确实可以减少启动耗时。
其中,tools:node="merge"
标签就是用来合并所有申明了InitializationProvider
的ContentProvider
。
等等,Initializer
接口还有一个方法dependencies,这又是干啥的呢?
可以明确地设置初始化顺序
这也就是App Startup的第二个特性了,可以设置初始化顺序。
可以想象,按照上述做法,所有库都这样设定了,那么都会在同一个ContentProvider
也就是androidx.startup.InitializationProvider
中初始化,但是如果我需要设定不同库的初始化顺序怎么办呢?
比如上述的facebook
初始化,我需要设定在另一个库WorkManager
之后运行,那么我们就可以重写dependencies
方法:
class FacebookSDKInitializer : Initializer<Unit> {
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<Class<out Initializer<*>>> {
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
的情况。
<provider
android:name=".appstartup.LibraryAContentProvider"
android:authorities="${applicationId}.LibraryAContentProvider"
android:exported="false" />
<provider
android:name=".appstartup.LibraryBContentProvider"
android:authorities="${applicationId}.LibraryBContentProvider"
android:exported="false" />
安装到手机后,打开应用,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代码,并注册:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.studynote.appstartup.LibraryAInitializer"
android:value="androidx.startup" />
<meta-data android:name="com.example.studynote.appstartup.LibraryBInitializer"
android:value="androidx.startup" />
</provider>
运行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中注册:
<provider
android:name=".appstartup.LibraryAContentProvider"
android:authorities="${applicationId}.LibraryAContentProvider"
android:exported="false" />
<!-- 省略剩下9个provider注册代码 -->
运行五次,取平均值:
TotalTime: 1758
TotalTime: 1759
TotalTime: 1733
TotalTime: 1737
TotalTime: 1747
平均值:1746.8
然后注释刚才的ContentProvider
注册代码,添加App startup代码,并注册:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.studynote.appstartup.LibraryAInitializer"
android:value="androidx.startup" />
<!--省略剩下9个meta-data注册代码-->
</provider>
运行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"
标签了。
<!-- 禁用所有InitializationProvider组件初始化 -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
<!-- 禁用单个InitializationProvider组件初始化 -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.FacebookSDKInitializer"
android:value="androidx.startup"
tools:node="remove"/>
</provider>
这样FacebookSDK
就不会自动进行初始化了,需要我们手动调用初始化方法。
总结
1)App Startup
的设计是为了解决一个问题:
- 即不同的库使用不同的ContentProvider进行初始化,导致ContentProvider太多,管理杂乱,影响耗时的问题。
2)App Startup
具体能减少多少耗时时间:
- 上面也实践过了,如果二三十个三方库都集成了App Startup,减少的耗时大概在20ms以内。
3)App Startup
的使用场景应该是:
- 针对三方库的设计者或者组件化的场景。当你设计一个库或者一个组件的时候,就可以接入App Startup。当尽量多的库遵循这个标准,都接入App Startup的时候,就能形成一种规范,App的启动耗时自然就降低了。
4)如果想解决多个库初始化任务太多导致的启动耗时
问题:
- 请左转前往各种启动器,比如alibaba/alpha
参考
拜拜
有一起学习的小伙伴可以关注下️ 我的公众号——码上积木,每天剖析一个知识点,我们一起积累知识。公众号回复111可获得面试题《思考与解答》以往期刊。
探究 | App Startup真的能减少启动耗时吗的更多相关文章
- 高德APP启动耗时剖析与优化实践(iOS篇)
前言最近高德地图APP完成了一次启动优化专项,超预期将双端启动的耗时都降低了65%以上,iOS在iPhone7上速度达到了400毫秒以内.就像产品们用后说的,快到不习惯.算一下每天为用户省下的时间,还 ...
- Android app启动耗时分析
前言 app启动耗时过长的话,无论你的app里面的内容多么丰富有趣,作为一个用户,首先是没有耐心去等待的,如果我是一个用户,我会这样想:这是什么垃圾公司出的什么烂app,再等2s不进来就卸载,黑人问号 ...
- eclipse java项目中明明引入了jar包 为什么项目启动的时候不能找到jar包 项目中已经 引入了 com.branchitech.app 包 ,但时tomcat启动的时候还是报错? java.lang.ClassNotFoundException: com.branchitech.app.startup.AppStartupContextListener java.lang.ClassN
eclipse java项目中明明引入了jar包 为什么项目启动的时候不能找到jar包 项目中已经 引入了 com.branchitech.app 包 ,但时tomcat启动的时候还是报错?java. ...
- iOS开发-测量APP启动耗时
冷启动 冷启动就是App被kill掉以后一切从头开始启动的过程. 热启动 当用户按下home键的时候,iOS的App并不会马上被kill掉,还会继续存活若干时间.理想情况下,用户点击App的图标再次回 ...
- 食之无味?App Startup 可能比你想象中要简单
请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...
- Jetpack架构组件学习(4)——APP Startup库的使用
最近在研究APP的启动优化,也是发现了Jetpack中的App Startup库,可以进行SDK的初始化操作,于是便是学习了,特此记录 原文:Jetpack架构组件学习(4)--App Startup ...
- Android Activity启动耗时统计方案
作者:林基宗 Activity的启动速度是很多开发者关心的问题,当页面跳转耗时过长时,App就会给人一种非常笨重的感觉.在遇到某个页面启动过慢的时候,开发的第一直觉一般是onCreate执行速度太慢了 ...
- 「Spring Boot 2.4 新特性」启动耗时详细监控
背景 Spring Boot 项目随着项目开发过程中引入中间件数量的增加,启动耗时 逐渐增加. 笔者在 <Spring Boot 2.4.0 正式 GA,全面拥抱云原生>文章评论下发现了 ...
- 免安装的tomcat双击startup.bat后,启动窗口一闪而过,而且tomcat服务未启动。
免安装的tomcat双击startup.bat后,启动窗口一闪而过,而且tomcat服务未启动. 原因是:在启动tomcat是,需要读取环境变量和配置信息,缺少了这些信息,就不能登记环境变量,导致了t ...
随机推荐
- iOS中字符串转float类型失真的解决办法
最近在做项目的过程中,偶然遇到了一个问题,就是字符串和浮点类型的转换.以往都是通过[NSString stringWithFormat:@"%d",goodcount]这种方式转换 ...
- 关于UILabel标签控件的使用小节
前段时间一直想停下来,总结一下近期在开发中遇到的一些问题顺便分享一下解决问题的思路和方法,无奈人生就像蒲公英,看似自由却身不由己.太多的时间和精力被占用在新项目的开发和之前项目的维护中,总之一句话外包 ...
- LCCUP 2020 秋季编程大赛 补题
果然是力扣杯,难度较于平时周赛提高了不少,个人感觉最后两题并不太容易QAQ LCP 18.早餐组合 #二分思想 题目链接 题意 你获得了每种主食的价格,及每种饮料的价格,你需要选择一份主食和一份饮料, ...
- 为什么要小心使用 Task.Run
昨天在博客园有园友问了我一个问题,是这样的: 先是半个月前 @碧水青荷 童鞋的一句话"大家都说不要随便 Task.Run(()=>{}) 这样写",当时没有想太多,这句话并没 ...
- 在 CentOS 7 安装 RabbitMQ
一.安装 Erlang RabbitMQ 是使用 Erlang 开发的,所以需要首先安装 Erlang,本文安装其最新版本 添加 repo 文件: sudo vim /etc/yum.repos.d/ ...
- IDEA无法识别module
如图,我爱算法模块无法识别 如此,放开注释部分 即可
- PyQt(Python+Qt)学习随笔:QListView的flow属性
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QListView的flow属性用于控制视图中的数据排列方向,其类型为枚举类型QListView.F ...
- 第九章、Qt Designer可视化设计界面布局组件介绍
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.引言 在Qt Designer中,在左边部件栏的提供了界面布局相关部件,如图: 可以看到共包含有 ...
- Leetcode学习笔记(4)
题目1 ID121 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格. 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润. 注意你不能在买入股 ...
- 第 3 篇 Scrum 冲刺博客
每天举行会议 会议照片: 昨天已完成的工作与今天计划完成的工作及工作中遇到的困难: 成员姓名 昨天完成工作 今天计划完成的工作 工作中遇到的困难 蔡双浩 了解任务,并做相关学习和思考,创建基本的收藏夹 ...