1

背景/概述

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

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

我目前正在我的 onSurfaceCreated 方法中完成所有这些工作(例如,请参见下文) - 这适用于我的 Nexus 10 平板电脑,一切都在不到一秒的时间内完成。但是,当我在较旧的手机(Galaxy Ace)上运行它时,我会在任何事情发生之前出现大约 3 到 4 秒的空白屏幕。我认为这是不可接受的,因此想尝试找到解决方法。

我知道我可以在它做生意的时候放一个简单的飞溅(甚至只是显示“加载”),但是我的问题是:

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();
}
4

2 回答 2

5

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

通常,您将使用AsyncTask需要与 UI 线程交互的后台资源加载。但是,GLSurfaceView不使用 GL 的主 UI 线程,因此AsyncTask不会在这里工作。不过,我们可以很容易地通过其他方式获得类似的行为。只需在您自己的后台线程上加载您的资源代码并将结果发布到 GL 线程上,使用GLSurface.queueEvent(). 后台资源加载可以通过多种方式完成;在这个例子中,让我们使用一个带有单个后台线程的 Executor。一切都已加载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 渲染状态。

此外,淡入和淡出启动屏幕可能会惹恼用户,而不仅仅是将其弹出和弹出视图。

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 秒有些东西在努力工作。将其与一切运行顺利的分析结果进行比较。

于 2013-06-16T17:39:30.610 回答
0

1)您可以将很多初始化移出到AsyncTask. 在 Activity 完成之前不会显示第一个屏幕onCreate()

2) 是与否,取决于方式、内容和地点。大多数用户都可以理解带有一些加载信息的简单屏幕。无论如何,大多数顶级游戏都带有某种品牌屏幕。

3)我不知道你是如何实现你的代码的,但我猜你的库或你的代码此时仍在做一些初始化工作。

Romain Guy在这里分享了一些加快初始加载的技巧。

于 2013-06-10T03:49:32.990 回答