古人已用三冬足,年少今开万卷余。这篇文章主要讲述在Android应用中正确加载资源(使用openGL)相关的知识,希望能为你提供帮助。
背景/概述
我的应用程序是一个openGL游戏(android和用java编写)。
我不确定我应该如何加载我的资源(即加载位图,创建对象等)。
我目前正在使用onSurfaceCreated方法完成所有这些工作(例如见下文) - 这对我的Nexus 10平板电脑来说还可以,一切都在不到一秒的时间内完成。但是,当我在较旧的手机(Galaxy Ace)上运行时,在发生任何事情之前,我会在大约3到4秒内出现空白屏幕。我觉得这是不可接受的,所以想试着找到解决办法。
我知道我可以放一个简单的启动(甚至只是显示'加载')虽然这是业务,但我的问题是:
1)如何在所有内容加载之前显示任何内容?我的意思是onSurfaceCreated中加载了所有内容,而onDurfaceCreated之后onDrawFrame才会运行。那么如何在资源仍在加载时显示任何内容?
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();
}
答案【在Android应用中正确加载资源(使用openGL)】1)如果要在加载资源时显示启动画面,则应在后台线程上尽可能多地加载。这是一个很好的做法,释放GL线程来做你想做的任何事情,因为它并不忙于加载资源。但是,与GLGL的任何直接交互都需要在GL线程上完成,例如上传纹理或编译着色器。
通常,您将使用需要与UI线程交互的
AsyncTask
for后台资源加载。但是,GLSurfaceView
不使用GL的主UI线程,因此AsyncTask
在这里不起作用。不过,我们可以通过其他方式很容易地获得类似的行为。只需在您自己的后台线程上加载资源代码,并使用GLSurface.queueEvent()
将结果发布到GL线程上。可以以多种方式完成后台资源加载;
在这个例子中,让我们使用一个具有单个后台线程的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 vs 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秒内更加努力。将其与一切运行平稳的分析结果进行比较。
另一答案1)您可以将大量初始化移动到
AsyncTask
中。在活动完成onCreate()
之前,第一个屏幕不会显示。2)是和否,取决于方式,内容和地点。大多数用户都可以理解带有一些加载信息的简单屏幕。无论如何,大多数顶级游戏都带有某种品牌屏幕。
3)我不知道你是如何实现你的代码的,但我猜你的库或你的代码在这一点上仍在做一些初始化的事情。
Romain Guy分享一些技巧来加速初始加载here。
推荐阅读
- 调用ondraw后重置画布,然后再在android中调用它
- 是否有适用于Android的图像编辑SDK [关闭]
- 如何在Android中的Canvas上移动路径()
- 查找JApplet的宽度和高度
- 使用AppActivate更改活动窗口
- 以编程方式测试是否在Android 10+中禁用了旧版外部存储访问
- Powershell(类似Haskell的$(apply)运算符())
- 任务执行失败'(app:transformDexArchiveWithExternalLibsDexMergerForDebug')
- 构建Android时会忽略make -j(jobs)选项(默认为所有核心)