Android 12 SplashScreen API

前言

1、什么是Android的冷启动时间?

冷启动时间是指点击桌面LOGO那一刻起到启动页面的Activity调用onCreate()之前的时间段。

2、冷启动的时间段内发生了什么?

在一个Activity打开时,如果Application还没有启动,系统会创建一个进程,进程的创建和初始化会消耗一些时间,在这个时间中,WindowManager会先加载APP的主题样式里的窗口背景(windowBackground)作为预览元素,然后才会真正的加载布局,如果这个时间过长,会给用户造成一种错觉:APP卡顿不流畅

常见做法

  • 将背景设置为APP Logo,市面上大部分APP都是这么做的
<style name="AppTheme" parent="BaseTheme">
    <item name="android:windowBackground">@drawable/window_splash_screen_content</item>
</style>
复制代码
  • 将背景设置为透明色,当点击桌面LOGO时并不会立即启动APP,而是在桌面停留一会(其实已经启动)
<style name="AppTheme" parent="BaseTheme">
    <item name="android:windowBackground">@android:color/transparent</item>
</style>
复制代码

上面做法可以达到秒开效果,但属于掩耳盗铃

Android 8.0

Google以前不推荐使用闪屏的使用,但是后来很多APP都在使用闪屏,Google希望让启动屏的制作更简单。

Android Oreo中提供了Splash Screen API,允许开发者把一个drawable资源设置为闪屏。

新建values-v26目录:

<resources>
    <style name="AppTheme" parent="BaseTheme">
        <item name="android:windowSplashscreenContent">@drawable/window_splash_screen_content</item>
    </style>
</resources>
复制代码

通过windowSplashscreenContent设置的drawable资源将会覆盖在windowBackground顶部,在系统状态栏之下,如果不想受到System Bars限制,请使用全屏主题。

如未设置windowSplashscreenContent,系统仍会使用其他属性来展示闪屏页,如:windowBackground

缺点

  • 无法定义动画
  • 无法控制时长

windowBackground设置时机

以<Android API 30 Platform>为例

PhoneWindowManager

public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
        CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
        int logo, int windowFlags, Configuration overrideConfig, int displayId) {
    ...
    try {
        ...
        final PhoneWindow win = new PhoneWindow(context);
        win.setIsStartingWindow(true);
        win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
        ...

        final WindowManager.LayoutParams params = win.getAttributes();
        ...
        
        addSplashscreenContent(win, context);

        wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
        view = win.getDecorView();
        wm.addView(view, params);
        // Only return the view if it was successfully added to the
        // window manager... which we can tell by it having a parent.
        return view.getParent() != null ? new SplashScreenSurface(view, appToken) : null;
    }
    ...
    return null;
}

private void addSplashscreenContent(PhoneWindow win, Context ctx) {
    final TypedArray a = ctx.obtainStyledAttributes(R.styleable.Window);
    final int resId = a.getResourceId(R.styleable.Window_windowSplashscreenContent, 0);
    a.recycle();
    if (resId == 0) {
        return;
    }
    final Drawable drawable = ctx.getDrawable(resId);
    if (drawable == null) {
        return;
    }
    // We wrap this into a view so the system insets get applied to the drawable.
    final View v = new View(ctx);
    v.setBackground(drawable);
    win.setContentView(v);
}
复制代码

Android 12

Android12 增加了全新的SplashScreen API,可以为所有应用启用新的启动画面,包括启动时的应用运动、显示应用图标的启动画面,以及向应用本身的过渡。

工作原理

当用户启动应用,且应用进程未在运行(冷启动)或Activity尚未创建(温启动)时:

  • 系统使用主题和自定义的动画来显示启动画面
  • 当应用准备就绪时,会关闭启动画面并显示应用

热启动不会显示启动画面。

动画元素和机制

由窗口背景、动画形式的应用图标和图标背景组成:

  • 应用图标(1)应该是矢量可绘制对象,也可以是静态或动画,动画图标会在启动画面显示时自动播放,时长不受限制,但建议不要超过1000毫秒,默认使用Launcher图标
  • 图标背景(2)是可选的,在图标与窗口背景之间需要更高的对比度时很有用
  • 窗口背景(4)由不透明的单色组成

启动画面动画由进入动画和退出动画组成:

  • 进入动画由系统控制,不可自自定义
  • 退出动画可自定义,可访问SplashScreenView以及ICON,可以设置任意动画(位移、透明度、颜色等),需要在动画执行完毕时手动移除启动画面

自定义启动画面

新建values-v31目录

<resources>
    <style name="AppTheme" parent="BaseTheme">
        <!-- 单色填充背景 -->
        <item name="android:windowSplashScreenBackground">#F18C1A</item>
        <!-- 替换ICON -->
        <item name="android:windowSplashScreenAnimatedIcon">@mipmap/ic_loading_logo</item>
        <!-- 设置启动画面在关闭之前显示的时长。最长时间为 1000 毫秒 -->
        <item name="android:windowSplashScreenAnimationDuration">1000</item>
        <!-- 设置启动画面图标后面的背景 -->
        <item name="android:windowSplashScreenIconBackgroundColor">#ffffff</item>
        <!-- 设置要显示在启动画面底部的图片。设计准则建议不要使用品牌图片。 -->
        <item name="android:windowSplashScreenBrandingImage">
            @drawable/window_splash_screen_content
        </item>
    </style>
</resources>
复制代码

让启动画面在屏幕上显示更长时间

当应用绘制第一帧后,启动画面会立即关闭。如果需要异步加载少量数据,可以使用ViewTreeObserver.OnPreDrawListener让应用暂停绘制第一帧。

companion object {
    const val DURATION = 2000
}
private val initTime = SystemClock.uptimeMillis()

val content: View = findViewById(android.R.id.content)
content.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
    override fun onPreDraw(): Boolean {
        return if ((SystemClock.uptimeMillis() - initTime) > DURATION) {
            content.viewTreeObserver.removeOnPreDrawListener(this)
            true
        } else false
    }
})
复制代码

自定义退出动画

可以通过getSplashScreen() 来自定义退出动画

splashScreen.setOnExitAnimationListener { splashScreenView ->
    ...
}
复制代码

向中心缩小退出

@RequiresApi(Build.VERSION_CODES.S)
private fun scaleExit(view: SplashScreenView) {
    ObjectAnimator.ofFloat(view, View.SCALE_X, View.SCALE_Y, Path().apply {
        moveTo(1f, 1f)
        lineTo(0f, 0f)
    }).apply {
        doOnEnd { view.remove() }
        start()
    }
}
复制代码

QQ录屏20210812092947.gif

ICON上滑退出

@RequiresApi(Build.VERSION_CODES.S)
private fun slideUp(view: SplashScreenView) {
    val iconView = view.iconView ?: return
    AnimatorSet().apply {
        playSequentially(
            ObjectAnimator.ofFloat(iconView, View.TRANSLATION_Y, 0f, 50f),
            ObjectAnimator.ofFloat(iconView, View.TRANSLATION_Y, 50f, -view.height.toFloat()),
        )
        doOnEnd { view.remove() }
        start()
    }
}
复制代码

QQ录屏20210812092807.gif

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享