android - 在 Android 应用程序中正确加载资源(使用 openGL)

标签 android graphics resources opengl-es-2.0 splash-screen

背景/概述

我的应用程序是一个 openGL 游戏(Android 和用 Java 编写的)。

我不确定应该如何加载我的资源(即加载位图、创建对象等)。

我目前正在我的 onSurfaceCreated 方法中完成所有这些工作(参见下面的示例)- 这对我的 Nexus 10 平板电脑来说工作正常,一切都在不到一秒钟内完成。但是,当我在较旧的手机 (Galaxy Ace) 上运行它时,我会在出现任何问题之前出现大约 3 到 4 秒的空白屏幕。我认为这是 Not Acceptable ,因此想尝试找到解决方法。

我知道我可以放一个简单的启动画面(甚至只是显示“正在加载”),但这是正常的,但我的问题是:

1) 如何在所有内容加载完成之前显示任何内容?我的意思是所有内容都在 onSurfaceCreated 中加载,而 onDrawFrame 直到 onSurfaceCreated 之后才运行。那么如何在资源仍在加载时显示任何内容?

2) 如果我显示一个简单的启动画面。在更新/更快的设备上会不会看起来有点垃圾?我的意思是启动画面只会显示一瞬间,因为所有内容在新手机/平板电脑上的加载速度都非常快。我是否应该强制它在屏幕上停留的时间超过它在更快的设备上所需的时间?

3) 最后,一个相关(且非常重要)的问题 - 在旧手机上,在游戏最终显示之后。在大约 10 到 15 秒的时间里,一切都断断续续地生涩和卡顿。手机显然在后台做某事。 (不是手机不能处理图形,因为它可以。10-15 秒后,一切都像黄油一样流畅)。任何关于这里可能发生的事情或如何追踪问题的线索都将不胜感激!! :-)

旁注:我在 SO 和更广泛的互联网上阅读过各种类似的问题,但它们似乎都涉及使用 XML 布局(而且似乎不太容易实现/成功),我应该指出,我没有在我的应用程序中使用任何 XML,如果可能的话,我想在不使用 XML 的情况下在代码中解决这个问题。

代码示例

public void onSurfaceChanged(GL10 gl, int width, int height) {

//The following are performed once at game load
//All resources are being loaded / created here
//Load graphics files
res.loadResources(view);
//Create game objects (sprites, collision detection etc)
res.createObjects(view, width, height);
//Recycle bitmaps as they are no longer required
res.Recycle();
//Set initial values variables (required for subsequent methods)
res.setInitialValues();
//Set level layout
res.setLevel();
}

最佳答案

1) 如果您想在加载资源时显示启动画面,您应该在后台线程上进行尽可能多的加载。这是一个很好的做法,释放 GL 线程来做任何你想做的事情,因为它不忙于加载资源。不过,与 OpenGL 的任何直接交互都需要在 GL 线程上完成,例如上传纹理或编译着色器。

通常您会使用 AsyncTask 来加载需要与 UI 线程交互的后台资源。但是,GLSurfaceView 不使用 GL 的主 UI 线程,因此 AsyncTask 在这里不起作用。不过,我们可以通过其他方式很容易地获得类似的行为。使用 GLSurface.queueEvent() 只需在您自己的后台线程上加载您的资源代码并将结果发布到 GL 线程上。后台资源加载可以通过多种方式完成;让我们在这个例子中使用一个带有单个后台线程的执行器。一切都在 onSurfaceChanged() 中加载,确保在用户暂停/停止和启动/恢复应用程序时再次重新创建它:

private Executor mExecutor = Executors.newSingleThreadExecutor();

@Override
public void onSurfaceChanged(final int width, final int height) {
    // Let's define a few states for the application; NONE, SPLASH,
    // GAME.
    mRenderState = NONE;

    // Currently, the screen is black. Let's get something on screen as
    // quickly as possible. Create a thread and start loading resources.
    mExecutor.execute(new Runnable() {
        @Override
        public void run() {
            final Resources res = mContext.getResources();

            // Load splash screen resource on background thread.
            final Bitmap splashBitmap = BitmapFactory.decodeResource(res, R.drawable.splash);

            // Splash screen is loaded, post a Runnable that will run on the GL thread.
            mGLSurfaceView.queueEvent(new Runnable() {
                @Override
                public void run() {
                    // Upload splash texture to GL, on GL thread.
                    GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, splashBitmap, 0);
                    // Recycle bitmap. We'll create a lot of bitmaps during loading.
                    // Each bitmap is only needed until we have uploaded it to GL. After
                    // that it's just wasting memory. Especially on older phones.
                    splashBitmap.recycle();
                    // Notify application that the splash screen is loaded,
                    // and it's okay to render it.
                    mRenderState = SPLASH;

                    // Screen is no longer black. User sees a splash screen,
                    // perhaps with a progress bar or a nice animation.

                    // Start loading the other textures.
                    // ...
                    // Texture loading finished.
                    mRenderState = GAME;
                }
            });
        }
    });
}

@Override
public void onDrawFrame()
{
if (mRenderState == SPLASH) {
        // Render splash screen and a progress bar.
    } else if (mRenderState == GAME) {
        // Render game.
    }
}

2) 您可以测量加载闪屏所花费的时间,并在快设备和慢设备上进行比较。如果加载速度与快速设备一样快,则不要将 mRenderState 设置为 SPLASH,用户将一直看到黑屏。希望不到一秒钟黑屏就会被游戏取代。如果需要,您可以在加载资源时添加更多计时,如果时间过长,请启用 SPLASH 渲染状态。

此外,启动画面的淡入和淡出可能比将其弹出和退出 View 更让用户烦恼。

3) 有几件事可能会导致口吃,但在放弃之前我至少会检查这些:

a) 内存管理。 Android 限制每个进程的内存堆大小,旧设备的每个进程的堆内存比新设备少很多,例如 16-32 MB 和 128 MB。这意味着当您加载许多繁重的资源时,垃圾收集器将在旧设备上做更多的工作。在“logcat”中查找大量此类消息:

D/dalvikvm: GC_CONCURRENT freed 4027K, 30% free 29173K/41635K, paused 3ms+8ms

这些可能只是您之前加载的位图被刷新出内存,或者您正在创建许多其他对象来触发旧设备上的 GC。希望在加载时回收位图可以缓解这种情况。

b) 只需检查其他进程是否正在占用 CPU,使用:

adb shell top -m 10

这将显示前 10 个进程的 CPU 使用率。

c) 从“DDMS”运行方法分析。在游戏卡顿时按下“设备”选项卡上带有三个箭头和一个红色圆圈的小按钮。几秒钟后再次按下它以获得分析结果。学习如何阅读图表需要一些时间,但它基本上会告诉您应用程序的哪些部分当前正在使用 CPU。也许在前 10 秒有些东西在更努力地工作。将其与一切运行顺利的分析结果进行比较。

关于android - 在 Android 应用程序中正确加载资源(使用 openGL),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17015690/

相关文章:

java - 没有 gl_LightSource[0] 的照明

python 3 : Converting image to grayscale

http - 当资源可用但由于权限而无法访问时更正 HTTP 状态代码

android - "Wrong version of key store"错误。如何创建 version=1 keystore 证书?

android - 使用 "key-value"格式的 multipart 将图像上传到服务器

android - Android Studio无法识别Galaxy Tab 4

java - 动画背景java游戏

java - 安卓 java.lang.ClassNotFoundException

c++ - 用 C++ 编写的简短的良好程序

javascript - 减少 Ajax 请求