以前咱们说了启动优化的一些经常使用方法,可是有的小伙伴就很不屑了:java
“这些方法好久以前就知道了,不知道说点新东西?好比App Startup?能对启动优化有帮助吗?”node
ok,既然你诚心诚意的发问了,那我就大发慈悲的告诉你:俺也不知道😢
。android
走吧,一块儿瞅瞅这个App Startup
吧,是否是真的能给咱们的启动带来优化呢?git
(想看结果的能够直接跳到最后的实践
和总结
阶段)github
想必你们都了解,不少三方库都须要在Application
中进行初始化,并顺便获取到Application
的上下文。面试
可是也有的库不须要咱们本身去初始化,它偷偷摸摸就给初始化了,用到的方法就是使用ContentProvider
进行初始化,定义一个ContentProvider
,而后在onCreate拿到上下文,就能够进行三方库本身的初始化工做了。而在APP的启动流程中,有一步就是要执行到程序中全部注册过的ContentProvider
的onCreate方法,因此这个库的初始化就默默完成了。shell
这种作法确实给集成库的开发者们带来了很大的便利,如今不少库都用到了这种方法,好比Facebook,Firebase
,这里拿Facebook
举例看看他的ContentProvider:app
<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进行初始化。异步
虽然更方便了,可是这种作法有给启动优化带来什么好处吗?咱们一块儿再回顾下以前的启动流程研究下,截取一部分:ide
这其中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
。
这样就至少不会增长启动耗时了。
怎么操做呢?假如咱们是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
的设计是为了解决一个问题:
2)App Startup
具体能减小多少耗时时间:
3)App Startup
的使用场景应该是:
4)若是想解决多个库初始化任务太多致使的启动耗时
问题:
有一块儿学习的小伙伴能够关注下❤️ 个人公众号——码上积木,天天剖析一个知识点,咱们一块儿积累知识。公众号回复111可得到面试题《思考与解答》以往期刊。