Android 超詳細SplashScreen入門教程
這次的Android系統(tǒng)變化當中,UI的變化無疑是巨大的。Google在Android 12中采取了一種叫作Material You的界面設(shè)計,一切以你為中心,以你的喜好為風(fēng)格。相信大家一旦上手Android 12之后應(yīng)該能立刻察覺到這些視覺方面的變化。
關(guān)于這個SplashScreen,今天就值得好好講一講了。
什么是SplashScreen
SplashScreen其實通俗點講就是指的閃屏界面。這個我們國內(nèi)開發(fā)者一定不會陌生,因為絕大多數(shù)的國內(nèi)App都會有閃屏界面這個功能,很多的App還會利用閃屏界面去打廣告。下圖是QQ的閃屏界面:
然而在海外,閃屏界面其實并不太常見,甚至Google之前都不推薦我們在App中加入閃屏界面,所以這次Android 12中官方推出了SplashScreen功能還是讓我有點意外的。
不過這次官方的SplashScreen和我們國內(nèi)常見的閃屏界面還不一樣,它并不是為了讓你在這個界面打廣告的,而是為了在App啟動初始化的時候避免讓用戶在一個空白界面等待過長時間。
雖說Android一直是建議我們將重量級的操作延后執(zhí)行,讓App的啟動時間越短越好,但是仍然無法完全避免一些App啟動時的短暫白屏情況。
因此,這次的SplashScreen就是為了解決這個問題而推出的,它將會在一定程度上提升用戶體驗,徹底告別過去的啟動白屏現(xiàn)象。
何時會顯示SplashScreen
注意,SplashScreen在Android 12上是強制的,即使你什么都不做,你的App在Android 12上也會自動擁有SplashScreen界面。默認情況下,App的Launcher圖標會作為SplashScreen界面的中央圖標,windowBackground屬性指定的顏色會作為SplashScreen界面的背景顏色。不過這些都可以修改。
關(guān)于如何修改我們稍后再談,既然SplashScreen界面是強制顯示的,我們首先應(yīng)該搞清楚,在什么情況下會顯示SplashScreen?
根據(jù)官方文檔的說明,SplashScreen會在App冷啟動和溫啟動的時候顯示,永遠不會在App熱啟動的時候顯示。
那么,什么是冷啟動、溫啟動和熱啟動呢?
簡單概括一下的話,如果App被完全殺死了,這個時候去啟動它就是冷啟動。如果App的主Activity被銷毀或回收了,這個時候去啟動它就是溫啟動。如果App只是被掛起到了后臺,這個時候去啟動它就是熱啟動。
我這種概括方式在一些細節(jié)方面其實并不足夠準確,但如果只是為了大概了解SplashScreen的顯示時機,那么簡單這樣理解就可以了。
而如果你想更加細致地學(xué)習(xí)這幾種啟動模式的區(qū)別,可以參考以下官方文檔鏈接:
https://developer.android.google.cn/topic/performance/vitals/launch-time
何時會隱藏SplashScreen
SplashScreen是為了防止App在冷啟動或溫啟動的時候初始化時間過長,導(dǎo)致用戶看到白屏現(xiàn)象而引入的。那么很顯然,只要App初始化完成,可以將內(nèi)容展示給用戶的時候,SplashScreen就會自動隱藏。
如果用更加科學(xué)一點的定義來描述的話,那就是當App開始在界面上繪制第一幀的時候,SplashScreen就會消失。
那么一個App什么時候會在界面上繪制第一幀呢?我們可以不用知道它準確的時機,但是要知道它大致的時機范圍,因為這決定要我們?nèi)绾胃玫鼐帉懘a。
假如我們在一個應(yīng)用的主Activity中編寫如下代碼:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Thread.sleep(3000) } }
或者也可以這樣寫:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } override fun onResume() { super.onResume() Thread.sleep(3000) } }
可以看到,我們分別在onCreate()和onResume()方法中讓主線程沉睡了3秒鐘。然后運行一下程序:
你會發(fā)現(xiàn),SplashScreen真的顯示了3秒鐘以上才消失。
同時這也說明了,不管是onCreate()還是onResume()方法,它們都還處于App的初始化階段,并沒有開始在界面上繪制第一幀。
接下來我們可以嘗試這樣改造一下代碼:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val contentView: View = findViewById(android.R.id.content) contentView.post { Thread.sleep(3000) } } }
這里可以借助任何一個View的實例調(diào)用一下它的post函數(shù),并在post的回調(diào)當中讓主線程沉睡3秒。然后再次運行程序:
你會發(fā)現(xiàn),SplashScreen只是短暫顯示了一下就進入了App的主界面。但現(xiàn)在主界面其實還是不能響應(yīng)任何事件的,而是要等待3秒鐘以后才能響應(yīng)。
由此我們就可以大致得出一些結(jié)論,比如說onCreate()和onResume()方法都是在App開始繪制第一幀之前執(zhí)行的,而View的post回調(diào)則是在App繪制第一幀之后執(zhí)行的。
當?shù)谝粠L制出來以后,說明App的界面上已經(jīng)可以有東西展示出來了,將不會再是一個空白界面,此時繼續(xù)展示SplashScreen就沒有意義了,所以SplashScreen理應(yīng)在這個時候消失。但同時,如果在第一幀繪制出來之后我們再在主線程里去執(zhí)行耗時邏輯,那么用戶將會實實在在感受到卡頓的體驗,SplashScreen已經(jīng)無法再幫我們進行掩蓋。
實際上,不管是在第一幀繪制之前還是之后,我們都不應(yīng)該在主線程執(zhí)行長時間的耗時操作。最正確的做法是,只在主線程里做最少的事情,讓App可以快速響應(yīng)用戶的各種輸入事件,將所有耗時的邏輯都放到子線程當中去處理。
延長顯示SplashScreen
延長SplashScreen的顯示時間是一種我不太建議的做法,但我們確實可以這樣做。
先說為什么不建議延長SplashScreen的顯示時間。
原則上我們應(yīng)該讓App的啟動時間越短越好,即使有了SplashScreen,我們也不應(yīng)該故意讓App的啟動時間變得更長。
要知道,在SplashScreen的顯示過程中,App是一直在主線程里執(zhí)行初始化操作的。這也就意味著,你的App主線程是一直被占據(jù)著的,從而無法響應(yīng)用戶的各種輸入,這也就導(dǎo)致了應(yīng)用程序ANR的可能。不管有沒有SplashScreen,只要在主線程里執(zhí)行了過多耗時操作,都可能會導(dǎo)致ANR。
那么為什么還要延長顯示SplashScreen呢?
有一種說法是,他們App的內(nèi)容都是從服務(wù)器或者從本地磁盤讀取的,即使App初始化完成了,數(shù)據(jù)還沒有準備好,也就沒有內(nèi)容可以展示,所以想要將SplashScreen延長到數(shù)據(jù)準備完成。
但我個人認為這并不是一種非常合適的做法,這種情況我們完全可以先在界面上顯示一個加載進度條,或者占位圖之類的東西,然后等有了數(shù)據(jù)之后再更新界面上的內(nèi)容。
還有一種說法是,他們希望SplashScreen不僅僅是用來加載等待的,還可以用來做一些品牌展示和推廣之類的工作。這樣如果SplashScreen過快地消失,可能用戶根本來不及看到SplashScreen上的內(nèi)容。
當然,也有另一種說法是,他們在SplashScreen上顯示的并不是一個靜態(tài)的圖標,而是一個動畫,所以至少要等到動畫結(jié)束之后再隱藏SplashScreen。
不管你是屬于哪一種,Google都給我們提供了延長顯示SplashScreen的能力。
剛才說了,SplashScreen會在App開始在界面上繪制第一幀的時候自動消失,那么如果我們阻止了App在界面上繪制第一幀,是不是SplashScreen就不會消失了?
沒錯,這就是延長顯示SplashScreen的工作原理。具體代碼如下:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val contentView: View = findViewById(android.R.id.content) contentView.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { return false } }) } }
這里我們在回調(diào)函數(shù)onPreDraw()中返回了一個false,也就意味著,我們的PreDraw階段始終沒有準備好。既然PreDraw都還沒準備好,App肯定是不會開始繪制第一幀的,那么SplashScreen自然也就不會消失了。
于是上述代碼將會實現(xiàn)一個永久顯示SplashScreen的效果。
有了這個原理,那么我們就可以根據(jù)自己的需求編寫一些邏輯了。比如剛才提到的從磁盤讀取數(shù)據(jù)的場景,我們可以一開始在onPreDraw()中函數(shù)中返回false,然后開啟子線程去讀取數(shù)據(jù),等到數(shù)據(jù)讀取完成再將返回值改成true即可。示例代碼如下:
class MainActivity : AppCompatActivity() { @Volatile private var isReady = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val contentView: View = findViewById(android.R.id.content) contentView.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { if (isReady) { contentView.viewTreeObserver.removeOnPreDrawListener(this) } return isReady } }) thread { // Read data from disk ... isReady = true } } }
注意,在SplashScreen的顯示過程中,onPreDraw()函數(shù)是以很高的頻率在持續(xù)刷新的。所以它依然會將主線程阻塞住,導(dǎo)致應(yīng)用程序無法響應(yīng)用戶的輸入事件,直到我們在onPreDraw()函數(shù)返回true才會停止刷新。
自定義SplashScreen樣式
接下來終于到了可能許多朋友最為關(guān)心的部分,自定義SplashScreen的樣式。
雖然默認的SplashScreen界面并不難看,對于大多數(shù)的App來說可能也已經(jīng)完全足夠了,但是Google仍然給了我們比較高的控制權(quán)來自定義SplashScreen的樣式。
這里我就將幾個比較重要的自定義樣式屬性來跟大家介紹一下。
剛才有提到過,SplashScreen默認會使用windowBackground屬性指定的顏色作為界面的背景顏色。但如果我想要單獨給SplashScreen界面指定一個背景色呢?可以在主題文件中定義如下屬性:
<item name="android:windowSplashScreenBackground">#CCCCCC</item>
這里我們單獨將SplashScreen的背景指定成了淺灰色,效果如下圖所示:
需要注意,這個屬性以及接下來要介紹的所有屬性都是在Android 12系統(tǒng)上新增的,所以你應(yīng)該在一個values-v31的專屬目錄下使用它們。
既然能夠自定義SplashScreen的背景色,那么我們是不是也可以自定義SplashScreen上的圖標呢?
很難想象為什么要在SplashScreen界面上展示一個和Launcher Icon不同的圖標,但Google確實允許我們這么做:
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_screen_icon</item>
這里我們給SplashScreen界面指定了一個單獨的圖標,注意這個圖標可以是一張靜態(tài)的圖片,也可以是一個動畫資源。由于制作動畫比較復(fù)雜,不在本文的討論范圍內(nèi),所以我們只以靜態(tài)圖片來舉例。
我準備了這樣一張圖,并將它命名為splash_screen_icon.jpg。
然后運行程序,效果如下圖所示:
你會發(fā)現(xiàn),雖然我提供的圖標是正方形的,但最終顯示在SplashScreen上的卻是一個圓形圖片。
由此我們可以得出結(jié)論,SplashScreen和Launcher Icon一樣,也是同樣會受到廠商mask的影響的。它的大致工作原理如下圖所示:
可以看到,這里背景層是一張藍色的網(wǎng)格圖,前景層是一張Android機器人Logo圖,然后蓋上一層圓形的mask,最終就裁剪出了一張圓形的應(yīng)用圖標。
如果對此還不夠了解的話,可以去參考我之前寫的一篇文章 Android 8.0系統(tǒng)中的應(yīng)用圖標適配 。
上述例子中我使用的是一張不透明的圖片來作為圖標,其實我們也可以提供一張有透明度的圖片,然后再借助如下屬性來控制圖標的背景色:
<item name="android:windowSplashScreenIconBackgroundColor">#BB86FC</item>
這樣,只要前景圖標是有透明度的圖片,背景顏色就可以顯示出來了,如下圖所示:
最后,如果你希望在SplashScreen上再進行一些品牌方面的推廣,還可以通過以下屬性來顯示你的品牌信息:
<item name="android:windowSplashScreenBrandingImage">@drawable/brand_logo</item>
這里可以傳入一張品牌圖片,我沒能在官網(wǎng)找到Google對這張圖片尺寸比例的定義,但如果你隨便傳入一張圖片的話,可能會出現(xiàn)拉伸的情況。
為此,我通過自己做實驗,大概總結(jié)出了這里應(yīng)該使用一張2.4:1的圖片,最終的效果如下圖所示:
適配舊版SplashScreen
最后,我們再來了解一下,如何才能去適配舊版的SplashScreen。
準確來說,Android官方是沒有舊版SplashScreen這一說的,因為SplashScreen是在Android 12中才新增加的功能。
但是,有很多的App早在官方提供API之前,就已經(jīng)自己實現(xiàn)了SplashScreen功能。正如前面所說,這個功能在國內(nèi)很常見。
那么接下來問題來了。過去通過自己的方式實現(xiàn)的SplashScreen,和現(xiàn)在官方提供的SplashScreen要如何兼容呢?
這著實是一個問題,主要原因在于,SplashScreen在Android 12上是強制啟用的。所以,如果你的代碼中還保留著過去自己實現(xiàn)的那一套SplashScreen,在Android 12中就會出現(xiàn)雙重SplashScreen的現(xiàn)象。
但如果我們從代碼中移除了過去自己實現(xiàn)的SplashScreen,那么在Android 12之前的系統(tǒng)版本就沒有SplashScreen功能了。
要如何解決這個問題呢?不要著急,Google在AndroidX中提供了一個向下兼容的SplashScreen庫。根據(jù)官方的說法,我們只要使用這個庫就可以輕松解決舊版SplashScreen的適配問題。
用法很簡單,跟著如下步驟走即可。
第一步,修改build.gradle文件,將targetSdkVersion指定到31,并添加如下依賴庫:
android {
compileSdkVersion 31
...
}
dependencies {
...
implementation 'androidx.core:core-splashscreen:1.0.0-alpha01'
}
第二步,修改主題文件,如下所示:
<style name="MySplashTheme" parent="Theme.SplashScreen"> <item name="windowSplashScreenBackground">#CCCCCC</item> <item name="windowSplashScreenAnimatedIcon">@drawable/splash_screen_icon</item> <item name="postSplashScreenTheme">@style/Theme.SplashTest</item> </style>
注意這里的變動至關(guān)重要。我們新定義了一個主題,這個主題的名字叫什么都可以,但它一定要繼承自Theme.SplashScreen。
然后我們可以使用windowSplashScreenBackground和windowSplashScreenAnimatedIcon這兩個屬性來分別指定SplashScreen的背景色和中央圖標。
不過我比較疑惑的是,我們不能像剛才那樣在SplashScreen界面指定圖標的背景色和品牌圖片,因為這里并沒有那兩個屬性。不知道是不是因為現(xiàn)在庫還屬性比較早期的階段,以后或許會加上這些屬性。
另外,我們還必須要指定postSplashScreenTheme這個屬性,將它的值指定成你的App原來的主題。這樣,當SplashScreen結(jié)束時,你的主題就能夠被復(fù)原,從而不會影響到你的App的主題外觀。
第三步,修改AndroidManifest.xml文件,應(yīng)用我們剛剛新定義的主題:
<manifest> <application android:theme="@style/MySplashTheme"> <!-- or --> <activity android:theme="@style/MySplashTheme"> ...
這里視你之前代碼的寫法來決定是替換application標題里的theme,還是activity標題里的theme。
第四步,在你的啟動Activity中加入如下代碼:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) installSplashScreen() setContentView(R.layout.activity_main) ... } }
如果你還在使用Java語言的話,那么需要改成如下寫法:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SplashScreen.installSplashScreen(this); setContentView(R.layout.activity_main); ... } }
注意,installSplashScreen()這句代碼一定要加入到setContentView()的前面。
這樣,當我們剛剛進入App的時候,就會先顯示一個SplashScreen界面,然后當App初始化完成之后,SplashScreen會自動消失,并且主題也會變成原來App的主題樣式。
接下來我們只需要把過去自己實現(xiàn)的SplashScreen移除即可,不然的話仍然還是會產(chǎn)生雙重SplashScreen的現(xiàn)象。
以上步驟是官方提供的適配舊版SplashScreen的解決方案,但是我按照上述步驟進行了一下實現(xiàn),最終的測試效果卻非常差。
主要問題集中在于舊版Android系統(tǒng)上中央圖標不會被mask,而在Android 12上中央圖標卻會被mask,從而導(dǎo)致新舊系統(tǒng)的SplashScreen界面差別很大,也很難看。
不過畢竟我們現(xiàn)在使用的SplashScreen庫還處于alpha階段,后面發(fā)生變動的可能性很大,或許這些問題在正式版出現(xiàn)之后都會被修復(fù)。
另外,即使官方的庫有問題,我們還是完全有辦法去規(guī)避它。比如說在代碼中進行邏輯判斷,如果是Android 12系統(tǒng)就不顯示自己的SplashScreen界面,因為系統(tǒng)有默認的SplashScreen。而在Android 12以下的系統(tǒng),就顯示自己的SplashScreen界面。
方法總比困難多,不是嗎?
那么本篇文章的內(nèi)容就到這里,讓我們一起靜靜等待Android 12的到來吧。
到此這篇關(guān)于Android 超詳細SplashScreen入門教程的文章就介紹到這了,更多相關(guān)Android SplashScreen內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實現(xiàn)界面內(nèi)嵌多種卡片視圖(ViewPager、RadioGroup)
這篇文章主要為大家詳細介紹了Android實現(xiàn)界面內(nèi)嵌多種卡片視圖,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09Android利用POI實現(xiàn)將圖片插入到Excel
這篇文章主要為大家詳細介紹了Android如何利用POI實現(xiàn)將圖片插入到Excel,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11基于android studio的layout的xml文件的創(chuàng)建方式
這篇文章主要介紹了基于android studio的layout的xml文件的創(chuàng)建方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03Android TabHost選項卡標簽圖標始終不出現(xiàn)的解決方法
這篇文章主要介紹了Android TabHost選項卡標簽圖標始終不出現(xiàn)的解決方法,涉及Android界面布局相關(guān)屬性與狀態(tài)設(shè)置操作技巧,需要的朋友可以參考下2019-03-03Flutter自定義下拉刷新時的loading樣式的方法詳解
Flutter中的下拉刷新,我們通常RefreshIndicator,可以通過color或strokeWidth設(shè)置下拉刷新的顏色粗細等樣式,但如果要自定義自己的widget,RefreshIndicator并沒有暴露出對應(yīng)的屬性,那如何修改呢,文中給大家介紹的非常詳細,需要的朋友可以參考下2024-01-01