APP啓動黑屏白屏原因與解決方式

我們在桌面啓動自己辛苦創建的APP時,總是會看到黑屏或是白屏現象,這讓人的體驗感覺不是很好,看看大廠的APP爲什麼不會有這個現象?有問題就要解決,即便不是BUG,用戶體驗一樣很重要。

1. APP啓動黑/白屏的原因

首先,我們需要知道一個APP啓動時,屏幕上都會有什麼。在我們的APP裏,顯示在屏幕上的自然是各個View了,而我們的View又都是在Activity的onCreate()方法中調用了setContentView()方法,傳入了我們的layout文件,也就是我們理論上應該看到的Activity內容。但是Android系統在啓動一個新的Activity時,首先進行的並不是繪製Activity的內容,我們來看看一個Activity的UI結構。
Activity UI結構

我們可以看到,一個Activity中在ContentView的外圍還有PhoneWindow、DecorView、TitleView,當Activity進行繪製時會先繪製這三個View,這時ContentView還沒加載進來,所以什麼東西都看不到,系統會將屏幕填充主題默認的背景色,亮系主題填充白色,暗系主題填充黑色,就出現了Activity啓動之前的黑/白屏現象。

2. 解決黑/白屏的方法

剛纔說了,系統會爲屏幕填充主題默認的背景色,那麼要解決這個問題就應該從屏幕的背景下手了。一想到背景,第一反應就是去layout裏設置ContentView的background,但是系統並不會先加載ContentView,那有什麼在系統繪製之前就能調整屏幕背景呢?

注意,系統會填充主題默認的背景色,所以主題會在繪製之前加載,我們可以修改主題的背景達到目的。一般一個APP第一個啓動的Activity都是Splash,作爲一個Splash並不需要標題欄,而且普遍是全屏的。那麼我們可以將主題進行修改一下,大概有兩種方式:

  1. 將主題背景變成透明的,這樣在ContentView加載出來之前,我們會透過啓動的Activity看到桌面,就不會有黑/白屏的現象。再把標題欄去掉,把Activity設置成全屏的,效果挺不錯,缺點是如果啓動的是一個有複雜耗時操作的Activity,那麼會有一種延遲的感覺。
<style name="AppTheme" parent="android:Theme.Light.NoActionBar">  
    <item name="android:windowIsTranslucent">true</item>  
    <item name="android:windowNoTitle">true</item>  
    <item name="android:windowActionBar">false</item>  
    <item name="android:windowBackground">@android:color/transparent</item>  
    <item name="android:WindowFullscreen">true</item>
</style>
  1. 將主題背景設置成一張圖片,把標題欄去掉,把Activity設置成全屏的,這這樣在ContentView加載出來之前,我們就能看到一張默認背景圖,但是圖片的屏幕適配問題就需要考慮了,主題裏的背景圖片會自動拉伸,可能會導致失真或者比例失調的問題。
<style name="AppTheme" parent="android:Theme.Light.NoActionBar">  
    <item name="android:windowNoTitle">true</item>  
    <item name="android:windowActionBar">false</item>  
    <item name="android:windowBackground">@drawable/bg_splash</item>  
    <item name="android:WindowFullscreen">true</item>
</style>

3. 背景顯示優化

這裏再將上述解決方法進行優化,減少用戶使用時不好的體驗。(PS:當然你可以不做此優化,如果你想忽悠老闆,把鍋甩給Android系統、手機的硬件配置、UI的圖給的不匹配屏幕等等)

3.1 方法一優化

方法一中的問題在於延遲感嚴重,那麼我們需要做的就是儘量加快Splash的啓動速度,在Splash中不加入任何邏輯操作,並且Application中任何的數據及開源框架的初始化方法都不應調用,當Splash啓動完全後,在Splash的OnResume()方法中可以啓動子線程進行各初始化操作,寧可讓用戶在背景圖中等待,不要讓用戶看着手機桌面認爲手機死機了。

3.2 方法二優化

方法二中的問題在於圖片拉伸可能導致失真或者比例失調,使得界面不夠美觀。簡單的方式就是建立各個drawable文件夾,覆蓋所有的屏幕尺寸類型,每個文件夾下塞一張讓UI做的合理的背景圖。這種方法超級令人無語,UI的工作量較大,而且你也不可能覆蓋所有的屏幕尺寸,比如這樣:
超長的手機屏幕

