Jetpack新成员,App Startup一篇就懂

2023-05-16

Android 11系统已经来了,随之而来的是,Jetpack家族也引入了许多新的成员。

其实以后Android的更新都会逐渐采用这种模式,即特定系统相关的API会越来越少,更多的编程API是以Jetpack Library的形式提供给我们的。这样我们就不需要专门针对不同的系统版本去写很多的适配逻辑,而是统一用Jetpack提供的接口即可。Android也是在用这种方式去解决长期以来的碎片化问题。

而今年的Jetpack家族当中又加入了两名重磅的新成员,一个是Hilt,另一个是App Startup。

Hilt是一个依赖注入组件库,功能非常强大,但是由于想把依赖注入讲清楚还是一个相对比较困难的工作,我准备过段时间再好好想想怎样去写好一篇关于Hilt的文章。

本篇文章的主题是App Startup。

App Startup是一个可以用于加速App启动速度的一个库。很多人一听到可以加速App的启动速度?那这是好东西啊,迫不及待地想要将这个库引入到自己的项目当中,结果研究了半天,发现越看越不明白,怎么学着学着还和ContentProvider扯上关系了?

所以,在学习App Startup的用法之前,首先我们需要搞清楚的是,App Startup具体是用来解决什么问题的。

关注我比较久的朋友应该都知道,LitePal是由我编写并长期维护的一个Android数据库框架。这个框架可以帮助大家自动管理表的创建与升级,并提供方便的数据库操作API。

而用过LitePal的朋友一定知道,LitePal有提供一个initialize()接口,在进行所有的数据库操作之前,我们需要在自己的Application当中去调用这个接口进行初始化:

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        LitePal.initialize(this)
    }
    ...
}

为什么LitePal要求先进行初始化呢?因为Android的数据库中有需要操作都是需要依赖于Context的,在初始化的时候传入一次Context,LitePal会在内部将其保存下来,这样所以有其他数据库接口就不需要再传入Context参数了,从而让API变得更加精简。

这确实是个不错的主意,但是并不是只有LitePal想到了这一点,许多库也提供了类似的初始化接口,因此如果你在项目当中引入了非常多的第三方库,那么Application中的代码就可能会变成这个样子:

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        LitePal.initialize(this)
        AAA.initialize(this)
        BBB.initialize(this)
        CCC.initialize(this)
        DDD.initialize(this)
        EEE.initialize(this)
    }
    ...
}

这样的代码就会显得有些凌乱了对不对?随着你引用的第三方库越来越多,这种情况真的是有可能发生的。

于是,有些更加聪明的库设计者,他们想到了一种非常巧妙的办法来避免显示地调用初始化接口,而是可以自动调用初始化接口,这种办法就是借助ContentProvider。

ContentProvider我们都知道是Android四大组件之一,它的主要作用是跨应用程序共享数据。比如为什么我们可以读取到电话簿中的联系人、相册中的照片等数据,借助的都是ContentProvider。

然而这些聪明的库设计者们并没有打算使用ContentProvider来跨应用程序共享数据,只是准备使用它进行初始化而已。我们来看如下代码:

class MyProvider : ContentProvider() {

    override fun onCreate(): Boolean {
        context?.let {
            LitePal.initialize(it)
        }
        return true
    }
    ...
}

这里我定义了一个MyProvider,并让它继承自ContentProvider,然后我们在onCreate()方法中调用了LitePal的初始化接口。注意在ContentProvider中也是可以获取到Context的。

当然,继承了ContentProvider之后,我们是要重写很多个方法的,只不过其他方法在我们这个场景下完全使用不到,所以你可以在那些方法中直接抛出一个异常,或者进行空实现都是可以的。

另外不要忘记,四大组件是需要在AndroidManifest.xml文件中进行注册才可以使用的,因此记得添加如下内容:

<application ...>

    <provider
        android:name=".MyProvider"
        android:authorities="${applicationId}.myProvider"
        android:exported="false" />

</application>

authorities在这里并没有固定的要求,填写什么值都是可以的,但必须保证这个值在整个手机上是唯一的,所以通常会使用${applicationId}作为前缀,以防止和其他应用程序冲突。

