Android 手遊閃屏極簡方案

愛柚子的陳同學發表於2019-05-29

逃離部落格園,搬運一篇2015年做手遊時期的舊文。

簡約至上 少寫程式碼

為什麼需要閃屏

  1. 手機應用程式不應該有閃屏, Google Android 自家的 App 據說已經全面禁用閃屏。

Splash Screens Are Evil, Don't Use Them!

  1. 中國大量手機應用程式,或者說相關從業人員依舊堅持必須存在一個閃屏圖片的審美。

為什麼需要原生實現

  1. UnrealEngine3 需要
    UE3 初始化和場景切換時,渲染執行緒暫停,因此需要使用原生方案顯示圖片或視訊來過渡等待。
  2. cocos2d-x 需要
    cocos2d-x 論壇相關討論帖
  3. Unity3D
    大概類似,未考證。

為什麼不應該使用 Splash Activity

“你搜到的都是錯的”
網上搜尋 Android 閃屏實現方案,99%的結果都是介紹如何使用一個簡單的 Activity 實現,再過渡切換到真正的 GameActivity 。這種方案僅限教學,實際應用有很多弊端。

  1. 這種方案要求在 AndroidManifest.xml 中配置的 LaunchActivity 必須是 SplashActivity
    當需要實現帶引數 Intent 啟動時,SplashActivity 需要正確地傳遞引數(Intent)給 GameActivity。繁瑣。
  2. 遊戲使用 NDK 開發,OpenGL, UI View, thread 需要跟 GameActivity's SurfaceView 繫結。
    SplashActivity 顯示期間,GameActivity 無法被載入,因此也無法並行載入遊戲引擎相關例項。導致閃屏過後,GameActivity 仍需一個載入介面用於過渡等待 GameEngine 的啟動耗時。
  3. 遊戲需要接入各種 SDK。很多 SDK 要求在 GameActivity 的生命週期插入諸多 hook 事件程式碼。
    例如 onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy() 等等,這些是常用 hook 位置。
    SplashActivity 方案使相關邏輯實現更復雜。

一種適合遊戲的簡單閃屏實現方案

  1. 使用一個全屏 Dialog
    Android Dialog 擁有獨立的 Window ,與 GameView 無耦合。
  2. 遮蔽 User Input Event
    Dialog 預設接收所有User Input Event ,不需要傳遞給 GameView,因此與遊戲邏輯無耦合。
  3. 實現動畫
    可以很方便的使用各種原生 Android Animation ,實現可用的過渡動畫呈現。
  4. 動畫結束後自動消失
    Dialog 可以自我管理生命週期,再次與遊戲無耦合。
  5. 並行載入遊戲例項
    GameSurfaceView and GameEngine 可以在 Dialog 顯示期間,後臺並行載入,無耦合,且真正達到非同步和節省時間的目標。

程式碼示例

  1. 建立全屏 Dialog
public class NSSplashDialog extends Dialog {
    private PercentFrameLayout mLayout = null;
    private ImageView mImageView = null;
    public NSSplashDialog(Context context) {
        super(context, android.R.style.Theme_NoTitleBar_Fullscreen);
        setContentView(R.layout.splash);
        mLayout = (PercentFrameLayout)findViewById(R.id.layout_splash);
        mImageView = (ImageView)this.findViewById(R.id.iv_splash);
    }
}
複製程式碼

2 . 遮蔽 User Input Event

setCanceledOnTouchOutside(false);
setCancelable(false);
複製程式碼

3 . 實現動畫

private AlphaAnimation mAnimation = null;
private int mBitmapIndex = 0;

mAnimation = new AlphaAnimation(0.0f, 1.0f); //fade in, fade out
mAnimation.setDuration(2000);//2 seconds
mAnimation.setRepeatCount(3); //show 4 images
mAnimation.setAnimationListener(new Animation.AnimationListener(){

    @Override
    public void onAnimationStart(Animation animation) {
        mBitmapIndex = 0;
        mLayout.setBackgroundColor(Color.WHITE);
        mImageView.setImageDrawable(BitmapUtil.loadDrawable(getContext(), R.drawable.splash0));
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        mBitmapIndex = 0;
        kick(false);
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
        mBitmapIndex++;
        switch(mBitmapIndex) {
        case 1:
            mLayout.setBackgroundColor(Color.WHITE);
            mImageView.setImageDrawable(BitmapUtil.loadDrawable(getContext(), R.drawable.splash1));
            break;
        case 2:
            mLayout.setBackgroundColor(Color.BLACK);
            mImageView.setImageDrawable(BitmapUtil.loadDrawable(getContext(), R.drawable.splash2));
            break;
        case 3:
            mLayout.setBackgroundColor(Color.BLACK);
            mImageView.setImageDrawable(BitmapUtil.loadDrawable(getContext(), R.drawable.splash3));
            break;
        default:
            break;
        }
    }
});

mImageView.setAnimation(mAnimation);
複製程式碼

4 . 動畫結束後自動消失

onAnimationEnd()中呼叫kick(false),即關閉自己。

實測發現部分系統有bug:onAnimationEnd() 和 cancel() 可能會死迴圈,因此新增保護邏輯判斷 hasEnded()

public void kick(boolean show) {
    if(show) {
        show();
        mAnimation.start();
    } else {
        if(!mAnimation.hasEnded()) {
            mAnimation.cancel();
        }
        dismiss();
    }
}
複製程式碼

5 . 閃屏與遊戲並行載入

GameActivity 生命週期中, 在 onCreate() 建立 SplashDialog 例項,在 onDestroy() 清除 SplashDialog 例項。

    //Create
    mSplashDialog = new NSSplashDialog(this);
	mSplashDialog.kick(true);

    //Destroy
    if(mSplashDialog != null && mSplashDialog.isShowing()) {
        mSplashDialog.kick(false);
    }
複製程式碼

Loading View

閃屏說完了,最後提一下 Loading View

上面說到 UE3 在場景切換時,需要使用平臺原生介面做過渡展示。根據業務需求不同,可能有時候不便複用 SplashDialog,那可以使用一個獨立 layout View 實現。

相關文章