那麼怎樣可以擁有更好的用戶體驗呢?這時候我們需要的是drawable。

3.2.1 drawable的類型

在Android中,我們可以使用xml自定義一個drawable,用的最多的場景就是背景圖了,Android系統的一些默認圖標也都是用xml實現的,當然那涉及到了一些矢量圖的知識。

首先我們先了解一下drawable的類型,常見的幾種有:BitmapDrawableShapeDrawableStateListDrawableLevelListDrawableLayerDrawableTransitionDrawableScaleDrawableAnimationDrawableInsetDrawableNinePatchDrawableClipDrawableVectorDrawable

這裏我採用了LayerDrawable來解決圖片拉伸的問題,其他的drawable以後再寫一篇文章專門分析各個drawable。

3.2.2 LayerDrawable解決圖片拉伸

LayerDrawable爲什麼能解決圖片拉伸問題呢?這要從LayerDrawble的性質說起了:

  1. XML標籤爲layer-list

  2. 層次化的Drawable合集

  3. 可以包含多個item,每個item表示一個Drawable

  4. item中可以通過android:drawable直接引用資源

  5. item中可以通過android:top等指定相對於父節點的位置

多個Drawable的層次化疊加,並且可以指定每個Drawable的位置,是不是和layout很像?一些簡單的佈局顯示可以用LayerDrawble來完成,不過只能塞Drawable進去,文字什麼的就不行了。

那麼我們來看一下一個可以很好適配屏幕的背景圖改如何完成。首先在drawable文件夾下建立一個layer-list類型的drawable文件bg_splash.xml,隨後寫入如下代碼:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/bg"/>
    <item android:top="175dp">
        <bitmap android:gravity="top" android:src="@drawable/logo"/>
    </item>
</layer-list>

我們在layer-list中放入了兩個item:第一個是一整個頁面的背景,可以是圖片,但是筆者建議用純色的ShapeDrawable,一定程度上減少內存開銷並且無需考慮圖片失真之類的問題;第二個是一個Bitmap,<bitmap>這個標籤是按照圖片大小插入一張圖片,這樣避免了圖片在屏幕上的拉伸,通過android:top來指定這個item頂部的偏移距離,同樣還可以指定android:bottomandroid:leftandroid:right來定位item的位置,隨後對<bitmap>android:gravity設置爲top,讓logo可以顯示在頂部。這樣一個能隨着屏幕進行適配並且不會失真的背景就做好了,按照方法二設置爲android:windowBackground即可。

3.2.3 style主題優化

按照方法二的設定,整個App將使用我們製作的bg_splash作爲背景,這時候如果不給每個Activity設置背景或者在使用虛擬鍵盤時,進入App之後屏幕上也會看到bg_splash出現在沒有控件的位置,造成用戶的疑惑或者反感。

我們知道Activity也是可以設置主題的,那麼我們可以給Application設置一個默認的主題AppTheme,然後給SplashActivity設置我們的全屏帶背景的主題SplashTheme,這樣在我們的SplashActivity中就可以迅速顯示啓動背景圖,進入App中,在其他Activity中也不會出現啓動背景圖,最終的styles和AndroidManifest文件如下:

styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
  <item name="colorPrimary">@color/colorPrimary</item>
  <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
  <item name="colorAccent">@color/colorAccent</item>
  <item name="android:windowBackground">@color/colorDefaultBg</item>
</style>

<style name="SplashTheme" parent="AppTheme">  
  <item name="android:windowFullscreen">true</item>
  <item name="android:windowBackground">@drawable/bg_splash</item>
</style>
AndroidManifest.xml
<application android:name=".MyApplication"
  android:theme="@style/AppTheme"
  android:supportsRtl="true"
  android:roundIcon="@mipmap/ic_launcher_round"
  android:label="@string/app_name"
  android:icon="@mipmap/ic_launcher"
  android:allowBackup="true">

  <activity android:name=".SplashActivity"
    android:theme="@style/SplashTheme"
    android:screenOrientation="portrait"
    android:configChanges="orientation|screenSize|keyboardHidden">

    <intent-filter>

      <action android:name="android.intent.action.MAIN"/>
      <category android:name="android.intent.category.LAUNCHER"/>

    </intent-filter>

  </activity>

</application>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章