Jetpack Splashscreen 解析 | 助力新生代 IT 农民工 事半功倍

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 今天这篇文章主要介绍 Google 新库 Core Splashscreen ,众所周知在 Android 12 中增加了一个改善用户体验的功能 SplashScreen API,它可为所有应用添加启动画面。包括启动时进入应用的启动动画,以及退出动画。

image.png


公众号:ByteCode,致力于分享最新技术原创文章,涉及 Kotlin、Jetpack、译文、系统源码、 LeetCode / 剑指 Offer / 多线程 / 国内外大厂算法题 等等一系列文章。


Jetpack 家族迎来了一位新的成员 Core Splashscreen,所以我也要重新开始写 Jetpack 系列文章了,在这之前写过一系列 Jetpack 文章以及配套的实战应用,包含 App StartupPaging3HiltDataStoreViewBinding 等等实战项目,点击下方链接前去查看。



而今天这篇文章主要介绍 Google 新库 Core Splashscreen ,众所周知在 Android 12 中增加了一个改善用户体验的功能 SplashScreen API,它可为所有应用添加启动画面。包括启动时进入应用的启动动画,以及退出动画。


通过这篇文章你将学习到以下内容


  • Core Splashscreen 解决了什么问题?
  • Core Splashscreen 工作原理?
  • 针对不同的场景,如何在项目中使用 Core Splashscreen?
  • Core Splashscreen 源码分析?


Core Splashscreen 实战项目地址,可以前往 GitHub 查看示例项目 Splashscreen。github.com/hi-dhl/Andr…


Core Splashscreen



Core Splashscreen 解决了什么问题?


在 Android 启动过程中会出现白屏 / 黑屏,为了改善这一体验,因此添加启动画面,从而改善视觉上的体验,为了实现这一功能,市面上也有很多实现方法,都有各自的优缺点,因此并不能保证在所有设备上都能够流畅的运行。


其次有的时候需要从本地磁盘或者网络异步加载数据,等待数据加载完之后,才会去渲染 View, 大多数时候,希望将数据加载提前,尽量保证用户进入到首页之后,看到数据,减少用户的等待时间。


在 Android 12 上新增的 SplashScreen API,可以解决这一系列问题,但是缺点是仅限于 Android 12。


Core Splashscreen 因此而诞生了,为 Android 12 新增的 SplashScreen API 提供了向后兼容,可以在 Android 5.0 (API 21) ~ Android 12 (API 31)所有的 API 上使用。来看一下 Google 提供的动画效果。


image.png


Core Splashscreen 工作原理


Core Splashscreen 为 Android 12 新增的 SplashScreen API 提供了向后兼容,但是仅仅在以下情况下才会显示启动画面:


  • 冷启动:用户打开 APP 时 APP 进程尚未运行
  • 温启动:APP 进程正在运行,但是 Activity 尚未创建

启动动画只有在以上情况才会显示,但是在热启动期间是不会显示启动画面。

  • 热启动:APP 进程正在运行,Activity 也已经创建,也就说用户按下 Home 键退到后台,直到 Activity 被销毁之前,是不会显示启动画面


如何使用 Core Splashscreen


因为 Core Splashscreen 兼容了 Android 12 新增的 SplashScreen API, 因此需要将 compileSdkVersion 更新到 31 及其以上。


如果你的 SDK 还没有更新到 Android 12, 请先更新。SDK Manager -> 选择 Android 12


android {
    compileSdkVersion 31
}


在模块级别的 build.gradle 文件中添加以下依赖。


implementation 'androidx.core:core-splashscreen:1.0.0-alpha01'


当添加完依赖之后就可以开始使用 Core Splashscreen,只需要三步即可实现显示启动画面。


1. 在 res/values/themes.xml 文件下添加新的主题 Theme.AppSplashScreen


<style name="Theme.AppSplashScreen" parent="Theme.SplashScreen">
    <item name="windowSplashScreenBackground">@color/purple_200</item>
    <item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
    <item name="postSplashScreenTheme">@style/Theme.AppTheme</item>
</style>
<!-- Base application theme. -->
<style name="Theme.AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
    <!-- 添加 APP 默认主题 -->
</style>


  • android:windowSplashScreenBackground : 设置背景颜色
  • windowSplashScreenAnimatedIcon : 设置显示在屏幕中间的图标, 如果是通过 AnimationDrawableAnimatedVectorDrawable 创建的对象,可呈现动画效果,则会在页面显示的时候,播放动画
  • postSplashScreenTheme : 设置显示动画不可见时,使用 APP 的默认主题