那么,自定义的这个MyProvider它会在什么时候执行呢?我们来看一下这张流程图:


可以看到,一个应用程序的执行顺序是这个样子的。首先调用Application的attachBaseContext()方法,然后调用ContentProvider的onCreate()方法,接下来调用Application的onCreate()方法。

那么,假如LitePal在自己的库当中实现了上述的MyProvider,会发生什么情况呢?

你会发现LitePal.initialize()这个接口可以省略了,因为在MyProvider当中这个接口会被自动调用,这样在进入Application的onCreate()方法时,LitePal其实已经初始化过了。

有没有觉得这种设计方式很巧妙?它可以将库的用法进一步简化,不需要你主动去调用初始化接口,而是将这个工作在背后悄悄自动完成了。

那么有哪些库使用了这种设计方式呢?这个真的有很多了,比如说Facebook的库,Firebase的库,还有我们所熟知的WorkManager,Lifecycles等等。这些库都没有提供一个像LitePal那样的初始化接口,其实就是使用了上述的技巧。

看上去如此巧妙的技术方案,那么它有没有什么缺点呢?

有,缺点就是,ContentProvider会增加许多额外的耗时。

毕竟ContentProvider是Android四大组件之一,这个组件相对来说是比较重量级的。也就是说,本来我的初始化操作可能是一个非常轻量级的操作,依赖于ContentProvider之后就变成了一个重量级的操作了。

关于ContentProvider的耗时,Google官方也有给出一个测试结果:


这是在一台搭载Android 10系统的Pixel2手机上测试的情况。可以看到,一个空的ContentProvider大约会占用2ms的耗时,随着ContentProvider的增加,耗时也会跟着一起增加。如果你的应用程序中使用了50个ContentProvider,那么将会占用接近20ms的耗时。

注意这还只是空ContentProvider的耗时,并没有算上你在ContentProvider中执行逻辑的耗时。

这个测试结果告诉我们,虽然刚才所介绍的使用ContentProvider来进行初始化的设计方式很巧妙,但是如果每个第三方库都自己创建了一个ContentProvider,那么最终我们App的启动速度就会受到比较大的影响。

有没有办法解决这个问题呢?

有,就是使用我们今天要介绍的主题:App Startup。

我上面花了很长的篇幅来介绍App Startup具体是用来解决什么问题的,因为这部分内容才是App Startup库的核心,只有了解了它是用来解决什么问题的,才能快速掌握它的用法。不然就会像刚开始说的那样,学着学着怎么学到ContentProvider上面去了,一头雾水。

那么App Startup是如何解决这个问题的呢?它可以将所有用于初始化的ContentProvider合并成一个,从而使App的启动速度变得更快。

具体来讲,App Startup内部也创建了一个ContentProvider,并提供了一套用于初始化的标准。然后对于其他第三方库来说,你们就不需要再自己创建ContentProvider了,都按我的这套标准进行实现就行了,我可以保证你们的库在App启动之前都成功进行初始化。

了解了App Startup具体是用来解决什么问题的,以及它的实现原理,接下来我们开始学习它的用法,这部分就非常简单了。

首先要使用App Startup,我们要将这个库引入进来:

dependencies {
    implementation "androidx.startup:startup-runtime:1.0.0-alpha01"
}

接下来我们要定义一个用于执行初始化的Initializer,并实现App Startup库的Initializer接口,如下所示:

class LitePalInitializer : Initializer<Unit> {

    override fun create(context: Context) {
        LitePal.initialize(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return listOf(OtherInitializer::class.java)
    }

}

实现Initializer接口要求重现两个方法,在create()方法中,我们去进行之前要进行的初始化操作就可以了,create()方法会把我们需要的Context参数传递进来。

dependencies()方法表示,当前的LitePalInitializer是否还依赖于其他的Initializer,如果有的话,就在这里进行配置,App Startup会保证先初始化依赖的Initializer,然后才会初始化当前的LitePalInitializer。

当然,绝大多数的情况下,我们的初始化操作都是不会依赖于其他Initializer的,所以通常直接返回一个emptyList()就可以了,如下所示:

class LitePalInitializer : Initializer<Unit> {

