Common Lifecycle Cases that Exhibit Configuration Issues


The following sections describe common issues we see with lifecycle events causing unexpected configuration changes. In both cases, the answer is actually pretty simple: using the configChanges tag in the manifest, with or without forcing the orientation with the screenOrientation tag. However, we present the full sequences with and without the potential fix, so that developers can better understand the expected results.

Launch a Forced-Landscape Application with Device in Portrait Orientation

Many games and media applications force landscape orientation using the AndroidManifest.xml key:

android:screenOrientation="landscape"  

From here, the assumption is that they will always be launched/resumed in the desired orientation with no anomalies or multiple launches. This is the case on some OS images and devices, which can lead to confusion on platforms that do not behave as simply.

For example, on some devices, an application that forces landscape with the above key and does NOT declare that it wants to receive configuration changes explicitly through the onConfigurationChanged callback will see the following lifecycle callbacks when launched from portrait orientation:

I/System.out( 1355): +-onCreate 
I/System.out( 1355): +-onStart
I/System.out( 1355): +-onResume
I/System.out( 1355): +-onPause
I/System.out( 1355): +-onStop
I/System.out( 1355): +-onDestroy
I/System.out( 1355): +-onCreate
I/System.out( 1355): +-onStart
I/System.out( 1355): +-onResume
I/System.out( 1355): +-surfaceCreated
I/System.out( 1355): +-surfaceChanged: 1366, 695
I/System.out( 1355): +-onWindowFocusChanged (TRUE)

Note that the system is brought all the way up to onResume before being shut down again. One answer is to simply add the aforementioned tag (android:configChanges) to handle the configuration change internally via a callback. In this case, the result is much simpler:

I/System.out( 1532): +-onCreate
I/System.out( 1532): +-onStart
I/System.out( 1532): +-onResume
I/System.out( 1532): +-onConfigurationChanged
I/System.out( 1532): +-surfaceCreated
I/System.out( 1532): +-surfaceChanged: 1366, 695
I/System.out( 1532): +-onWindowFocusChanged (TRUE)

For many 3D applications, there will not even be a need to override onConfigurationChanged. Note that the configuration changed callback comes before the creation of the window, and thus there is no problem for a 3D app, which bases its rendering buffer on the surfaceCreated and surfaceChanged callbacks. Merely the addition of the manifest key may be sufficient.

This can be more complex with applications that have advanced Android UI layouts using XML. But for many 3D applications, this is not the case, and thus there is no complication.

Sleep and Resume with a Fixed-Orientation Application

On some devices, we have seen particularly problematic sequences when putting a device to sleep and then waking it up again. The "expected" sequence (from the Android docs) is:

I/System.out(  437): +-onCreate 
I/System.out( 437): +-onStart
I/System.out( 437): +-onResume
I/System.out( 437): +-onConfigurationChanged
I/System.out( 437): +-surfaceCreated
I/System.out( 437): +-surfaceChanged: 1366, 695
I/System.out( 437): +-onWindowFocusChanged (TRUE)
→ Sleep
I/System.out( 437): +-onPause
→ Resume
I/System.out( 437): +-onResume
I/System.out( 437): +-onWindowFocusChanged (FALSE)
→ Unlock the Screen
I/System.out( 437): +-onWindowFocusChanged (TRUE)


This is not the sequence seen on some devices, however… With a forced layout (screenOrientation) and without the configuration changed callback tag configChanges, some devices will provide extremely "noisy" sequences involving multiple configuration changes:

→ 	Sleep
I System.out: +-onPause
I System.out: +-onStop
I System.out: +-onDestroy
I System.out: +-surfaceDestroyed
I System.out: +-onCreate 0
I System.out: +-onStart
I System.out: +-onResume
I System.out: +-onPause
I System.out: +-surfaceCreated
I System.out: +-surfaceChanged: 540, 884
→ Resume
I System.out: +-onResume
→ Unlock the Screen
I System.out: +-onPause
I System.out: +-onStop
I System.out: +-onDestroy
I System.out: +-surfaceDestroyed
I System.out: +-onCreate 0
I System.out: +-onStart
I System.out: +-onResume
I System.out: +-onWindowFocusChanged (TRUE)
I System.out: +-surfaceCreated
I System.out: +-surfaceChanged: 960, 464
I System.out: +-onPause
I System.out: +-onStop
I System.out: +-onDestroy
I System.out: +-surfaceDestroyed
I System.out: +-onCreate 0
I System.out: +-onStart
I System.out: +-onResume
I System.out: +-onWindowFocusChanged (TRUE)
I System.out: +-surfaceCreated
I System.out: +-surfaceChanged: 540, 884
I System.out: +-onPause
I System.out: +-onStop
I System.out: +-onDestroy
I System.out: +-surfaceDestroyed
I System.out: +-onCreate 0
I System.out: +-onStart
I System.out: +-onResume
I System.out: +-onWindowFocusChanged (TRUE)
I System.out: +-surfaceCreated
I System.out: +-surfaceChanged: 960, 464


Quite a wild ride for the application, especially if they start loading 3D resources each time.

The solution is the same as the previous case: Adding the configuration callback tag android:configChanges causes the sequence on the problematic devices to simplify significantly. With these changes, the device still sends multiple screen-size changes, but they do not generate the problematic shutdown/startup "bounces":

→ 	Sleep
I System.out: +-onPause
→ Resume
I System.out: +-surfaceChanged: 540, 884
I System.out: +-onWindowFocusChanged (FALSE)
I System.out: +-onResume
→ Unlock the Screen
I System.out: +-onWindowFocusChanged (TRUE)
I System.out: +-surfaceChanged: 960, 464
I System.out: +-surfaceChanged: 540, 884
I System.out: +-surfaceChanged: 960, 464

These resolution changes are much easier to handle quickly than a full shutdown, all by simply setting the flag to receive configuration changes for orientation.

Honeycomb and the Volume Button

In previous versions of Android, pressing the volume up/down button showed a volume "overlay" and changed the device volume. There was no lifecycle messaging to the application, as the slider could not be dragged – it was merely a visual representation of the hardware volume button presses.

However, as of Honeycomb, that behavior has changed. The volume buttons launch a temporary dialog with one or more sliders that you can actually touch and interact with. As a result, if you do not "eat" the volume button events and change the volume yourself, you will receive a focus lost/gained pair of events. Focus will be lost on the first press of a volume key when the dialog is launched, and focus will be regained when the volume "slider" dialog times out an fades away.

This can be confusing for some apps, as the behavior differs per OS. Workaround options include:

Suspend/Resume with No Lock Screen

The sequence shown in the previous sections for suspend/resume all assume some form of lock screen that displays when the device is resumed. This tends to cause a focus lost/gained pair of messages at suspend/resume. However, apps that have coded explicit expectations that onResume and onWindowFocusChanged(true) will always come together when exiting suspended mode have run into issues in a rather common case: the case where a user has for convenience’s sake disabled the lock screen. On such a device, resuming the device goes straight to the app that was focused prior to suspend without any intermediary screen.

In terms of lifecycle messages, this tends to cause Android not to send either of the focus messages as a part of the suspend/resume. The app never loses focus because no lock screen ever obscures it. Thus, in this case, it is up to the app to have kept a flag that tracks the current focus state. If the focus has never been lost via onWindowFocusChanged(false), then the app should go back to its focused (and presumably auto-paused) state after it receives the onResume and a new rendering surface (if the previous one was destroyed in the suspend). The sequence is then something along the lines of the following (app starting from resumed and focused):

→ 	Sleep
I System.out: +-onPause
→ Resume
I System.out: +-onResume
I System.out: +-surfaceDestroyed
I System.out: +-surfaceCreated
I System.out: +-surfaceChanged: 540, 884

Thus, in this case, it is up to the app to have kept a record of the focus state to know that the onResume indicated that the app was not only resumed but focused. The destruction and recreation of the surface on resume is odd, but does seem common in this case.

 

 

 


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