探究 | App Startup真的能减小启动耗时吗

前言

以前咱们说了启动优化的一些经常使用方法,可是有的小伙伴就很不屑了:java

“这些方法好久以前就知道了,不知道说点新东西?好比App Startup?能对启动优化有帮助吗?”node

ok,既然你诚心诚意的发问了,那我就大发慈悲的告诉你:俺也不知道😢android

走吧,一块儿瞅瞅这个App Startup吧,是否是真的能给咱们的启动带来优化呢?git

(想看结果的能够直接跳到最后的实践总结阶段)github

Contentprovider中初始化

想必你们都了解,不少三方库都须要在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

  • ...
  • attachBaseContext
  • Application attach
  • installContentProviders
  • Application onCreate
  • Looper.loop
  • Activity onCreate,onResume

这其中installContentProviders方法就是用来启动并执行各个ContentProvideronCreate方法的,它会在ApplicationonCreate方法以前执行。

因此这些库只是把Application的三方库初始化工做提早放到ContentProvider中了,并不会减小启动耗时,反而会增长启动耗时。

怎么说呢?由于不一样的库就定义了不一样的ContentProvider类,多了这么多ContentProviderContentProvider做为四大组件之一,启动也是耗时的,天然也就增长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"标签就是用来合并全部申明了InitializationProviderContentProvider

等等,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)若是想解决多个库初始化任务太多致使的启动耗时问题:

参考

Google文档

App Startup-郭霖

Android启动时间—siyu8023

App Startup源码—叶志陈

拜拜

有一块儿学习的小伙伴能够关注下❤️ 个人公众号——码上积木,天天剖析一个知识点,咱们一块儿积累知识。公众号回复111可得到面试题《思考与解答》以往期刊。

相关文章
相关标签/搜索