    override fun create(context: Context) {
        LitePal.initialize(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }

}

定义好了Initializer之后,接下来还剩最后一步,将它配置到AndroidManifest.xml当中。但是注意,这里的配置是有比较严格的格式要求的,如下所示:

<application ...>

    <provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <meta-data
            android:name="com.example.LitePalInitializer"
            android:value="androidx.startup" />
    </provider>
    
</application>

上述配置,我们能修改的地方并不多,只有meta-data中的android:name部分我们需要指定成我们自定义的Initializer的全路径类名,其他部分都是不能修改的,否则App Startup库可能会无法正常工作。

没错,App Startup库的用法就是这么简单,基本我将它总结成了三步走的操作。

引入App Startup的库。
自定义一个用于初始化的Initializer。
将自定义Initializer配置到AndroidManifest.xml当中。
这样,当App启动的时候会自动执行App Startup库中内置的ContentProvider,并在它的ContentProvider中会搜寻所有注册的Initializer,然后逐个调用它们的create()方法来进行初始化操作。

只用一个ContentProvider就可以让所有库都正常初始化,Everyone is happy。

其实到这里为止,App Startup库的知识就已经讲完了,最后再介绍一个不太常用的知识点吧:延迟初始化。

现在我们已经知道,所有的Initializer都会在App启动的时候自动执行初始化操作。但是如果我作为LitePal库的用户,就是不希望它在启动的时候自动初始化,而是想要在特定的时机手动初始化,这要怎么办呢?

首先,你得通过分析LitePal源码的方式,找到LitePal用于初始化的Initializer的全路径类名是什么,比如上述例子当中的com.example.LitePalInitializer(注意这里我只是为了讲解这个知识点而举的例子,实际上LitePal还并没有接入App Startup)。

然后,在你的项目的AndroidManifest.xml当中加入如下配置:

<application ...>

    <provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <meta-data
            android:name="com.example.LitePalInitializer"
            tools:node="remove" />
    </provider>
    
</application>

区别就在于,这里在LitePalInitializer的meta-data当中加入了一个tools:node="remove"的标记。

这个标记用于告诉manifest merger tool,在最后打包成APK时,将所有android:name是com.example.LitePalInitializer的meta-data节点全部删除。

这样,LitePal库在自己的AndroidManifest.xml中配置的Initializer也会被删除,既然删除了,App Startup在启动的时候肯定就无法初始化它了。

而在之后手动去初始化LitePal的代码也极其简单,如下所示:

AppInitializer.getInstance(this)
    .initializeComponent(LitePalInitializer::class.java)

将LitePalInitializer传入到initializeComponent()方法当中即可,App Startup库会按照同样的标准去调用其create()方法来执行初始化操作。

到这里为止,App Startup的功能基本就全部讲解完了。

最后如果让我总结一下的话,这个库的整体用法非常简单,但是可能并不适合所有人去使用。如果你是一个库开发者,并且使用了ContentProvider的方式来进行初始化操作,那么你应该接入App Startup,这样可以让接入你的库的App降低启动耗时。而如果你是一个App开发者,我认为使用ContentProvider来进行初始化操作的概率很低,所以可能App Startup对你来说用处并不大。

当然,考虑到业务逻辑分离的代码结构,App的开发者也可以考虑将一些原来放在Application中的初始化代码,移动到一个Initializer中去单独执行,或许可以让你的代码结构变得更加合理与清晰。
————————————————
版权声明:本文为CSDN博主「guolin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/guolin_blog/article/details/108026357

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Jetpack新成员,App Startup一篇就懂 的相关文章

  • 我的日食今天停止工作了

    今天我遇到了 Eclipse 的问题 就像每次我关闭一个项目时都会弹出一个窗口 上面写着 保存工作空间时出错 并抱怨 apache xerces 我决定下载全新安装 但现在它甚至无法启动 我尝试了许多变体 经典 javase c 希望存在一
  • 如何编写一个随windows启动自动启动的python程序?

