Android 手游闪屏极简方案

作者: 爱柚子的陈同学 | 来源:发表于2019-05-26 20:29 被阅读6次

    逃离博客园,搬运一篇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 实现。

    相关文章

      网友评论

        本文标题:Android 手游闪屏极简方案

        本文链接:https://www.haomeiwen.com/subject/itgftctx.html