作者 | 小虾米君

来源 | TechMerger(ID:ELC-XTLS-QSW)

Android 12上新引入的Splash Screen功能,可以高效打造自由、丰富的应用启动效果。但仍占据市场主流的低版本设备,如何才能尝上鲜呢?答案就是Jetpack的新成员SplashScreen库,让我们一探其用法以及背后的原理!


前言

早在Android 12 Preview版公开的时候,我注意到了酷炫的Splash Screen功能,便快速定制和分享了用它打造的各式启动效果,收到了很多积极的反馈。万分荣幸的是:负责Splash Screen功能的Google工程师也给我的Demo点了赞,还进行了转发!

但不少开发者表示Android 12上的效果固然不错,可12之前的版本才是主流,如果不兼容的话,实属有些鸡肋。包括我在内,都很关心低版本如何才能应用上这个新功能。

翻阅这位Google工程师的历史推文的时候,意外发现不久前AndroidX包也刚推出了一个叫SplashScreen的同名API。我们都知道AndroidX归属Jetpack,是独立于SDK版本以外的开发框架集合,很显然它就是用来兼容低版本的Splash Screen功能库。

这可谓是及时雨啊,必须研究一下,让Splash Screen功能充分发挥它的作用!加上之前分享的文章没有提及实现原理,本文一并探讨一下。

新成员SplashScreen

Jetpack SplashScreen是向后兼容Android 12 Splash Screen功能的开发库。其最早支持到Android 6(API 23),全球的Android设备中6及以上的版本占用率达到了9成以上,可以说完全够用了。

2.1 作用以及局限

Splash Screen功能打造的启动画面分为进场和退场两部分:前者负责展示Icon、Icon动画以及品牌Logo等基本信息,后者则用于展示整体或Icon视图过渡到目标画面的动画效果。

对于退场部分而言,无论是否运行在12上,Jetpack SplashScreen库都能达到Android 12同等效果;但进场部分如果是运行在低版本上,暂时存在些局限。

  • 暂不支持展示Icon动画:AnimatedVectorDrawable

  • 暂不支持配置Icon背景:IconBackgroundColor

  • 暂不支持设置品牌Logo:BrandingImage

备注:后面会讲到SplashScreen库的实现原理,面向低版本的进场效果本质上是一个LayerDrawable。说实话,对于支持Vector Drawable动画、Adaptive Icon背景是无能为力的。但笔者认为在代码层面加入些额外处理,是可以做到完美支持的,期待后期升级能完善一下!

2.2 导入依赖

SplashScreen库的最新版本为1.0.0-alpha01,Gradle里简单依赖即可。

