Niepowodzenie operacji na plikach w przypadku awarii / zabicia gry przez system operacyjny / użytkownika w systemie Android

13

Moja gra zapisuje swój stan po ustalonym czasie na pliku w pamięci wewnętrznej gry / aplikacji. Gdy moja gra zostanie zabita lub rozbita odpowiednio przez użytkownika lub system operacyjny, zapisujemy bieżący stan gry na tym pliku. Zapisywanie plików po okresie między naprawami działa poprawnie, ale gdy gra ulegnie awarii / zabije system operacyjny lub użytkownik, operacja zapisu pliku kończy się niepowodzeniem. Błąd operacji powoduje niepełny stan gry lub pusty stan gry, tzn. Brak zapisanych danych w pliku. Wypróbowałem wiele rozwiązań za pomocą usługi Android, w przypadku gdy usługa NON STICKY została zabita przez aplikację. Z drugiej strony, jeśli usługa jest NAPRAWNA, jest ona restartowana, ale zamiar, który został dołączony na początku z usługą, jest zerowy lub nowy.

Pytanie brzmi: w jaki sposób mogę całkowicie zapisać moje dane (może to być ~ 2-3 MB) w pliku w pamięci wewnętrznej, gdy moja gra / aplikacja zostanie zabita przez użytkownika / system operacyjny?

Faisal Imran
źródło
Musisz obsługiwać SIGTERM i SIGKILL (no może nie SIGKILL, Android prawdopodobnie używa SIGTERM). (Nie mam czasu na badanie / napisanie pełnej odpowiedzi, ale to jest podstawowa idea.)
John Hamilton,
2
@JohnHamilton SIGKILL z definicji nie może być obsługiwany.
Darkhogg,
@Darkhogg Cóż, na pewno nie w Unity. (W pewnym momencie zmieniłem jądro Linuksa na projekt do tego konkretnego celu i na pewno można pozwolić programom obsługiwać SIGKILL)
John Hamilton

Odpowiedzi:

20

Sugerowałbym zapisanie stanu zapisu w sposób podwójnie buforowany, tak jak to było w przypadku tytułów konsolowych w przeszłości (gdzie trzeba było poradzić sobie z wyjmowaniem karty pamięci w trakcie zapisu, a zapisy przebiegały powoli).

Zapisać:

  • Jeśli nie ma żadnych plików, zapisz stan do pliku A
  • Jeśli A istnieje, napisz do B
  • Jeśli oba A i B istnieją, znajdź, który jest starszy, usuń go, a następnie napisz do tego

Załaduj:

  • Jeśli istnieją zarówno A, jak i B, spróbuj załadować nowszą z nich. Jeśli to się nie powiedzie (ponieważ plik jest niekompletny lub uszkodzony), usuń go i załaduj drugi
  • Jeśli istnieje tylko jeden, załaduj go
  • Jeśli oba są uszkodzone, poinformuj użytkownika, że ​​ich stan zapisywania był nieodwracalny (tak jak prawdopodobnie musisz już)

Ten przepływ będzie działał, aby zapewnić, że w pamięci zawsze jest co najmniej jeden prawidłowy zapis, nawet jeśli aplikacja zostanie zabita w trakcie zapisu. Gracz nie otrzyma żadnych zmian w stanie gry od poprzedniego zapisu, jeśli wystąpi tego rodzaju awaria, ale nie będzie musiał zaczynać od nowa.

Ponadto oprócz zapisywania interwałów należy zapisywać bezpośrednio po kluczowych zdarzeniach (takich jak zakupy w aplikacji), aby zminimalizować okno podatności, w przypadku której awaria / zabicie spowodowałoby utratę tego kluczowego zdarzenia. Jest to również ochrona przed exploitami gier (np. Wymuszanie zabijania aplikacji po utracie życia, ponieważ wiesz, że przywrócisz stan gry sprzed utraty życia i będziesz mógł spróbować ponownie). Oczywiście, jeśli to zrobisz, musisz zabezpieczyć swój interwał oszczędzania za pomocą logiki, która mówi „jeśli zapis jest już w toku, nie próbuj zaczynać zapisywania ponownie”.

