XRAPH/Writing/The Android lifecycle problem in embedded engines

The Android lifecycle problem in embedded engines

Roughly a fifth of six years of issues came down to one question: what happens to a game engine when the activity underneath it is destroyed and recreated.

Published
Dec 2020
Length
2 min read
Systems
2

#The shape of the bug

A user rotates the device. Or takes a call. Or the system reclaims memory while the app is in the background. On Android, any of those can destroy the activity and create a new one, and the engine that was rendering into a surface owned by the old activity now holds a reference to something that no longer exists.

What the user sees is a black rectangle where the scene was. What the developer sees, if anything, is a native crash with no useful frame, some minutes later, in a different part of the process.

#Why it is harder than it sounds

Three parties have opinions about the lifecycle and none of them are coordinated.

Android has an activity lifecycle with well-defined callbacks, and it may destroy and recreate at times of its choosing. Flutter has a widget lifecycle that is related but not identical, since a widget can be disposed while the activity lives and the engine can outlive both. Unity has its own player lifecycle with pause and resume, and it holds resources that Android may reclaim independently.

A correct integration has to define what happens for every combination, and the combinations that cause trouble are the rare ones. That is why the bugs are timing dependent and why reproductions are so valuable.

#What eventually worked

Three rules, arrived at by attrition.

One owner for the player. A single object holds the engine and is responsible for pausing, resuming and tearing it down. Widgets attach to it and detach from it. They never own it, because widgets are disposed for reasons that have nothing to do with whether the engine should stop.

Never assume visibility. Engine code that runs on resume rebuilds anything tied to a surface rather than trusting that the surface it had is still valid.

Make the state explicit. The widget exposes whether the engine is created, paused or destroyed. Applications that need to react can, and the ones that do not still benefit, because a bug report that includes the state is worth ten that say it went black.

#The part I could not fix

Some of this is not fixable in a library. Whether the activity is recreated on rotation depends on the host application's manifest, which the library does not control. A team that sets the configuration changes attribute one way gets one set of behaviour and a team that sets it another way gets a different set, and both are legitimate choices for their app.

Documenting that clearly was worth more than any code change. The README now states which configurations are supported and which are not , and the volume of lifecycle issues fell noticeably afterwards.

#What it changed later

When I eventually rewrote the integration from scratch, lifecycle handling went into the core rather than being something each integrator rediscovers. That single decision accounts for most of the difference in issue volume between the two projects.

References

  1. [1]Rex Raphael and contributors, flutter-unity-view-widget: Embeddable Unity Game Engine View for Flutter, GitHub, 2019https://github.com/juicycleff/flutter-unity-view-widget