Falha na (s) operação (ões) de arquivo na falha / interrupção do jogo por OS / Usuário no Android

13

Meu jogo salva seu estado após um intervalo fixo em um arquivo no armazenamento interno do jogo / aplicativo. Quando meu jogo é interrompido ou travado por usuário ou sistema operacional, respectivamente, escrevemos o estado atual do jogo nesse arquivo. A gravação do arquivo após o intervalo de correção está funcionando bem, mas quando o jogo é travado / interrompido pelo sistema operacional ou pelo usuário, a operação de gravação do arquivo falha. A falha na operação resulta em estado incompleto do jogo ou vazio, ou seja, nenhum dado gravado no arquivo. Eu tentei várias soluções usando o serviço Android, no caso de o serviço NÃO PEGAR O serviço é eliminado com o aplicativo. Por outro lado, se o serviço for STICKY, ele será reiniciado, mas a intenção que foi anexada inicialmente ao serviço é nula ou nova.

A questão é: como posso salvar meus dados (aproximadamente de 2 a 3 MB) em um arquivo no armazenamento interno quando meu jogo / aplicativo é morto pelo usuário / sistema operacional?

Faisal Imran
fonte
Você precisa lidar com seus SIGTERMs e SIGKILLs (talvez não com SIGKILLs, o Android provavelmente está usando SIGTERM). (Não tenho tempo para pesquisa / escrever-se uma resposta completa, mas essa é a idéia básica.)
John Hamilton
2
@JohnHamilton SIGKILL não pode ser tratado, por definição.
Darkhogg
@ Darkhogg Bem, não no Unity, isso é certo. (Eu fiz a mudança do kernel do Linux em um ponto para um projeto para esse fim específico e é certamente possível permitir que programas alça SIGKILL)
John Hamilton

Respostas:

20

Sugiro que você escreva seu estado de salvamento de maneira com buffer duplo, como era comum em títulos de console no passado (onde você tinha que lidar com o cartão de memória sendo removido no meio da gravação e as gravações eram lentas).

Salve :

  • Se não houver arquivos, escreva o estado no arquivo A
  • Se A existir, escreva para B
  • Se A e B existirem, encontre qual é o mais antigo, exclua-o e depois escreva nele

Carga:

  • Se existirem A e B, tente carregar o mais novo dos dois. Se isso falhar (porque o arquivo está incompleto ou corrompido), exclua-o e carregue o outro
  • Se houver apenas um, carregue-o
  • Se ambos estiverem corrompidos, informe ao usuário que seu estado de salvamento era irrecuperável (como você provavelmente já deve ter)

Esse fluxo funcionará para garantir que sempre haja pelo menos uma gravação válida ainda presente em seu armazenamento, mesmo que o aplicativo seja eliminado no meio da gravação. O jogador não receberá nenhuma das alterações no estado do jogo desde o salvamento anterior, se esse tipo de falha ocorrer, mas não precisará iniciar novamente.

Além disso, você deve salvar imediatamente após os principais eventos (como compras no aplicativo), além do intervalo de salvamento, para minimizar a janela de vulnerabilidade em que uma falha / interrupção causaria a perda do evento principal. Isso também é proteção contra explorações de jogos (por exemplo, matar o aplicativo com força depois de perder uma vida, porque você sabe que voltará ao estado de jogo antes de perder uma vida e tentará novamente). Obviamente, se fizer isso, você deve proteger o intervalo de salvamento com uma lógica que diz "se um salvamento já estiver em andamento, não tente começar a salvar novamente".

MrCranky
fonte
Obrigado MrCranky, esta é uma solução parcial para o meu problema, como posso salvar os dados da última sessão, ou seja, antes de matar o aplicativo / jogo? Por exemplo, ele fez uma compra no aplicativo e o matou.
Faisal Imran
12
Você nem precisa de dois arquivos. Escreva para B, se e somente se a gravação tiver sido bem-sucedida, exclua A e renomeie B para A. java.nio.file.Files.move(Path source, Path target, CopyOption... options)pode renomear e substituir como uma operação atômica.
Polygnome
6
@ Polygnome: Observe que, no caso de falta de energia, isso nem sempre oferece as garantias esperadas, a menos que você chame o sync()arquivo B antes de movê-lo sobre o arquivo A (mesmo assim, não há garantias completas, mas sync()é bastante bom).
Dietrich Epp
1
@FaisalImran Salve os dados logo após a operação importante, por exemplo, após o IAP. Se a operação fosse interrompida pelo desligamento do telefone, acho que nunca seria necessário tirar dinheiro da pessoa. Mas, por precaução, convém salvar os dados da conta dele em uma compra em alguma plataforma, como uma tentativa de comprar alguma coisa. Depois, você pode comparar seus ganhos com esses dados e ver se essa pessoa realmente comprou algo. E abra esse recurso para ele através do serviço online que você está usando para salvar todos os dados. Se você estiver usando salvamentos locais para IAP, será ainda pior que esse problema.
Candid Moon _Max_
1
Por fim, embora isso possa não resolver todos os seus problemas, eu diria que seu problema não é totalmente solucionável. Você não pode suportar "o usuário pode interromper meu aplicativo a qualquer momento" e "nenhum dado deve ser perdido", pois sempre haverá uma janela depois que um evento ocorre, mas antes de persistir no armazenamento onde o usuário pode matar o aplicativo e perder dados.
18418 MrCranky
3

O Android mata aplicativos apenas quando estão em segundo plano. Os dados do seu jogo realmente precisam ser atualizados quando o aplicativo está em segundo plano ou você pode parar de atualizar os dados até que o aplicativo retorne ao primeiro plano? Você pode adiar as atualizações mesmo em um jogo multiplayer, se conseguir pegar um histórico de eventos ou até apenas o estado atual quando o aplicativo retornar ao primeiro plano. Isso é algo que você provavelmente deveria pensar em fazer de qualquer maneira em prol da eficiência da CPU, da rede e da bateria.

Se o usuário matar seu aplicativo, os serviços em execução deverão receber uma chamada para onTaskRemoved. Não sei o que aconteceria se você tentasse fazer muito processamento nesse método. Espero que, se ele não retornar dentro de um determinado período, o Android fará o kill -9seu aplicativo.

Kevin Krumwiede
fonte
O aplicativo não precisa ser atualizado em segundo plano, mas o aplicativo deve salvar seus dados, ou seja, o arquivo json antes de ir para o segundo plano ou destruir.
Faisal Imran
você está certo no métodoTaskRemoved chamará, mas o tempo necessário para salvar o arquivo é maior ou podemos dizer que o tempo de computação é grande. Quando o usuário mata o aplicativo, esse método é interrompido.
Faisal Imran