MrCranky
źródło
Dzięki MrCranky to częściowe rozwiązanie mojego problemu. Jak mogę zapisać dane z ostatniej sesji, tj. Tuż przed zabiciem aplikacji / gry? Na przykład dokonał zakupu aplikacji i zabił aplikację.
Faisal Imran
12
Nie potrzebujesz nawet dwóch plików. Napisz do B, jeśli i tylko jeśli zapis się powiódł, usuń A i zmień nazwę B na A. java.nio.file.Files.move(Path source, Path target, CopyOption... options)Możesz zmienić nazwę i nadpisać jako operację atomową.
Polygnome
6
@Polygnome: Należy pamiętać, że w przypadku awarii zasilania nie zawsze zapewni to oczekiwane gwarancje, chyba że wywołasz sync()plik B przed przeniesieniem go do pliku A (nawet wtedy nie ma pełnych gwarancji, ale sync()jest całkiem dobry).
Dietrich Epp
1
@FaisalImran Zapisz dane zaraz po ważnej operacji, na przykład po IAP. Gdyby operacja została przerwana przez wyłączenie telefonu, wydaje mi się, że i tak nigdy nie odebrałby pieniędzy. Ale na wszelki wypadek możesz zapisać dane jego konta przy zakupie na jakiejś platformie jako próbę zakupu. Następnie możesz porównać swoje zarobki z tymi danymi i sprawdzić, czy ta osoba naprawdę coś kupiła. A potem otwórz dla niego tę funkcję za pośrednictwem usługi online, której używasz do zapisywania wszystkich danych. Jeśli używasz lokalnych zapisów dla IAP, jest to nawet gorsze niż ten problem.
Candid Moon _Max_
1
Wreszcie, chociaż może to nie rozwiązać wszystkich problemów, powiedziałbym, że twój problem nie jest w pełni do rozwiązania. Nie możesz obsługiwać zarówno „użytkownik może zabić moją aplikację w dowolnym momencie”, jak i „nigdy nie trzeba nigdy utracić danych”, ponieważ po zdarzeniu zawsze będzie okno, ale zanim zostanie ono utrwalone w pamięci, w której użytkownik może zabić aplikację i utracić dane.
MrCranky
3

Android zabija aplikacje tylko wtedy, gdy są w tle. Czy dane gry naprawdę wymagają aktualizacji, gdy aplikacja działa w tle, czy możesz przestać aktualizować dane, dopóki aplikacja nie wróci na pierwszy plan? Możesz odroczyć aktualizacje nawet w grze wieloosobowej, jeśli możesz pobrać historię wydarzeń lub nawet aktualny stan, gdy aplikacja powróci na pierwszy plan. Jest to coś, co powinieneś prawdopodobnie zrobić, biorąc pod uwagę wydajność procesora, sieci i baterii.

Jeśli użytkownik zabije twoją aplikację, uruchomione usługi powinny otrzymać połączenie z onTaskRemoved. Nie wiem, co by się stało, gdybyś próbował wykonać dużo przetwarzania w tej metodzie. Spodziewam się, że jeśli nie wróci w określonym czasie, Android uruchomi kill -9Twoją aplikację.

Kevin Krumwiede
źródło
aplikacja nie musi być aktualizowana w tle, ale aplikacja musi zapisać swoje dane, tj. plik json, zanim przejdzie do tła lub zniszczy.
Faisal Imran
masz rację Metoda onTaskRemoved zadzwoni, ale czas wymagany do zapisania pliku jest dłuższy lub możemy powiedzieć, że czas obliczeń jest duży. Gdy użytkownik zabije aplikację, ta metoda zostanie zatrzymana.
Faisal Imran