正確實踐Jetpack SplashScreen API —— 在所有Android系統上使用總結,內含原理分析

語言: CN / TW / HK

theme: arknights highlight: androidstudio


1.前言

文章末尾有演示的APK連結,感興趣的同學,可以自行下載體驗一下

官方Android 12的Splash Screen文件地址
官方Splash Screen相容庫,支援所有版本系統

本篇文章主要圍繞下面三個問題來介紹: - 我們能從Android 12 SplashScreen API裡面學到什麼? - 新出的SplashScreen相容庫又是什麼?能做成什麼樣子? - 小甲同學:我想看Android12 SplashScreen原始碼,可以嗎?

前方高能預警:一定要記得『點贊❤️+關注❤️+收藏❤️』起來,划走了可就再也找不到了😅😅🙈🙈

進入正題,我們先介紹:SplashScreen如何使用,以及目前會遇到的問題,如何無縫過渡?會出現什麼問題,怎麼解決?

2.SplashScreen使用

首先我們需要把compileSdk和targetSdk(可選)升級到31

2.1.Android12版本

(A).主題和外觀配置

```xml

@color/...

@drawable/...

1000

@color/...

@drawable/... ```

(B).延長啟動畫面

kotlin val content: View = findViewById(android.R.id.content)     content.viewTreeObserver.addOnPreDrawListener(         object : ViewTreeObserver.OnPreDrawListener {             override fun onPreDraw(): Boolean { // 模擬一些資料的初始化,再取消掛起                 return if (viewModel.isReady) {                     // 取消掛起,恢復頁面內容繪製                     content.viewTreeObserver.removeOnPreDrawListener(this)                     true                 } else {                     // 掛起,內容還沒有準備好                     false                 }             }         }     )

(C).關閉啟畫面的動畫

kotlin // 自己定製關閉的動畫 splashScreen.setOnExitAnimationListener { splashScreenView ->         val slideUp = ObjectAnimator.ofFloat( // 你們自己控制,自己隨便寫什麼動畫,這裡我們測試讓圖示移動             splashScreenView.iconView,             View.TRANSLATION_Y,             0f,             -splashScreenView.height.toFloat()         )         slideUp.interpolator = AnticipateInterpolator()         slideUp.duration = 200L         slideUp.doOnEnd { splashScreenView.remove() }         slideUp.start()     }

(D).遇到的問題

  • android:windowSplashScreenBrandingImage 定義的圖片尺寸要求是多少?總覺得有點拉伸;
  • 使用AnimationDrawable 或者 AnimatedVectorDrawable,來設定中心圖示,會出現“中心圖示”消失的情況,靜態圖示不會有這種問題出現;
  • Android12父主題設定android:windowBackground被覆蓋,看不到效果

問題1: 在原始碼裡面也沒有看到具體的值或者比例大小,怎麼辦呢?

小技巧: 使用一個超大的正方形的圖示設定進去測試了一下,拉伸不要緊,我們要的是比例, 然後測量了一下比例為2.5 : 1,所以設計品牌名圖示的時候,可以設定為400 * 160這樣的比例為2.5:1的圖示

問題2: 針對中心圖示會閃現消失的問題做測試,
測試一:靜態Icon、測試二:動態Icon


靜態中心圖示 - 正常

下面我們來測試動態中心圖示,為了方便測試出效果,我們覆蓋住圖示後面的背景色,方便對比,最後發現:測試結果不太理想,效果不行

```xml

```


動態中心圖示,不正常
仔細看圖示後面的「藍色背景」

我們再來看一下官方文件中的順滑效果


官方效果順滑

對比官方的效果,猜測可能是模擬器和預覽版Android12的問題,主要是沒有真機來測試Android12這個效果,不過這難不到我們,如果你的模擬器也有同樣問題,請跟著我們做如下操作:

下面我們使用AnimatedVectorDrawable來製作動態圖示,
為了觀察出效果:我們開啟模擬器的開發者選項,找到Animator時長縮放設定為:動畫時長x10,來往下看效果:


10倍慢放 - 看著才正常

笑臉眼睛動畫的向量圖檔案 👇👇,點選檢視線上製作向量圖動畫 ```xml

`` 後來我們又用了AnimationDrawable測試了一下慢放效果也不行,你仔細想一下:圖片輪播放效果能好嗎? 所以:AnimationDrawable不推薦`,我們這裡推薦使用:AnimatedVectorDrawable為向量圖新增動畫效果

問題3: Android12父主題設定android:windowBackground被覆蓋,看不到效果

不要緊,只要我們的UI設計師(美工)按照如下尺寸規範來設計,使用靜態中心圖示,一樣可以實現同樣效果:
中心圖示: 圖示內容區域內邊距2/3,防止元素被切
品牌名圖示: 設計的尺寸比例為:2.5:1

2.2.SplashScreen相容庫

點選檢視官方Splash Screen相容庫文件

(A).依賴庫

點選檢視Core庫裡面的最新版本 gradle // 可在所有Android版本上使用的相容庫 implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'

(B).主題和外觀配置

  • 定義Activity應該使用的主題

```xml

``` - 建立父主題給啟動畫面使用

```xml

``` - AndroidManifest.xml配置Activity的主題

xml <manifest> <application android:theme="@style/Theme.App.Starting"> <!-- application和activity,兩個選一個配置: @style/Theme.App.Starting --> <activity android:theme="@style/Theme.App.Starting"> ...

(C).初始化SplashScreen

java override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val splashScreen = installSplashScreen() setContent { ...... } splashScreen.setKeepVisibleCondition { !mainViewModel.mockDataLoading() } splashScreen.setOnExitAnimationListener(this) }

(D).中心圖示大小修改

xml <item name="splashScreenIconSize">@dimen/....</item>

(E).遇到的問題

相容庫目前存在的問題 - 沒有android:windowSplashScreenBrandingImage這個屬性 - 配置了中心圖示,會裁剪成圓形 - 低版本系統不配置windowSplashScreenAnimatedIcon會出現預設的Icon - Android12父主題設定android:windowBackground被覆蓋,看不到效果

問題1: 是因為相容庫的layout檔案目錄下面的splash_screen_view.xml沒有“品牌名的檢視”,大家點選檢視一下,兩個佈局的xml內容就知道了:
frameworks下面的Android12的splash_screen_view.xml
core-splashscreen下面的相容庫的splash_screen_view.xml

但是我們在Android12即values-v31的themes.xml裡面依然可以配置android:windowSplashScreenBrandingImage這個屬性,因為Android12的SplashScreen是整合在frameworks裡面的;

問題2: 是因為相容庫裡面使用了MaskedDrawable包裝了Icon,會裁剪成圓形,圖示內容設計要保留2/3的內邊距,否則會出現內容被裁剪掉的問題;
如何修復這個裁剪圓形問題呢?

把原始碼拷貝出來,總共就3個原始碼檔案,自己複製出來修改刪除也可以的
或者,圖示設計準則為:內容保留內邊距為2/3,防止元素被裁剪

問題3: 寫一個透明的drawable.xml然後替換就行了,類似如下方式

```xml

`` **問題4:** Android12**父主題**設定android:windowBackground`被覆蓋,看不到效果

不要緊,只要我們的UI設計師(美工)按照如下尺寸規範來設計,使用靜態中心圖示,一樣可以實現同樣效果:
中心圖示: 圖示內容區域內邊距2/3,防止元素被切
品牌名圖示: 設計的尺寸比例為:2.5:1

(F).製作一個啟動頁

  • 1.模仿快手App的啟動頁 只需要配置父主題的android:windowBackground

Android5.0 ~ Android11 效果

由於我們在文章上面介紹到Android12上,無法為SplashScreen設定父主題的android:windowBackground,但我們依然可以通過配置靜態中心圖示來做到一樣的效果的,請看下面的效果:


Android12 效果

如果你的UI設計師,給你向量圖,那麼你就可以讓中心圖示在Android12系統上動起來了😆
另外,可以建議UI設計師:統一所有系統上,啟動頁“中心”圖示,居中展示,不然會有點怪

  • 2.動態圖示啟動頁 如果設計成動態啟動圖示,這個需要考慮2個因素:

    一、 低版本系統表現效果不一致,有些系統裡面,動態圖示只在啟動頁關閉的時候才顯示(親測Android平板5.1.1系統就是這樣的);
    二、 如何相容低版本系統,是先展示底部品牌名,最後只能等動態圖示顯示咯?

如果喜歡折騰的同學,可以多測試測試,我在使用Android10.0測試動態圖示的時候,效果看著還可以,具體系統下限在哪?
這個看你們產品設計定位了,還有測試妹妹是否同意你們用,效果是否符合你們的產品;

建議的做法是:
- Android 5.0 ~ Android 11.0系統,都統一使用android:windowBackground配置啟動頁背景 - Android12.0 如果UI設計師給你做了向量圖,你可以做動態的中心圖示,不給你,使用靜態圖示也可以的,參考上面:模擬快手App啟動頁
為了在模擬器上能正常顯示出效果,我們在模擬器的開發者選項,找到Animator時長縮放設定為:動畫時長x10,放慢10倍,缺真機測試😂😂😂😂😂


Android12 動態啟動頁圖示

3.原始碼分析

我們這裡只分析Android12 SplashScreen,相容庫沒有太多內容不足500行,感興趣的同學可以自己閱讀一下

我們在XXXActivity裡面第一次用到了splashScreen.setOnExitAnimationListener,從這裡開始往源頭開始找,在下面的方法初始化了SplashScreen

java //android.app.Activity private SplashScreen getOrCreateSplashScreen() { synchronized (this) { if (mSplashScreen == null) { mSplashScreen = new SplashScreen.SplashScreenImpl(this); } return mSplashScreen; } } 我們來看SplashScreenImpl實現類

```java //android.app.Activity

class SplashScreenImpl implements SplashScreen { ...... //把SplashScreenImpl新增到這個單例類裡面 private final SplashScreenManagerGlobal mGlobal; public SplashScreenImpl(Context context) { mGlobal = SplashScreenManagerGlobal.getInstance(); } @Override public void setOnExitAnimationListener(@NonNull SplashScreen.OnExitAnimationListener listener) { ...... mGlobal.addImpl(this); // 用於後面執行啟動畫面將退出的回撥 } ...... public void setSplashScreenTheme(@StyleRes int themeId) { ...... try { //設定啟動畫面的主題 AppGlobals.getPackageManager().setSplashScreenTheme(......); } catch (RemoteException e) { Log.w(TAG, "Couldn't persist the starting theme", e); } } }

// 啟動畫面管理器 class SplashScreenManagerGlobal { ...... // 管理多個閃屏實現 private final ArrayList mImpls = new ArrayList<>();

private SplashScreenManagerGlobal() {
    // 向此程序註冊啟動畫面管理器
    ActivityThread.currentActivityThread().registerSplashScreenManager(this);
}
......

private static final Singleton<SplashScreenManagerGlobal> sInstance =
new Singleton<SplashScreenManagerGlobal>() {
    @Override
    protected SplashScreenManagerGlobal create() {
        return new SplashScreenManagerGlobal();
    }
};

private void addImpl(SplashScreenImpl impl) {
    synchronized (mGlobalLock) {
        mImpls.add(impl);
    }
}

private void removeImpl(SplashScreenImpl impl) {
    synchronized (mGlobalLock) {
        mImpls.remove(impl);
    }
}
......
public void handOverSplashScreenView(IBinder token,SplashScreenView splashScreenView) {
    //呼叫的是 => splashScreenView.transferSurface();
    transferSurface(splashScreenView);
    //回撥 => impl.mExitAnimationListener.onSplashScreenExit(view);
    dispatchOnExitAnimation(token, splashScreenView);
}
......

} `` 我們看到初始化SplashScreenManagerGlobal的時候,向此程序註冊啟動畫面管理器`

```java //android.app.ActivityThread

public void registerSplashScreenManager(SplashScreen.SplashScreenManagerGlobal manager) { synchronized (this) { mSplashScreenGlobal = manager; } } 如何把SplashScreen新增到當前的視窗的呢? ActivityThread繼承ClientTransactionHandler,裡面有一個這樣的抽象方法:java //android.app.ClientTransactionHandler public abstract void handleAttachSplashScreenView(@NonNull ActivityClientRecord r, @NonNull SplashScreenViewParcelable parcelable); ``` ActivityThread肯定會實現這個方法,那麼是誰呼叫了它呢?由於篇幅問題,就不一行一行程式碼的去介紹分析了,感興趣的同學,可以自己深入研究,我們下面貼出來呼叫的流程圖

誰呼叫了handleAttachSplashScreenView(),它的呼叫鏈流程圖如下:

好了,看完流程圖,我們再看一下ActivityThread#handleAttachSplashScreenView

```java //android.app.ActivityThread

@Override public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r, @Nullable SplashScreenView.SplashScreenViewParcelable parcelable) { final DecorView decorView = (DecorView) r.window.peekDecorView(); if (parcelable != null && decorView != null) { createSplashScreen(r, decorView, parcelable); } ...... }

private void createSplashScreen(ActivityClientRecord r, DecorView decorView, SplashScreenView.SplashScreenViewParcelable parcelable) { // 初始化SplashScreenView構建器 final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity); // 從parcelable中獲取配置資料,並通過build()初始化SplashScreenView,設定資料 final SplashScreenView view = builder.createFromParcel(parcelable).build(); // 把SplashScreenView新增到DecorView中 decorView.addView(view); // 設定SystemUI顏色 view.attachHostActivityAndSetSystemUIColors(r.activity, r.window); // 重新整理檢視 view.requestLayout(); ...... } ``` 核心的部分原始碼已經分析差不多了,剩下的一些原始碼,感興趣的同學可以自己檢視閱讀

4.總結

  • compileSdk升級到31
  • 產品中統一使用相容庫SplashScreen

gradle implementation 'androidx.core:core-splashscreen:最新版本' - 演示示例中資源目錄

text drawable —— 定義低版本的drawable資源 drawable-v23 —— 定義6.0以上的資源 drawable-v31 —— 定義Android12及以上的資源 values —— 定義預設資源 values-night —— 定義預設深色模式資源 values-v23 —— 定義6.0以上系統資源 values-v31 —— 定義Android12及以上的資源 values-night-v31 —— 定義Android12及以上的深色模式資源 - 啟動頁圖示設計準則 中心圖示大圖,內容需要保留2/3的內邊距否則圖示會被裁剪掉,另外:圖示尺寸大小可以修改;
底部品牌名圖示:尺寸比例需要為 2.5:1
相容庫SplashScreen低版本不支援設定底部品牌圖示,
Android12需要在values-v31目錄手動新增如下屬性,才可以顯示品牌名圖示; ```xml

@drawable/... `` - **Android12以下系統**可以使用android:windowBackground為父主題設定視窗背景,**切記不要**在Android12及以上系統設定父主題的視窗背景(因為沒有效果😅😅)`

  • Android12系統以下系統,使用android:windowBackground的話,一定要給windowSplashScreenAnimatedIcon設定一個透明的drawable,否則會出現機器人圖示
  • windowSplashScreenBackground 這個屬性的顏色一定要注意,配置有問題的話,啟動頁過渡到主頁面的時候,會有這個顏色閃出來,建議和Activity的android:windowBackground配置成一樣的顏色
  • 在啟動畫面上面,新增一個“廣告或者推廣頁面”,程式碼和效果如下:

kotlin override fun onSplashScreenExit(splashScreenViewProvider: SplashScreenViewProvider) { if(splashScreenViewProvider.view is ViewGroup){ // 在這裡新增一個啟動頁廣告或者推廣頁面 val composeView = ComposeView(this@SplashScreenCompatActivity).apply { setContent { SplashAdScreen(onCloseAd = { splashScreenViewProvider.remove() }) } } (splashScreenViewProvider.view as ViewGroup).addView(composeView) return } }


實踐 - 啟動頁新增廣告或者推廣頁

參考地址
- 線上流程圖製作 - 官方文件 Splash screens - Google官網介紹向量圖
- 線上svg編輯器 - 線上製作向量圖動畫 - 線上合併多個GIF製作

文章中示例的演示APK及原始碼地址:

| 靜態圖示啟動頁 | 動態圖示啟動頁
(Android12系統有動畫效果) | 啟動頁加廣告 | | :---: | :---: | :---: | | 下載:SplashScreen快手啟動頁效果的apk001 | 下載:SplashScreen快手啟動頁效果的apk002| 下載:SplashScreen啟動頁廣告apk| | 提取碼:7gj2 | 提取碼:b6ce | 提取碼:fnva |

點選檢視:SplashScreen演示示例的原始碼