Common Steps and When They Can Be Done


Loading and Deleting Game Data

Technically, game data other than OpenGL ES resources can be loaded at any time from onCreate beyond. Actually, application copies of OpenGL ES resources could be loaded in onCreate as well, but they cannot be loaded to OpenGL ES until it is initialized. So it may or may not make sense to load the rendering resources so early.

Note: Depending on your manifest settings, the application may be brought up and shut down several times during initial launch (see the later section Surprising Application Lifecycle Events and How to Handle Them). If you choose not to use the settings to avoid this, you may wish to delay the loading of any data until you know that the game is fully launched. Doing so can avoid doubling or tripling the load time if the system launches and re-launches the app several times.

Initializing and Deleting EGL, Contexts, Surfaces and Rendering Resources

Technically, the following steps can be done at any time between onCreate and onDestroy, as they do not require any specific resources from the app:

  1. eglInitialize.
  2. Querying, sorting and selection of the EGLConfig.
  3. Creation of the EGLContext for OpenGL ES.

However, the context cannot be bound until there is an EGLSurface that can be bound at the same time. The EGLSurface in turn cannot be created until there is an Android surface available to attach the EGLSurface. Such a surface is provided by the surfaceCreated callback. As a result, in order to fully set up OpenGL ES, we must be between surfaceCreated and surfaceDestroyed.

Indeed, since an EGLContext must be bound in order to load OpenGL ES resources (which in turn requires an EGLSurface), this means that we cannot load our 3D resources for the application until we receive surfaceCreated. We should note further that it is best to wait for surfaceChanged to indicate that the surface has positive width and height as well; some cases can generate temporary 0x0 surfaces.

When we receive a surfaceDestroyed callback, we must immediately unbind the EGLSurface and EGLContext (eglMakeCurrent with display, NULL, NULL) and must stop rendering. However, we do not need to destroy the EGLContext at this time. It can be kept resident even when it is not bound. When a new surface is available we can rebind the existing context and we will not have lost any of our rendering resources. This can avoid an expensive asset reloading process in some lifecycle cases like suspend/resume.

We should still consider deleting the EGLContext (and thus freeing resources) in onStop, even if the context is not bound. If the context is bound, it may be best to release the GLES resources manually using GLES calls. However, if there is no surface and thus no way to bind the context at the point at which we want to delete the GLES resources, the best option is to destroy the EGLContext manually using eglDestroyContext.

One common way to handle initialization by making a function that returns "true" if we are completely ready to load resources, allocating/initializing/failing as needed/possible. This can handle partial initializations; for example, it can handle the case where we had a surface, lost it due to surfaceDestroyed and now have a new Android surface (and can thus create and bind a new EGLSurface). The pseudo-code might be as follows:

bool EGLReadytoRender () 
{
// If we have a bound context and surface, then EGL is ready
if ((Is EGL Context Bound?) == false)
{
// If we have not bound the context and surface,
// do we even _have_ a surface?
if ((Is EGL Surface Created?) == false)
{
// No EGL surface; is EGL is set up at all?
if ((Are EGL Context/Config Initialized?) == false)
{
// Init EGL, the Config and Context here…
if (failed)
return false;
// Mark that we have a new context – this will
// be important later, as a new context bound
// means we can/must load content into it
}
if ((Is there a valid Android surface?) == false)
return false;
// Create the EGLSurface surface here…
if (failed)
return false;
} // We have a surface and context, so bind them // eglMakeCurrent with the surface and context here… if (failed) return false; } // One way or another (free or requiring some amount of setup) // We have a fully initialized set of EGL objects… Off we go return true; }

Rendering

In general, an app should only render when it is fully visible and interact-able. In Android lifecycle terms, this tends to mean:

There are cases where we might render outside of being the focused window and in rare cases when onPause’d, but those tend to be:

Catching Resize

In practice, while there are several callbacks that can indicate that a screen resize has occurred (e.g. surfaceChanged and onConfigurationChanged), we have found in practice that these should be used only to trigger a forced-redraw of the application. Resizing of the window and surface can be delayed in some cases, especially with native_app_glue. As a safety net, we recommend that at the start of each rendered frame, the application check the size of the window or surface and update their aspect ratio, size, and viewport information. In this way, any problems from a rogue size change that is not signaled with a callback or is out of sequence can be minimized.

The Case of Suspend/Resume

A very important case to consider is the previously-discussed suspend/resume.

Note the sequence: when the device is suspended, the app receives onPause and focus loss, the signal to stop rendering and playing music. When the device is resumed, onResume is given. But the app is NOT visible. It must not render and must not play sounds yet. The lock screen covers it. Users often power up to the lock screen in quiet areas to look at their clock and messages. If the app began blasting music, it could be a major usability issue. It is not until the user unlocks the device and the lock screen goes away that the focus regained callback is given. Only when both focus and onResume have both been given should rendering and sound begin again. However, the application should not resume to active gameplay on unlock – see the next section for more discussion of this.

Note: The application should not resume to active gameplay on unlock. See the next section for more discussion of this.

Auto-pause

In general, gameplay should be paused to some auto-pause screen whenever the user would have lost the ability to control the game for any period of time. Commonly, this is the case when you are regaining window focus from any form of loss. This screen should be kept active even once focus is regained. Do not restart active gameplay just because focus is regained.

If you handle window focus regain by simply enabling the gameplay again, the user may be caught off guard and left to catch up. When receiving an onWindowFocusChanged(TRUE) after a onWindowFocusChanged(FALSE), it is best to show an autopause screen to allow the user to resume gameplay on their own time.

Handling Input

In general, most applications will want to handle the BACK button explicitly and “eat” the event in order to avoid having Android kill their activity with no further user interaction. Generally, for games, this involves handling BACK as a “back step” through their UI menus, and then when the start screen is reached, having the BACK button trigger a “do you really want to quit” dialog. In these cases, the application must signal to Android that it has completely handled the BACK button, and Android should do no further processing.

Depending on the application’s architecture, this will be one of the following:

The application can always request quit on its own schedule via Java finish or NativeActivity ANativeActivity_finish.

Exiting and Native Apps

Native executables on most platforms signal exit by returning from their main function. However, since native code in Android is merely one piece of the application, this will not work for native Android applications.

Technically, a native Android application should not return from its main function until the framework (NativeActivity or native_app_glue) has requested it. If an application wishes to proactively exit, they must invoke the Java finish method. This can be done:

Exiting the native entry-point (e.g., android_main) does not necessarily cause the top-level Java application to exit. Applications should keep their main loops running until requested to exit the thread by APP_CMD_DESTROY.

Saving Game Progress

Game progress will be lost if it is not saved prior to the application being killed. Since an application is killable in all Android variants after returning from onStop, and is killable on pre-Honeycomb after returning from onPause, these are the obvious callbacks to use to save game state to persistent storage.

Resuming versus Restarting

In addition, it may make sense to save a flag in onPause or onStop that indicates that the current save game is an auto-save, and not a user-initiated save. Then, if the user exits out of gameplay or entirely out of the game with explicit decisions, clear this "autosaved" flag.

When your application is launched, you can check if this is a "fresh" launch initiated by a user or simply your app being relaunched because it was killed silently by Android. In the latter case, you may want to resume directly to auto-pause in the saved game, since that is presumably where the user last sat before the game was interrupted. This can make the game appear seamless with respect to getting auto-killed.


NVIDIA® GameWorks™ Documentation Rev. 1.0.200601 ©2014-2020. NVIDIA Corporation. All Rights Reserved.