    我正在使用 python 2 6 和 pyqt4 编写一个程序 我希望这个程序在 Windows 启动时自动启动 类似于 uTorrent 客户端 我该如何进行这项工作 我使用的是Windows 7 您只需在 Windows 开始菜单的 启
  • 为什么使用共享库时 Linux 上的应用程序启动速度会变慢?

    在我正在开发的嵌入式设备上 启动时间是一个重要问题 整个应用程序由多个使用一组库的可执行文件组成 由于闪存空间有限 我们希望使用共享库 当编译并与共享库链接时 应用程序照常工作 并且闪存容量按预期减少 与链接到静态库的版本的区别在于应用程序
  • 服务器重新启动后重新启动 WCF 服务

    WCF 中是否有某种机制可用于 预启动 热身 托管在 IIS 中的 WCF 服务 类似于 SharePoint 网站的预热脚本 我遇到过这样的情况 服务器在夜间重新启动 第二天 WCF 服务启动时会出现很长的延迟 我无法更改使用这些服务的各
  • HAProxy 无法启动,无法绑定 UNIX 套接字 [/run/haproxy/admin.sock]

    我尝试使用 空 配置文件启动 haproxy 版本 1 5 8 2014 10 31 我得到 user server sudo service haproxy start Starting haproxy haproxy ALERT 126
  • 在 Windows 启动时运行程序

    我想知道是否有人可以向我解释如何让我的程序在启动时运行 我的程序是一个带有小型 WPF UI 的 C WCF 必须在服务器上运行 并且我需要确保该程序将在服务器重新启动或出于任何其他原因时启动 我环顾四周 看来我必须使用注册表项 但我不太熟
  • Windows 启动时在后台运行批处理文件

    每次 Windows 启动时如何运行批处理文件 我还需要在后台运行它 不显示该命令窗口 我使用Windows XP 我的实际要求是我想在 Windows 启动时使用命令行命令启动 Tracd 服务器 将您的程序添加到registry htt
  • 如何在每次启动时运行我自己的脚本

    我有一个问题 如何在 Ubuntu 中每次启动时运行自己的 bash 脚本 假设我有一个正在执行特定类型工作的脚本 现在我希望它在启动 Ubuntu 系统时自动运行 你应该学习如何使用暴发户 看this http upstart ubunt
  • npm 错误!代码 ELIFECYCLE(起始问题)

    感谢您阅读本文并帮助解决该问题 我正在尝试在 Windows 计算机上运行 nodejs 并在安装 expo cli 后启动 expo 客户端 最初它工作正常 除了实时刷新或任何其他刷新不起作用 所以我尝试再次删除 卸载 重新安装nodej
  • VSCode 在启动时打开特定文件

    目前是否可以在 VSCode 中设置启动时打开的特定文件 我安装了 待办任务 扩展 并且我想在每次加载 VSCode 时默认打开我的 TODO 文件 使用版本1 12 1 编辑 我认为最好的方法是使用可以在启动时运行命令的扩展 例子是 自动
  • 在 Windows 7 用户登录之前运行批处理文件以启动 VLC Web 界面

    我想运行一个批处理文件 该文件在用户登录之前执行以下操作 start VLC web Interface C Program Files x86 VideoLAN VLC vlc exe I http 目前我的启动文件夹中有此文件 但我希望
  • 如何捕获Tomcat启动日志

    如何捕获Tomcat启动日志 要在Windows中启动Tomcat 可以执行命令 卡塔琳娜运行 在你的 tomcat bin 文件夹中 Tomcat 启动的输出将保留在当前窗口中 以便您可以对其进行分析
  • WTSRegisterSessionNotification 有时在 XP home 启动时不起作用

    我正在使用该函数 消息来检查工作站是否已锁定 现在我的应用程序位于启动文件夹中 它在 XP pro 上运行没有任何问题 但由于我在 XP home 上使用该程序 WTSRegisterSessionNotification 在启动时大约有
  • ASP.NET Core 中应用程序启动逻辑放在哪里