dependencies {    def core_version = "1.6.0"    ...    // Optional - APIs for SplashScreen, including compatiblity helpers on devices prior Android 12    implementation "androidx.core:core-splashscreen:1.0.0-alpha01"}
2.3 SplashScreen库预设主题

SplashScreen库针对启动画面的打造定义了专用的Attr,比如设置Icon和画面背景的windowSplashScreenAnimatedIcon和windowSplashScreenBackground,以及设置启动画面退出后Activity主题的postSplashScreenTheme等。

低版本并不支持Icon动画,也就谈不上配置时长,但库还是提供了相关的属性windowSplashScreenAnimationDuration,原因不太清楚。

<attr format="reference" name="postSplashScreenTheme"/><attr format="reference" name="windowSplashScreenAnimatedIcon"/><attr format="integer" name="windowSplashScreenAnimationDuration"/><attr format="color" name="windowSplashScreenBackground"/>
此外为了简化App端的配置,还预设了主题,同时并针对设备版本进行了区分。

如下是面向低版本的主题:

<style name="Theme.SplashScreen" parent="Theme.SplashScreenBase">    <item name="postSplashScreenTheme">?android:attr/theme</item>    <item name="windowSplashScreenAnimationDuration">@integer/default_icon_animation_duration</item>    <item name="windowSplashScreenBackground">@android:color/background_light</item>    <item name="windowSplashScreenAnimatedIcon">@android:drawable/sym_def_app_icon</item></style>
<style name="Theme.SplashScreenBase" parent="android:Theme.NoTitleBar">    <item name="android:windowBackground">@drawable/compat_splash_screen</item>    <item name="android:opacity">opaque</item>    <item name="android:windowDrawsSystemBarBackgrounds">true</item>    <item name="android:fitsSystemWindows">false</item>    <item name="android:statusBarColor">@android:color/transparent</item>    <item name="android:navigationBarColor">@android:color/transparent</item></style>

比较关键的是父主题SplashScreenBase设置了windowBackground属性,其指向的Drawable负责展示低版本的启动画面。

原理很简单:读取windowSplashScreenBackground设置的背景ColorDrawable和windowSplashScreenAnimatedIcon设置的图标Drawable,图标 Drawable放置到背景Drawable的中央,然后组合成Layer Drawable。老实说,跟我们以前自定义启动画面的思路是类似的。

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">    <item android:gravity="fill">        <color android:color="?attr/windowSplashScreenBackground" />    </item>    <item        android:drawable="?attr/windowSplashScreenAnimatedIcon"        android:gravity="center"        android:width="@dimen/splashscreen_icon_size"        android:height="@dimen/splashscreen_icon_size" /></layer-list>

因12版本提供了Splash Screen功能的系统属性,所以针对12及以上的主题,只需要将库定义的属性转化为系统属性。

<style name="Theme.SplashScreen" parent="android:Theme.DeviceDefault.NoActionBar">    <item name="android:windowSplashScreenAnimatedIcon">?windowSplashScreenAnimatedIcon</item>    <item name="android:windowSplashScreenBackground">?windowSplashScreenBackground</item>    <item name="android:windowSplashScreenAnimationDuration"> ?windowSplashScreenAnimationDuration</item></style>

打造进场效果

在原有的Demo上适配新的API,感觉缺乏新意。之前用Jetpack Compose复刻的Flappy Bird游戏没来得及设计启动画面,那这次就利用Jetpack的SplashScreen库完善一下。

3.1 准备动画图标

Flappy Bird游戏的Logo是一个小鸟,目前只有一张PNG,要定制Icon动画效果的话要转为SVG。

找了很多工具,终于发现一个可以将PNG完美转换为SVG的网站:支持添加和删除各种颜色区域,进而可以最大程度地还原每个色块,每个Path。

https://www.pngtosvg.com/

通过AS自带的Vector Asset Tool和Animated Vector Drawable标签,可以将SVG扩展成格式效果的矢量图动画文件。

<animated-vector ...>    <aapt:attr name="android:drawable">        <vector            android:width="400dp"            android:height="404.73373dp"            ...>            <group                android:name="BirdGroup"                android:pivotX="200"                android:pivotY="202"                android:rotation="0">                <path                    android:fillColor="#503745"                    android:fillType="evenOdd"                    android:pathData="M173.7,133.065C173.419,133.178 ..."                    android:strokeColor="#00000000" />                ...            </group>        </vector>    </aapt:attr><target android:name="BirdGroup">    <aapt:attr name="android:animation">      <set>        <objectAnimator            android:duration="@integer/icon_animator_duration"            android:interpolator="@android:anim/decelerate_interpolator"            android:propertyName="translateX"            android:valueFrom="@integer/icon_animator_translate_from"            android:valueTo="@integer/icon_animator_translate_to" />        ...      </set>    </aapt:attr>  </target>
</animated-vector>

3.2 适配主题

Android 12上给入口Activity指定Splash Screen功能的相关属性就可以实现进场效果。SplashScreen库也是一样的思路,不过为了简化代码,我们可以给Activity配置库预设好的主题。

因App端要给这些属性指定独有的资源,所以主题还需要简单扩展下,同时也要针对版本进行区分。如果碰巧最早也支持到Android 6的话,配置默认主题和面向12的values-v31主题即可,不然还需要配置面向6~11的values-23主题。

默认主题必须继扩展自预设主题Theme.SplashScreen,同时覆写一下Icon、Duration和ScreenBackground三个属性。因面向12的主题的部分属性和默认主题是一致的,所以将共通的部分抽出到Base中复用。

<style name="JetpackSplashTheme" parent="JetpackSplashTheme.Base"></style>
<style name="JetpackSplashTheme.Base" parent="Theme.SplashScreen">    ...    <item name="windowSplashScreenAnimatedIcon">@drawable/ic_icon_bird_small_animated_translate</item>    <item name="windowSplashScreenAnimationDuration">@integer/icon_animator_duration</item>    <item name="windowSplashScreenBackground">@color/appBackground</item><item name="postSplashScreenTheme">@style/SplashActivityTheme</item></style>
<style name="SplashActivityTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">    <item name="colorPrimary">@color/purple_500</item>    <item name="colorPrimaryDark">@color/purple_700</item>    <item name="colorAccent">@color/teal_200</item></style>

需要提醒的是,最好指定下postSplashScreenTheme属性。因为后续的定制效果需要用到SplashScreen类,而它将严格检查要求这个属性的配置,否则会发生Crash。

Cannot set AppTheme. No theme value defined for attribute postSplashScreenTheme.

另外,大部分Activity一般继承自AppCompat框架提供的Activity,该框架要求画面必须配置AppCompat系主题。也就是说,postSplashScreenTheme属性不仅要指定,还得是AppCompat系主题!

备注:AppCompat框架作此限制的原因,可以在之前写的文章里查看「深度解读Jetpack框架的基石-AppCompat」。

You need to use a Theme.AppCompat theme!

通过复用共通主题,面向12的主题只需要指定Icon背景属性IconBackgroundColor和品牌Logo属性BrandingImage。

<style name="JetpackSplashTheme" parent="JetpackSplashTheme.Base">    <item name="android:windowSplashScreenIconBackgroundColor">@color/skyBlue</item>    <item name="android:windowSplashScreenBrandingImage">@drawable/ic_tm_brand_newer</item></style>

下面是分别运行在Android 8.1和12上的启动画面进场效果,小鸟从左边入场并逐渐放大的动画。

再比如小鸟旋转着放大进场动画。

事实上Animated Vector Drawable所支持的Path动画更加炫酷和灵活,可以打造更丰富的动画体验,大家可以试试!

3.3 优化低版本的进场Icon

通过对比,可以看到8.1的进场效果里确实没有展示Icon动画,也没有Icon背景和品牌Logo。为了尽可能和12的进场效果接近,可以将低版本主题的图标属性改为Adaptive Icon,面向12的主题才使用动画Icon。

<!-- 默认主题:面向12之前的版本 --><style name="JetpackSplashTheme" parent="JetpackSplashTheme.Base">    <!-- Adaptive icon drawable -->    <item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher_bird_final</item></style>
<!-- 面向12的版本 --><style name="JetpackSplashTheme" parent="JetpackSplashTheme.Base">    <!-- Animated vector drawable -->    <item name="windowSplashScreenAnimatedIcon">@drawable/ic_icon_bird_small_animated_translate</item>    ...</style>

延长启动画面

4.1 获取SplashScreen定制入口

上述主题配置完就可以实现进场效果了,但为了进一步控制启动画面或定制退场效果,还得取得控制的入口即SplashScreen类。SplashScreen库提供的是installSplashScreen()。

class MainActivity : ComponentActivity() {    private val viewModel: GameViewModel by viewModels()override fun onCreate(savedInstanceState: Bundle?) {        ...        // Need to be called before setContentView or other view operation on the root view.        val splashScreen = installSplashScreen()setContent { ... }SplashScreenController(splashScreen, viewModel).apply {            customizeSplashScreen()        }    }}有一点需要留意一下:installSplashScreen()必须先于setContentView()调用,原因在于install函数会获取postSplashScreenTheme属性指定的主题,并在检查通过后setTheme给Activity。

需要明确一下:install函数只是执行一下主题和资源方面的初始化,此刻尚未加载退场使用的视图。

4.2 控制启动画面展示时长

当App的第一帧开始描画,启动画面Window即Splash Screen Window将退出。若描画已经开始,但部分关键逻辑还没执行完,这将影响完整的展示。这时候我们可以适当地延长启动画面,让用户再等一会儿。

Android 12并没有提供控制启动时长的API,之前的Demo是通过向ContentView注册OnPreDrawListener回调挂起描画来实现的。(挂起描画间接导致Splash Screen Window的延迟退出,进而达到延长启动画面的目的。)

SplashScreen库提供了专用API来处理这种需求,即KeepOnScreenCondition。我们需要告知SplashScreen需要持续展示的条件,在条件被破坏之前WMS将维持它的展示。事实上这个API的实现原理跟之前的思路是一致的。

class SplashScreenController( ... ) {    fun customizeSplashScreen() {        keepSplashScreenLonger()        ...    }// Keep splash screen showing till data initialized.    private fun keepSplashScreenLonger() {        splashScreen.setKeepVisibleCondition { !viewModel.isDataReady() }    }}可以看到启动画面的展示时间明显变长了。

定制退场效果

为了使得启动画面能完美过渡到目标画面,退场效果的定制格外重要。而且相较于进场只能定制Icon动画,退场则能定制整体和Icon的各式动画,空间更大,也更灵活。

5.1 执行退场动画

Splash Screen Window在退出的时候,是执行退场动画的时机。通过SplashScreen库提供的OnExitAnimationListener API可以拿到这个时机,它同时会返回启动画面视图,便于后续的定制。

不同于Android 12,SplashScreen库启动画面视图统一封装到了SplashScreenViewProvider中。这个API将依据版本返回对应视图:低版本是自定义的FrameLayout,12则是版本专属的SplashScreenView。

fun customizeSplashScreen() {    ...    customizeSplashScreenExit()}
// Customize splash screen exit animator.private fun customizeSplashScreenExit() {    splashScreen.setOnExitAnimationListener { splashScreenViewProvider ->        val onExit = {            splashScreenViewProvider.remove()        }        showSplashExitAnimator(splashScreenViewProvider.view, onExit)        showSplashIconExitAnimator(splashScreenViewProvider.iconView, onExit)    }}

无论运行在哪个版本,通过统一提供的SplashScreenViewProvider实例,可以执行一致的动画效果。

比如针对整体视图做缩放和淡出动画。

private fun showSplashExitAnimator(splashScreenView: View, onExit: () -> Unit = {}) {    val alphaOut = ObjectAnimator.ofFloat(        splashScreenView,        ...    )    val scaleOut = ObjectAnimator.ofFloat( ... )    AnimatorSet().run {        duration = defaultExitDuration        interpolator = AnticipateInterpolator()        playTogether(scaleOut, alphaOut)        start()    }}

针对Icon视图做缩放、淡出和位移的动画。目的是让小鸟渐渐缩小并上移至游戏画面中,即无缝过渡到游戏区域。

private fun showSplashIconExitAnimator(iconView: View, onExit: () -> Unit = {}) {    val alphaOut = ObjectAnimator.ofFloat(        iconView,        ...    )    val scaleOut = ObjectAnimator.ofFloat( ... )    val slideUp = ObjectAnimator.ofFloat( ... )AnimatorSet().run {        ...        playTogether(alphaOut, scaleOut, slideUp)        doOnEnd {            onExit()        }        start()    }}

注意:退场动画结束后一定要调用remove()手动移除视图,否则可能会看到启动画面一直盖在App上,无法消失。对于12来说,这实际上是系统返回的SplashScreenView Surface一直残留在画面上,而低版本则是FrameLayout仍残存在ContentView树里。

5.2 优化退场动画时长

App开始描画的时候,无论进场动画是否执行完,Splash Screen Window都将退出,同时执行预设的退场动画。而设备性能的优劣或负荷状态的不同,又会影响App描画的开始时机,所以退场动画的执行时机并不固定,随着设备的状况略微变化。

  • 如果App描画得早,进场动画可能还没执行完。为了让用户更快看到目标内容,退场动画可以缩短执行时长,比如直接沿用进场动画的剩余时长

  • 相反,如果描画得晚,进场动画早结束了。如果退场动画还再占用时间,那将严重耽误用户看到目标内容,造成启动卡顿的坏印象。所以这时候,退场动画可以执行很短的固定时长,甚至可以不执行

换句话说,退场动画本意是后台加载的缓冲和过渡,不能为了单纯展示动画牺牲启动体验,可以灵活控制退场的占用时长。

为了便于计算进场Icon动画的剩余时长,SplashScreen库提供了获取其开始时刻和总时长的API:

/** * Start time of the icon animation. * * On API 31+, returns the number of millisecond since the Epoch time (1970-1-1T00:00:00Z) * * Below API 31, returns 0 because the icon cannot be animated. */public val iconAnimationStartMillis: Long get() = impl.iconAnimationStartMillis
/** * Duration of the icon animation as provided in attr. */public val iconAnimationDurationMillis: Long get() = impl.iconAnimationDurationMillis

下面的代码演示了在退场动画执行前判断下进场动画是否完毕,完毕的话沿用动画剩余的时间,否则放弃执行。

private fun getRemainingDuration(splashScreenView: SplashScreenViewProvider): Long {    // Get the duration of the animated vector drawable.    val animationDuration = splashScreenView.iconAnimationDurationMillis    // Get the start time of the animation.    val animationStart = splashScreenView.iconAnimationStartMillis    // Calculate the remaining duration of the animation.    return if (animationDuration == 0L || animationStart == 0L)        defaultExitDuration    else (animationDuration - SystemClock.uptimeMillis() + animationStart)        .coerceAtLeast(0L)}

前面说过低版本在进场的时候不支持Icon动画,那自然没有必要计算剩余时长。所以运行在低版本上的话,这两个API总是返回默认值0,值得留意!

private open class ViewImpl(val activity: Activity) {    open val iconAnimationStartMillis: Long get() = 0    open val iconAnimationDurationMillis: Long get() = 0    ...}

SplashScreen实现原理

Android 12的源码尚未公开,下面针对Jetpack SplashScreen库的源码进行解读。※之前觉得SplashScreen库提供的API简单明了,原理应该也不复杂。但深究源码后发现,不少细节和猜测存在出入,还是值得反复思考的。

6.1 总体架构

Activity通过SplashScreen库提供的SplashScreen实例可以获取到控制启动画面的入口,其内部将依据OS版本决定采用12的专属API还是自定义视图,来展示、延时或移除启动画面。

6.2 installSplashScreen

获取SplashScreen实例的installSplashScreen()将读取并设置目标Activity的Theme。如果运行在低版本上的话,还需要获取Icon和Background的配置。此刻只是获取,并未将退场用的自定义视图添加上来。

6.3 setKeepVisibleCondition

通过setKeepVisibleCondition()可以延长启动画面的展示,无关运行的版本,原理都是向ContentView的ViewTreeObserver注册OnPreDrawListener回调来实现。描画放行的时候,低版本额外需要手动执行退出的回调,12则由系统自行执行。

这个时候退场用的自定义视图仍然还没添加上来,只是延迟了Splash Screen Window的退出而已。

6.4 setOnExitAnimationListener

setOnExitAnimationListener()可以监听退场时机,SplashScreen会将启动画面视图准备好封装到SplashScreenViewProvider中,然后在启动画面需要退出的时候,通过OnExitAnimationListener接口回调。

低版本上视图相关的处理比较多,需要inflate一个自定义布局并添加到ContentView中,然后将install准备好的background和icon反映进来。

面向低版本添加的自定义布局 :

<FrameLayout ...>  <ImageView      android:id="@+id/splashscreen_icon_view"      android:layout_width="@dimen/splashscreen_icon_size"      android:layout_height="@dimen/splashscreen_icon_size"      android:layout_gravity="center" /></FrameLayout>

6.5 adjustInsets特殊处理

SplashScreenViewProvider初始化后会额外调用adjustInsets(),而且只有面向低版本才实现了具体逻辑。一起来研究下这个特殊处理的用意。

先来看下函数的注释:

Adjust the insets to avoid any jump between the actual splash screen and the SplashScreen View.

字面意思是为了避免启动画面和SplashView视图之间发生跳跃,需要调整下视图的参数。好像还不是很理解,再结合下函数的具体实现:

private class Impl23(activity: Activity) : Impl(activity) {    override fun adjustInsets(        view: View,        splashScreenViewProvider: SplashScreenViewProvider    ) {        // Offset the icon if the insets have changed        val rootWindowInsets = view.rootWindowInsets        val ty =            rootWindowInsets.systemWindowInsetTop - rootWindowInsets.systemWindowInsetBottom        splashScreenViewProvider.iconView.translationY = -ty.toFloat() / 2f    }}

默认的adjustInsets()没有具体实现,只有低版本才有具体实现,这又是为什么?

处理的内容很直白:监听SplashScreenViewProvider里存放的View的布局变化,从中获取rootWindowInsets,将覆盖在Window的状态栏和导航栏的高度差值取中值,然后将Icon视图向上或向下进行移动。

  • 前面的原理提到过低版本的进场效果实际上是将一个Layer Drawable设置到了Window Background上,Icon在该Drawable里的位置是居中的,所以Icon在Splash Screen Window里也完全居中

  • 但退场效果的画面视图并非Window Background Drawable,而是向ContentView中手动添加的Framelayout布局。对于布局来说,Icon视图是居中的,但对于它所属的Window来说,因顶部的Statusbar和底部的NavigationBar的高度不同,最终Icon视图在Window整体的位置并不完全居中。尤其当设备采用Gesture Navigation导航模式的话,Icon视图偏下的情况更明显

  • 12的退场视图可以说是Splash Screen Window的完整拷贝,不是自定义的ContentView子布局,不存在这个问题

总结来说,如果不加干预的话,低版本上从进场的Window Background到退场的FrameLayout时,App的Icon会发生错位或跳跃的违和感!

6.6 remove

setOnExitAnimationListener调用后,SplashScreen库会将启动画面的视图回调回来,执行完退场动画后需要调用remove()手动移除视图,它的原理很简单:

  • 12之前就是将FrameLayout从ContentView树上移除

  • 12则是调用专用的API即SplashScreenView#remove()

API总结

对SplashScreen库几个关键API做个整理:

并和Android 12提供的版本做个简单对比:

本文Demo

https://github.com/ellisonchan/ComposeBird

适配Splash Screen功能后的Compose版Flappy Bird游戏的整体效果:

未决事项

通过上面的阐述已经知道:低版本上展示退场效果的启动画面,实质上是临时添加到App内部的FrameLayout。而12上显然不是这种方式,通过Dump命令发现退场的时候也没有创建额外的Window。

  • 那么12上启动画面Window退出后提供给App定制退场效果的SplashScreenView到底是什么?

  • 假使这个“View”并不属于App本身,那么App能够访问它并对属性做出改动,是如何实现的?

在决定搁置这个疑惑的时候,偶然发现下12上启动画面执行过程中会打印Splash相关关键字的系统日志:

StartingSurfaceDrawer: addSplashScreen com.ellison.flappybird: nonLocalizedLabel=null theme=7f1000e7 task= 334

StartingSurfaceDrawer: window attributes color: ffecfcdd icon android.graphics.drawable.AnimatedVectorDrawable...

StartingSurfaceDrawer: fillViewWithIcon surfaceWindowView android.window.SplashScreenView...

StartingSurfaceDrawer: Copying splash screen window view for task: 334 parcelable? android.window.SplashScreenView$SplashScreenViewParcelable

StartingSurfaceDrawer: Task start finish, remove starting surface for task 334

StartingSurfaceDrawer: Removing splash screen window for task: 334

于是我大胆猜测,StartingSurfaceDrawer负责通过WMS读取、展示和移除App设置的Splash Screen Window视图。并在Window退出前通过WMS通知目标Activity的PhoneWindow,从Splash Screen Window里复原视图,然后将复原得到的SplashScreenView添加到App画面上。因为PhoneWindow直接持有DecorView,所以可能直接附着到DecorView上去。

StartingSurfaceDrawer到底是谁、猜测是否正确以及具体的细节,还有待12的源码公开后再做深入地研究~

结语

起初推测因为Android 6以上的版本已是市场主流,才决定让SplashScreen库最早兼容到这个版本。后面探究原理的时候,发现其内部调用的adjustInsets()处理依赖一个版本6才加入的API,所以又感觉这才是兼容到6的真正原因。

兼容到Android 6版本是出于占用率的考虑还是源于API的限制已不再重要,关键是Splash Screen功能已支持足够多的Android设备。

之前你们说Android 12全新的Splash Screen功能虽然很酷,但不兼容低版本,略显鸡肋。如今有了Jetpack SplashScreen库的加持,总该适配起来了吧?

参考资料:

SplashScreen官方文档:https://developer.android.google.cn/about/versions/12/features/splash-screen

A peek inside Jetpack Core Splashscreen:                    https://dev.to/tkuenneth/a-peek-inside-jetpack-core-splashscreen-odo

Meet the Jetpack Splashscreen API: https://www.tiagoloureiro.tech/posts/definitive-guide-for-splash-screen-android/

☞程序员被纳入新生代农民工;“腾讯视频崩了”上热搜;英特尔发布全新独立显卡品牌 Arc|极客头条☞谷歌:开源捐赠需分成,否则下架!☞敏捷20周年:一场失败的起义

Jetpack 新成员 SplashScreen:为全新的应用启动效果赋能!相关推荐

  1. Jetpack新成员,Paging3从吐槽到真香

    本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新. 各位小伙伴们大家早上好. 随着Android 11的正式发布,Jetpack家族也引入了许多 ...

  2. Jetpack新成员,一篇文章带你玩转Hilt和依赖注入

    本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新. 各位小伙伴们大家早上好. 终于要写这样一篇我自己都比较怕的文章了. 虽然今年的Google ...

  3. 戴尔PowerEdge-C服务器新成员

    彭宇恒,戴尔大型企业事业部下一代数据中心解决方案高级市场经理 熟悉戴尔服务器的朋友都知道,戴尔PowerEdge-C系列是机架式服务器中很有特色的一个产品线,它以戴尔的数据中心解决方案(DCS)业务为 ...

  4. 11月25日发!余承东官宣华为Mate新成员:最强悍高端平板?

    [TechWeb]随着全面屏手机的飞速迭代更新,人们对之前最受欢迎的硬件产品之一的平板电脑的需求迅速下滑,这从越来越少的平板品牌和新机的推出频率就可见一斑.不过在近日,华为消费者业务CEO余承东正式官 ...

  5. 戴尔PowerEdge-C服务器新成员:PowerEdge C5125和C5220

    彭宇恒,高级经理, 下一代数据中心解决方案,戴尔大型企业事业部 熟悉戴尔服务器的朋友都知道,戴尔PowerEdge-C系列是机架式服务器中很有特色的一个产品线,它以戴尔的数据中心解决方案(DCS)业务 ...

  6. 富士通扫描仪PaperStream系列软件喜迎新成员;B2B音乐授权市场Songtradr完成D轮融资并获超额认购| 全球TMT...

    国内市场 泛亚数据中心公司Big Data Exchange(BDx)位于中国南京的数据中心园区启动,目前一期机房(NKG1)已正式投入运营.NKG1是该市第一个获得Uptime Institute设 ...

  7. 开年纳新|天空卫士家族荣誉谱上再添多名新“成员”

    2021年是我国网络与数据安全产业具有里程碑意义的一年,对天空卫士来说,也是收获颇丰的一年.成绩.荣誉.奖项.好评是我们给时间最好的答卷.有了2021的积累,2022在开年之初就收获了不错的成绩.1月 ...

  8. 阿里新成员「瓴羊」正式亮相,由阿里副总裁朋新宇带队,集结多个核心部门技术团队

    继天猫.菜鸟.蚂蚁.考拉.盒马.神马.飞猪.犀牛.平头哥之后,阿里又迎来了一位新成员. 6月29日,阿里宣布瓴羊智能服务公司"羚羊"正式加入阿里动物园. 阿里方面介绍,瓴羊寓意&q ...

  9. 聚焦“复兴号” 新成员开启新征程

    继350公里时速"复兴号"中国标准动车组在京沪高铁线投入运行后,"复兴号"家族将增添新成员.25日,中国铁路总公司在上海宣布,为满足不同线路的运输需求,时速25 ...

最新文章

  1. WCF服务重构实录(上)
  2. android自定义view获取控件,android 自定义控件View在Activity中使用findByViewId得到结果为null...
  3. 【第六期】拿不到offer全额退款 人工智能工程师培养计划招生
  4. 云服务收入年增长28%
  5. 如何使‘CREATE TABLE AS SELECT’能支持ORDER BY ?
  6. 约三分之二的 DDoS 攻击指向通信服务提供商
  7. spring dao层注解_Spring– DAO和服务层
  8. Codeforces Round #321 (Div. 2) C. Kefa and Park dfs
  9. xCode 安装Mobile Device Framework出错的问题的解决方法
  10. 分布式应用中的一致性协议
  11. 如何自定义一个异常类
  12. JSP技术-02-内置对象/作用域/EL表达式/JSTL标签库
  13. galton板matlab,Matlab实现Galton板的动画演示
  14. 永久免费使用免费20G空间的推荐
  15. 个人中心(修改密码)
  16. idea git回退到某个历史版本
  17. Flutter的菜鸟教程
  18. 伺服电机选型常见的五大方面
  19. 跑路了,去东北国企干软件测试一个月的感触
  20. git进行push报错: RPC failed; HTTP 403 curl 22 The requested URL returned error: 403 Forbidden

热门文章

  1. 遍历hashmap 的四种方法
  2. c++ 函数参数问题
  3. Native层和so接口和Java层
  4. python数据类型—字符串
  5. 从一次线上故障思考Java问题定位思路
  6. RxJava 和 RxAndroid 四(RxBinding的使用)
  7. golang——channel笔记
  8. 黄金周张家界之行(1)
  9. [原]ASP.NET中使用JQUERY-EASYUI后,解决ClientScript.RegisterStartupScript 所注册脚本执行两次...
  10. C#自定义数字格式字符串