2. 在 application 节点中,设置上一步添加主题 Theme.AppSplashScreen


<application
    android:theme="@style/Theme.AppSplashScreen">
</application>


3. 在调用 setContentView() 方法之前调用 installSplashScreen()


class MainActivity : AppCompatActivity() {
    private val binding: ActivityMainBinding by viewbind()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        installSplashScreen()
        with(binding) {
            // init view
        }
    }
}


调用 installSplashScreen() 方法主要将 Activity 与我们添加的主题相关联。这一步完成之后,就可以在 APP 启动过程中,看到刚才设置的图标或者动画了。


扩展功能


让启动动画持久一点


默认情况下当应用绘制第一帧后,启动画面会立即关闭,但是有的时候需要从本地磁盘或者网络异步加载数据,这个时候,希望启动画面能够等到数据加载完回来才结束。可以通过以下方法实现。


splashScreen.setKeepVisibleCondition { !appReady }
// 模拟从本地磁盘或者网络异步加载数据的耗时操作
Handler(Looper.getMainLooper())
    .postDelayed({ appReady = true }, 3000)


调用以上方法,可以让应用暂停绘制第一帧这样启动画面就不会结束,当数据加载完之后,通过更新变量 appReady 来控制是否结束启动画面。


实现退出动画


当然我们也可以添加启动画面的退出动画,即从启动画面优雅的回到应用主界面。


splashScreen.setOnExitAnimationListener { splashScreenViewProvider ->
    ......
    // 自定义退出动画
    val translationY = ObjectAnimator.ofFloat(......)
    translationY.doOnEnd { splashScreenViewProvider.remove() }
    translationY.start()
}


效果可以前往 GitHub 查看示例项目 Splashscreen。


GitHub 示例项目:https://github.com/hi-dhl/AndroidX-Jetpack-Practice


Core Splashscreen 源码解析



Core Splashscreen 源码很简单,总共就只有两个类。


  • SplashScreen :主要为实现 SplashScreen API 提供了向后兼容性,用于将 Activity 与主题相关联。
  • SplashScreenViewProvider : 用于控制退出动画(启动画面 -> 应用主界面),当退出动画结束时需要手动调用  SplashScreenViewProvider#remove() 方法


初始化 SplashScreen


通过调用 SplashScreen#installSplashScreen() 方法来进行初始化,将 Activity 与添加的主题相关联。androidx/core/splashscreen/SplashScreen.kt


public companion object {
    @JvmStatic
    public fun Activity.installSplashScreen(): SplashScreen {
        val splashScreen = SplashScreen(this)
        splashScreen.install()
        return splashScreen
    }
}
private fun install() {
    impl.install()
}


最终都是通过调用 impl.install() 方法来进行初始化,一起来看看成员变量 impl 是如何初始化的。


private val impl = when {
    SDK_INT >= 31 -> Impl31(activity)
    SDK_INT == 30 && PREVIEW_SDK_INT > 0 -> Impl31(activity)
    SDK_INT >= 23 -> Impl23(activity)
    else -> Impl(activity)
}


到这里我们知道了 Google 为了向后兼容,针对于不同版本的系统,分别对应有不同的实现类。最终都是调用 install() 方法来进行初始化的,在 install() 方法内通过解析我们添加的主题,最后通过 activity.setTheme() 方法,将添加的主题和 Activity 关联在一起。


如何让启动动画持久一点


在代码中,我们通过调用  SplashScreen#setKeepVisibleCondition() 方法,让启动动画持久一点,等待数据加完之后,才结束启动动画。一起来看看这个方法。

androidx/core/splashscreen/SplashScreen.kt


public fun setKeepVisibleCondition(condition: KeepOnScreenCondition) {
    // impl:针对于不同版本的系统,分别对应有不同的实现类
    impl.setKeepVisibleCondition(condition)
}
open fun setKeepVisibleCondition(keepOnScreenCondition: KeepOnScreenCondition) {
    ......
    observer.addOnPreDrawListener(object : OnPreDrawListener {
        override fun onPreDraw(): Boolean {
            if (splashScreenWaitPredicate.shouldKeepOnScreen()) {
                return false
            }
            contentView.viewTreeObserver.removeOnPreDrawListener(this)
            // 当开始绘制时,会调用 dispatchOnExitAnimation 方法,结束启动动画
            mSplashScreenViewProvider?.let(::dispatchOnExitAnimation)
            return true
        }
    })
}


