13
\$\begingroup\$

My game saves its state after fixed interval on a file in game/app internal storage. When my game is killed or crashed by user or OS respectively, We write current game state on that file. File writing after fix interval is working fine but when game is crashed/killed by OS or user, file writing operation fails. Operation failure results incomplete game state or empty game state i.e. no data written on file at all. I have tried multiple solutions using android service, In case the service is NON STICKY service is killed with the app. On the other hand if service is STICKY then it is restarted but intent that was attached initially with service is null or new one.

The question is that how can i save my data (could be ~2-3MB) completely on file in internal storage when my game/app is killed by user/OS ?

\$\endgroup\$
  • \$\begingroup\$ You gotta handle your SIGTERMs and your SIGKILLs (well maybe not SIGKILLs, Android is probably using SIGTERM). (Don't have time to research / write up a full answer but that's the basic idea.) \$\endgroup\$ – John Hamilton May 12 '18 at 7:05
  • 2
    \$\begingroup\$ @JohnHamilton SIGKILL can't be handled, by definition. \$\endgroup\$ – Darkhogg May 12 '18 at 11:45
  • \$\begingroup\$ @Darkhogg Well, not in Unity, that's for sure. (I did change linux kernel at one point for a project for this specific purpose and it's certainly possible to let programs handle SIGKILL) \$\endgroup\$ – John Hamilton May 12 '18 at 17:41
20
\$\begingroup\$

I would suggest writing your save state in a double-buffered way, as was common for console titles in the past (where you had to cope with the memory card being removed mid-write and writes were slow).

Save:

  • If no files exist, write state to file A
  • If A exists, write to B
  • If A and B both exist, find which one is older, delete it, and then write to that one

Load:

  • If both A and B exist, try to load the newer of the two. If that fails (because the file is incomplete or corrupt), delete it and load the other
  • If only one exists, load it
  • If both are corrupt, inform the user that their save state was irretrievable (like you presumably have to already)

This flow will work to ensure there is always at least one valid save still present in your storage, even if the app is killed mid-write. The player will not get any of the changes in the game state since the previous save if that sort of failure occurs, but they will not have to start again.

In addition, you should be saving immediately after key events (such as in-app purchases) in addition to your interval saving, to minimise the window of vulnerability where a crash/kill would cause that key event to be lost. This is also protection against game exploits (e.g. force-killing the app after losing a life, because you know you'll restore to a game-state from before you lost a life, and get to try again). Of course if do this you must protect your interval saving with logic that says "if a save is already in progress, do not try to start saving again."

\$\endgroup\$
  • \$\begingroup\$ Thanks MrCranky this is partial solution to my problem how can i save data of last session i.e just before killing app/game? For example he made an in app purchase and killed app. \$\endgroup\$ – Faisal Imran May 11 '18 at 15:56
  • 12
    \$\begingroup\$ You don't even need two files. Write to B, if and only if writing was successful, delete A and rename B to A. java.nio.file.Files.move(Path source, Path target, CopyOption... options) can do renaming and overwriting as an atomic operation. \$\endgroup\$ – Polygnome May 11 '18 at 16:20
  • 6
    \$\begingroup\$ @Polygnome: Note that in the case of power failures this will not always provide the guarantees you expect, unless you call sync() on file B before you move it over file A (even then there are not complete guarantees, but sync() is fairly good). \$\endgroup\$ – Dietrich Epp May 11 '18 at 21:13
  • 1
    \$\begingroup\$ @FaisalImran Save data right after the important operation, for example, after IAP. If the operation was interrupted by phone turning off, I guess it would never take money from the person anyway. But just in case, you might want to save his account data in a purchase on some platform as an attempt to buy something. Then later you can compare your earnings to that data and see if this person really bought something. And then open this feature for him via online service you are using to save all the data. If you are using local saves for IAP, then it's even worse than this problem. \$\endgroup\$ – Candid Moon _Max_ May 12 '18 at 10:32
  • 1
    \$\begingroup\$ Lastly, although this may not solve all your problems, I'd say that your problem is not fully solvable. You can't support both "the user may kill my app at any time" and "no data must be lost ever" because there will always be a window after an event has taken place but before it has been persisted to storage where the user can kill the app and lose data. \$\endgroup\$ – MrCranky May 12 '18 at 17:13
3
\$\begingroup\$

Android only kills apps when they're in the background. Does your game data really need to be updated when the app is in the background, or can you stop updating the data until the app returns to the foreground? You might be able to defer updates even in a multiplayer game, if you can grab a history of events or even just the current state when the app returns to the foreground. This is something you should probably look at doing anyway for the sake of CPU, network, and battery efficiency.

If the user kills your app, running services should receive a call to onTaskRemoved. I don't know what would happen if you tried to do much processing in this method. I expect that if it doesn't return within a certain time, Android will kill -9 your app.

\$\endgroup\$
  • \$\begingroup\$ app doesn't need be update in background, but app must save its data i.e json file before going to background or destroy. \$\endgroup\$ – Faisal Imran May 17 '18 at 9:59
  • \$\begingroup\$ you are right onTaskRemoved method will call but the time required to save the file is more or we can say computation time is large. When user kills the app this method will be stoped. \$\endgroup\$ – Faisal Imran May 17 '18 at 10:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.