    我想使用 ASP NET Core 2 1 创建一个 Web 服务 该服务在应用程序启动时检查与数据库的连接是否正常 然后在数据库中准备一些数据 检查循环运行 直到连接成功或用户按 Ctrl C IApplicationLifetime 重
  • Asp.net core 2.0条件服务或条件连接字符串

    我正在使用 Asp net core 2 0 开发 Web api 现在我需要的是使用不同的连接字符串 这些字符串将在用户的标头中指定 我以前见过这个 不是直接关于这个问题 app Use async context next gt if
  • 普通的 x86 或 AMD PC 是直接从 ROM 运行启动/BIOS 代码,还是先将其复制到 RAM? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我知道现代计算机已经修改了哈佛架构 它们可以从保存数据的地方以外的地方读取指令 这一事实是否允许它们直接从 ROM 芯片获取指令 他们是先
  • 如何在 Raspberry pi 中从启动运行 Python 脚本

    我尝试在 rc local bashrc 文件中执行操作 但 rc local 没有显示任何日志或不清楚它是否正在运行 bashrc 使脚本仅在我打开终端时运行 我需要在没有用户干预的情况下运行 并且我的脚本涉及网络连接和 websocke
  • Aurelia 以 PHP 传递的参数开头

    我需要在开始时将参数传递给 Aurelia 根据传递的值 应用程序将具有不同的状态 该应用程序被注入到使用 PHP 构建的页面上 因此最好的方法是使用 PHP 代码指定的参数启动它 有什么办法可以做到这一点吗 您可以在普通 JS 中访问的任
  • windows服务启动超时

    有没有办法为每个服务设置不同的服务启动超时值 我可以使用 ServicesPipeTimeout 注册表项更改它 但它是每台计算机的 http support microsoft com kb 824344 http support mic
  • Python启动脚本[重复]

    这个问题在这里已经有答案了 我想执行一个脚本work py在Python中 执行一些初始化脚本后init py 如果我正在寻找交互式会话 请执行python i init py或设置PYTHONSTARTUP path to init py

随机推荐

  • Ubuntu18.04 上 安装微信(Deepin-Wechat)

    文章目录 一 安装Deepin Wine环境二 安装Deepin 版微信 微信什么时候支持在linux下的安装包啊 xff0c 我的天哪 xff0c 感觉受到了针对 xff0c 各位看官且看下图 xff1a 这里先作声明 xff1a 本文的
  • ROS机器人操作系统——ROS介绍

    AI is the new electricity 1 ROS发展史 本世纪开始 关于人工智能的研究进入了大发展阶段 包括全方位的具体的AI 例如斯坦福大学人工智能实验室STAIR Stanford Artificial Intellige
  • 如何快速学习一门计算机语言

    一 4步掌握一门计算机语言 1 学习语言的语法 xff0c 关键字 xff0c 以及基本的库 xff08 基础阶段 xff09 2 学习语言的第三方库和各个组件 xff08 OS xff0c 数据库 xff0c 网络 xff09 之间的连用
  • CentOs6.8离线安装svn,并设置自动更新

    CentOs6 8离线安装svn xff0c 并设置自动更新 离线安装所需依赖离线安装 GCC xff0c 如果系统已经有 GCC 了 xff0c 跳过这一步需要的 rpm 包安装顺序 离线安装 openssl需要的代码编译设置环境变量 离
  • Linux ping不通,连不上网的解决办法

    Linux ping不通 xff0c 连不上网的解决办法 可能原因是DNS没有配置好 方法一 xff1a 修改vi etc resolv conf 增加如下内容 xff1a nameserver 114 114 114 114 电信的DNS
  • Android——多进程

    之前我们了解了 Java 多线程浅析 Android Handler详解 Android HandlerThread浅析 Java ThreadPool线程池 让我们继续看看Android多进程 xff1a 1 概述 默认情况下 xff0c
  • React、Ant Desgin自定义加载动画,lottie-web 将json解析成动画