最后通过 ViewTreeObserver 来监听视图的变化,当视图将要开始绘制时,会回调 OnPreDrawListener#onPreDraw() 方法。最后调用 dispatchOnExitAnimation 方法,结束启动动画。


实现退出动画


最后一起来看一下,源码中是如何实现退出动画,即从启动画面优雅的回到应用主界面,源码中只是提供了一个  OnExitAnimationListener 接口,将退出动画交给了开发者去实现,一起来看一下SplashScreen#setOnExitAnimationListener() 方法。

androidx/core/splashscreen/SplashScreen.kt


Android 12 以上


override fun setOnExitAnimationListener(
    exitAnimationListener: OnExitAnimationListener
) {
    activity.splashScreen.setOnExitAnimationListener {
        val splashScreenViewProvider = SplashScreenViewProvider(it, activity)
        exitAnimationListener.onSplashScreenExit(splashScreenViewProvider)
    }
}


在 Android 12 中是通过系统源码提供的接口 activity.splashScreen.setOnExitAnimationListener ,回调对外暴露的接口 OnExitAnimationListener 让开发者去实现退出动画的效果。


Android 12 以下


open fun setOnExitAnimationListener(exitAnimationListener: OnExitAnimationListener) {
    animationListener = exitAnimationListener
    val splashScreenViewProvider = SplashScreenViewProvider(activity)
    ......
    splashScreenViewProvider.view.addOnLayoutChangeListener(
    object : OnLayoutChangeListener {
        override fun onLayoutChange(......) {
            ......
            dispatchOnExitAnimation(splashScreenViewProvider)
        }
    })
}
fun dispatchOnExitAnimation(splashScreenViewProvider: SplashScreenViewProvider) {
    ......
    splashScreenViewProvider.view.postOnAnimation {
        finalListener.onSplashScreenExit(splashScreenViewProvider)
    }
}


通过向屏幕中显示的 View 添加 addOnLayoutChangeListener 方法,来监听布局的变化,当布局会发生改变时,会回调 onLayoutChange 方法,最后通过回调对外暴露的接口 OnExitAnimationListener 让开发者去实现退出动画。


不过这里需要注意的是,最后都需要调用 SplashScreenViewProvider#remove() 方法在合适的时机移除动画,可以在退出动画结束时,调用这个方法。


总结



本文从不同的角度分别分析了 Core Splashscreen。如何在项目中使用 Core Splashscreen,可以前往 GitHub 查看示例项目 Splashscreen。


仓库地址:https://github.com/hi-dhl/AndroidX-Jetpack-Practice


另外 KtKit 是用 Kotlin 语言编写的小巧而实用工具库,包含了项目中常用的一系列工具,我添加了许多新的功能,包含了很多 Kotlin 技巧。文章分析可前往查看  为数不多的人知道的 Kotlin 技巧以及解析(三)


监听 EditText


将 Flow 通过 lifecycleScope 将 EditText 与 Activity / Fragment 的生命周期绑定在一起,在 Activity / Fragment 生命周期结束时,会结束 flow , flow 结束时会断开它们之间的引用,有效的避免内存泄漏。


......
// 监听 TextWatcher#onTextChanged 的回调函数
editText.textChange(lifecycleScope) {
    Log.e(TAG, "textChange = $it")
}
// 监听 TextWatcher#beforeTextChanged 的回调函数
editText.textChangeWithbefore(lifecycleScope) {
    Log.e(TAG, "textChangeWithbefore = $it")
}
// 监听 TextWatcher#afterTextChanged 的回调函数
editText.textChangeWithAfter(lifecycleScope) {
    Log.e(TAG, "textChangeWithbefore = $it")
}
......


监听蜂窝网络变化


lifecycleScope.launch {
    listenCellular().collect {
        Log.e(TAG, "listenNetwork = $it")
    }
}


监听 wifi 网络的变化


lifecycleScope.launch {
    listenWifi().collect {
        Log.e(TAG, "listenNetwork = $it")
    }
}


监听蓝牙网络的变化


lifecycleScope.launch {
    listenNetworkFlow().collect {
        Log.e(TAG, "listenNetwork = $it")
    }
}


更多 API 使用方式点击这里前往查看:



如果这个仓库对你有帮助,请在仓库右上角帮我 star 一下,非常感谢你的支持,同时也欢迎你提交 PR  ❤️❤️❤️


如果有帮助 点个赞 就是对我最大的鼓励


代码不止,文章不停


欢迎关注公众号:ByteCode,持续分享最新的技术


最后推荐我一直在更新维护的项目和网站:


  • 个人博客,将所有文章进行分类,欢迎前去查看 hi-dhl.com
  • 计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看:AndroidX-Jetpack-Practice
  • LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析


image.png


  • 最新 Android 10 源码分析系列文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,仓库持续更新,欢迎前去查看 Android10-Source-Analysis
  • 整理和翻译一系列精选国外的技术文章,每篇文章都会有译者思考部分,对原文的更加深入的解读,仓库持续更新,欢迎前去查看 Technical-Article-Translation
  • 「为互联网人而设计,国内国外名站导航」涵括新闻、体育、生活、娱乐、设计、产品、运营、前端开发、Android 开发等等网址,欢迎前去查看 为互联网人而设计导航网站


历史文章




目录
相关文章
|
15天前
|
Kubernetes Cloud Native 云计算
云原生技术深度解析:重塑企业IT架构的未来####
本文深入探讨了云原生技术的核心理念、关键技术组件及其对企业IT架构转型的深远影响。通过剖析Kubernetes、微服务、容器化等核心技术,本文揭示了云原生如何提升应用的灵活性、可扩展性和可维护性,助力企业在数字化转型中保持领先地位。 ####
|
27天前
|
人工智能 运维 监控
运维技术深度解析:构建高效、稳定的IT基础设施
【10月更文挑战第22天】运维技术深度解析:构建高效、稳定的IT基础设施
52 0
|
27天前
|
机器学习/深度学习 边缘计算 运维
运维技术深度解析:构建高效、稳定的IT基础设施
【10月更文挑战第22天】运维技术深度解析:构建高效、稳定的IT基础设施
44 0
|
4月前
|
存储 数据库 Android开发
🔥Android Jetpack全解析!拥抱Google官方库,让你的开发之旅更加顺畅无阻!🚀
【7月更文挑战第28天】在Android开发中追求高效稳定的路径?Android Jetpack作为Google官方库集合,是你的理想选择。它包含多个独立又协同工作的库,覆盖UI到安全性等多个领域,旨在减少样板代码,提高开发效率与应用质量。Jetpack核心组件如LiveData、ViewModel、Room等简化了数据绑定、状态保存及数据库操作。引入Jetpack只需在`build.gradle`中添加依赖。例如,使用Room进行数据库操作变得异常简单,从定义实体到实现CRUD操作,一切尽在掌握之中。拥抱Jetpack,提升开发效率,构建高质量应用!
70 4
|
存储 Android开发
android Jetpack—ViewModel使用方法和详细原理解析
android Jetpack—ViewModel使用方法和详细原理解析
949 0
android Jetpack—ViewModel使用方法和详细原理解析
|
Java API Android开发
Jetpack 之 LifeCycle 组件原理解析
1. LifeCycle 是如何监听到 Activity/Fragment 生命周期变化的? 2. LifeCycle 如何将生命周期变化的事件分发给观察者的?
127 0
Jetpack 之 LifeCycle 组件原理解析
|
JSON 缓存 API
深度探讨 Jetpack SplashScreen 如何重塑应用启动画面(2)
深度探讨 Jetpack SplashScreen 如何重塑应用启动画面(2)
深度探讨 Jetpack SplashScreen 如何重塑应用启动画面(2)
|
API Android开发
深度探讨 Jetpack SplashScreen 如何重塑应用启动画面(1)
深度探讨 Jetpack SplashScreen 如何重塑应用启动画面(1)
深度探讨 Jetpack SplashScreen 如何重塑应用启动画面(1)
|
API Android开发
Jetpack新成员SplashScreen:为全新的应用启动效果赋能!(2)
Jetpack新成员SplashScreen:为全新的应用启动效果赋能!(2)
Jetpack新成员SplashScreen:为全新的应用启动效果赋能!(2)
|
开发框架 API 开发工具
Jetpack新成员SplashScreen:为全新的应用启动效果赋能!(1)
Jetpack新成员SplashScreen:为全新的应用启动效果赋能!(1)
Jetpack新成员SplashScreen:为全新的应用启动效果赋能!(1)

推荐镜像

更多
下一篇
无影云桌面