    在项目中 xff0c 遇到需要在网页首屏 xff0c 展示动画的需求 xff0c 你会想到怎么做 xff1f 思路一 xff1a 设计师导出gif图片 xff0c 用img进行展示 缺点 xff1a 图片失真 xff0c 影响效果 思路二
  • mac下安装多版本PHP及切换

    mac下安装多版本PHP及切换 工作环境一直是PHP5 6 xff0c 后来发布了PHP7 xff0c 性能提升不少 xff0c 如今打算试试PHP7 xff0c 所以就有了两个php版本的需求 本文的原理就是用一个php管理工具 xff0
  • 短视频拍摄脚本怎么写

    优质的短视频每一个镜头都经过精心设计 xff0c 镜头的设计就是利用镜头脚本 xff0c 提前设想好一切想要的镜头效果和画面 xff0c 最终作品才能一气呵成的呈现出来 xff0c 接下来就来分析一下短视频拍摄脚本怎么写 xff0c 短视频
  • 串口开发之环形缓冲区

    01 简介 串口的基本应用 xff0c 使用串口中断接收数据 xff0c 串口中断发送回包 xff08 一般可以使用非中断形式发送回包 xff0c 在数据接收不频繁的应用中 串口接收中断保证串口数据及时响应 xff0c 使用非中断方式发送回
  • fastboot 命令

    1 fastboot概念 fastboot fastboot是PC与bootloader的USB通信的命令行工具 xff0c 通过向bootloader传送刷机文件 xff08 img xff09 实现Android系统分区重烧 fastb
  • Android Studio 开启视图绑定 viewBinding

    Google 在 Android Studio 3 6 Canary 11 及更高版本中提供了一个 viewBinding 的开关 xff0c 可以开启视图绑定功能 xff0c 以此来替代 findViewById viewBinding功
  • ViewPager 装载fragment 页面显示空白

    ViewPager 装载fragment 页面显示空白 xff0c 这个时候有两种情况 xff1a 在分页面较多的情况下 使用了 FragmentPagerAdapter xff0c 可能会导致第二次加载页面显示空白或是多次滑动页面后页面空
  • The following packages have unmet dependencies: openssh-server : Depends: openssh-client (= 1:6.6p1

    在虚拟机中安装openssh server的时候报了这个错误 xff0c 不知道这台虚拟机抽了什么风 xff0c 别的虚拟机都能顺利安装 xff0c xff0c xff0c 提示说是openssh server 依赖于 openssh cl
  • Docker Desktop stopped 问题解决

    推广博客 xff1a Docker Desktop stopped 问题解决
  • windows连接远程桌面必须要有用户名和密码

    被远程连接的电脑如果有用户名但没有密码 xff0c 连接时需要输入密码时空着会导致无法连接 想想也是 xff0c 如果没有密码 xff0c 只要有人连入电脑所在局域网 xff0c 就可以通过ip地址和用户名连入电脑 xff0c 非常不安全
  • Android中APK签名工具之jarsigner和apksigner详解

    一 工具介绍 jarsigner是JDK提供的针对jar包签名的通用工具 位于JDK bin jarsigner exe apksigner是Google官方提供的针对Android apk签名及验证的专用工具 位于Android SDK
  • Android NumberPicker的基本用法及常见问题汇总

    前言 在项目中需要一个选择人数的控件 xff0c 于是想到了NumberPicker xff0c 这个控件相对不是那么热门 xff0c 我也是第一次用 xff0c 所以遇到了一些问题 xff0c 这里做个小结 正文 首先来看一下最终的效果
  • angular将html代码输出为内容

    在前端与后台的撕逼中 xff0c 很大一部分是因为数据的问题 使用angular会遇到这样的问题 xff0c 后台返回的数据不是自己想要的纯字符串 xff0c 而是带有html标签及属性的 xff0c 那么我们将它输出来后 xff0c 在页
  • Jetpack新成员,App Startup一篇就懂

    Android 11系统已经来了 xff0c 随之而来的是 xff0c Jetpack家族也引入了许多新的成员 其实以后Android的更新都会逐渐采用这种模式 xff0c 即特定系统相关的API会越来越少 xff0c 更多的编程API是以