Android getSharedPreferences startActivityForResult периодическая ошибка
У меня есть игра, чья main
деятельность называет три других деятельности, используя startActivityForResult
-- первый (SignInActivity
) возвращает имя пользователя или разрешает создание нового; второй (LevelChooser
) использует getSharedPreferences
найти файл настроек с именем этого пользователя или создать новый, отображающий прогресс пользователя до сих пор (уровни разблокированы, заработанные звезды), и позволяет пользователю выбрать воспроизведение любого разблокированного уровня; третий (GameActivity
) обновляет файл настроек пользователя, если уровень успешно завершен, прежде чем вернуться (через main
) чтобы LevelChooser
, В LevelChooser
Я переопределил onBackPressed
чтобы вернуть вас в SignInActivity; в GameActivity
это onFinish
это переопределено, так что вы вернетесь к LevelChooser
независимо от того, как это происходит.
Сейчас, в девяти случаях из десяти, все работает точно так, как задумано, но иногда это не так: иногда вместо того, чтобы видеть реальные звезды и уровни пользователя, LevelChooser
показывает набор неправильных и теоретически невозможных значений (например, один уровень показан как заблокированный, но дополнен тремя звездами). Это часто (но не всегда) случается, если вы выбираете уровень и затем выходите из него при первом открытии игры: тогда он позволит вам играть на любом уровне, показанном как разблокированный, но GameActivity
не удается сохранить ваш результат, если вы пройдете уровень, и те же неправильные уровни отображаются, когда вы вернетесь к LevelChooser
; в качестве альтернативы, если вы вернетесь из LevelChooser
и повторно выберите то же имя пользователя, оно снова будет вести себя, как ожидалось. Мне также удалось воспроизвести ошибку, многократно запуская уровни и отступая от них - если вы попробуете достаточно раз, это в конечном итоге пойдет не так. Для моего собственного имени пользователя (и, как мне кажется, для всех пользователей) неверная информация всегда одна и та же, то есть проблема нерегулярна, но, когда она возникает, не случайна.
Я попытался отладить, но по какой-то причине (а) проблема возникает только на моем телефоне, а не на эмуляторе, и (б) при отладке (в отличие от запуска) на моем телефоне он либо работает правильно, либо, если идет не так, просто завершается (AFAIR даже без диалога "X остановлен") вместо отображения экрана неправильного уровня. Единственное, что мне удалось увидеть в отладке, это то, что onCreate
из LevelChooser
деятельность иногда выполняется более одного раза.
Поскольку проблема носит периодический характер и не может быть воспроизведена напрямую, мне интересно, предположил ли я, что невольно предположил, что какой-то асинхронный процесс завершился / будет завершен своевременно, линейным образом и что он обычно (но не всегда) обязывает; или иначе, я думаю, что не смог понять что-то важное и значимое в жизненном цикле Activity. Иначе я в тупике и догадываюсь.
main Activity
:
public class MainActivity extends AppCompatActivity {
public ImageView splash;
private int GET_USER_NAME_CODE = 0;
private int GET_LEVEL_CODE = 1;
private int PLAY_GAME_CODE = 2;
private String user;
private int level;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// remove title
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_splash_screen);
splash = (ImageView) findViewById(R.id.splashView);
splash.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent sign_intent = new Intent(MainActivity.this, SignInActivity.class);
startActivityForResult(sign_intent, GET_USER_NAME_CODE);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
// check if the request code is same as what is passed
if(requestCode==GET_USER_NAME_CODE)
{
user=data.getStringExtra("USER");
Intent intent = new Intent(MainActivity.this, LevelChooser.class);
intent.putExtra("user", user);
startActivityForResult(intent, GET_LEVEL_CODE);
}
else {
if(requestCode==GET_LEVEL_CODE) {
level=data.getIntExtra("LEVEL", 0);
if(level==-1) {
Intent sign_intent = new Intent(MainActivity.this, SignInActivity.class);
startActivityForResult(sign_intent, GET_USER_NAME_CODE);
}
else {
Intent intent = new Intent(MainActivity.this, GameActivity.class);
intent.putExtra("user", user);
intent.putExtra("level", level);
startActivityForResult(intent, PLAY_GAME_CODE);
}
}
else {
if(requestCode==PLAY_GAME_CODE) {
user=data.getStringExtra("user");
Intent intent = new Intent(MainActivity.this, LevelChooser.class);
intent.putExtra("user", user);
startActivityForResult(intent, GET_LEVEL_CODE);
}
}
}
}
LevelChooser
:
public class LevelChooser extends AppCompatActivity {
private String user;
private ImageView[] level;
private Boolean[] locked;
private int[] stars;
private SharedPreferences userprefs;
private SharedPreferences.Editor prefseditor;
private Boolean createNewPrefsFile = false;
private int tempResIdVisible;
private int tempResIdInvisible;
private ImageView tempView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.levelchooser);
}
@Override
protected void onStart() {
super.onStart();
level = new ImageView[21];
locked = new Boolean[21];
stars = new int[21];
user = getIntent().getStringExtra("user");
userprefs = getSharedPreferences(user, MODE_PRIVATE);
prefseditor = userprefs.edit();
//level numbers for views etc start from 1 to match images etc
level[0] = null;
locked[0] = null;
stars[0] = 0;
locked[1] = false;
level[1] = (ImageView) findViewById(R.id.level1);
level[1].setOnClickListener(new LevelClickListener(level[1], 1));
if (!userprefs.contains("stars1")) {
createNewPrefsFile = true;
}
stars[1] = userprefs.getInt("stars1", 0);
if (stars[1] != 0) {
for (int j = 1; j < 4; j++) {
tempResIdInvisible = getResources().getIdentifier("stars" + j + "_1", "id", getPackageName());
tempView = (ImageView) findViewById(tempResIdInvisible);
tempView.setVisibility(View.INVISIBLE);
}
tempResIdVisible = getResources().getIdentifier("stars" + stars[1] + "_1", "id", getPackageName());
tempView = (ImageView) findViewById(tempResIdVisible);
tempView.setVisibility(View.VISIBLE);
}
for (int i = 2; i < 21; i++) {
locked[i] = userprefs.getBoolean("locked" + i, true);
if (locked[i]) {
tempResIdVisible = getResources().getIdentifier("padlock" + i, "id", getPackageName());
tempResIdInvisible = getResources().getIdentifier("level" + i, "id", getPackageName());
} else {
tempResIdVisible = getResources().getIdentifier("level" + i, "id", getPackageName());
tempResIdInvisible = getResources().getIdentifier("padlock" + i, "id", getPackageName());
level[i] = (ImageView) findViewById(tempResIdVisible);
level[i].setOnClickListener(new LevelClickListener(level[i], i));
}
tempView = (ImageView) findViewById(tempResIdVisible);
tempView.setVisibility(View.VISIBLE);
tempView = (ImageView) findViewById(tempResIdInvisible);
tempView.setVisibility(View.INVISIBLE);
stars[i] = userprefs.getInt("stars" + i, 0);
if (stars[i] != 0) {
for (int j = 1; j < 4; j++) {
tempResIdInvisible = getResources().getIdentifier("stars" + j + "_" + i, "id", getPackageName());
tempView = (ImageView) findViewById(tempResIdInvisible);
tempView.setVisibility(View.INVISIBLE);
}
tempResIdVisible = getResources().getIdentifier("stars" + stars[i] + "_" + i, "id", getPackageName());
tempView = (ImageView) findViewById(tempResIdVisible);
tempView.setVisibility(View.VISIBLE);
}
}
if (createNewPrefsFile) {
for (int i = 1; i < 21; i++) {
prefseditor.putBoolean("locked" + i, locked[i]);
prefseditor.putInt("stars" + i, stars[i]);
prefseditor.commit();
}
}
}
GameActivity
:
public class GameActivity extends AppCompatActivity implements TextToSpeech.OnInitListener {
//TTS Object
private TextToSpeech myTTS;
//TTS status check code
private int MY_DATA_CHECK_CODE = 0;
private int level;
private String user;
private PhonemeGroup levelGroup;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
level = getIntent().getIntExtra("level", 0);
user = getIntent().getStringExtra("user");
levelGroup = initializeLevels(level);
Intent checkTTSIntent = new Intent();
checkTTSIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkTTSIntent, MY_DATA_CHECK_CODE);
}
@Override
public void finish() {
Intent intent = new Intent();
intent.putExtra("user", user);
setResult(2, intent);
super.finish();
}
@Override
public void onStop() {
if (myTTS != null) {
myTTS.stop();
}
super.onStop();
}
@Override
public void onDestroy() {
if (myTTS != null) {
myTTS.shutdown();
}
Button ok_button = (Button) findViewById(R.id.button);
ok_button.setOnClickListener(null);
ImageView tickImageView = (ImageView) findViewById(R.id.tickImageView);
tickImageView.setOnClickListener(null);
ImageView starsView = (ImageView) findViewById(R.id.starsImageView);
starsView.setOnClickListener(null);
super.onDestroy();
unbindDrawables(findViewById(R.id.GameParentView));
System.gc();
}
Снимок экрана правильного отображения:
Снимок экрана после выбора уровня 1 выше и затем отступления:
1 ответ
Оказывается, проблема не в getSharedPreferences()
как таковой, а скорее, что переменная user
вернулся getIntent().getStringExtra()
было иногда - в сложившихся обстоятельствах, которые мне было трудно определить или воспроизвести надежно - null
как в этом вопросе. Эта строка была затем использована в качестве параметра для getSharedPreferences()
, в результате чего набор сохраненных результатов для пользователя null
, Эти результаты (показанные на втором изображении в вопросе) затем отображались каждый раз, когда проблема повторялась.
Решение - хотя оно до сих пор не объясняет, почему getStringExtra()
должен вернуться null
- должен был обойтись без дополнений, поместив следующий код в onCreate
обоих видов деятельности, для которых требуется действительный пользователь:
if(getIntent().getExtras()==null || getIntent().getStringExtra("user")==null) {
Intent intent = new Intent();
setResult(0, intent);
finish();
}
Если это выполняется до загрузки макета для нового действия, он возвращается к основному действию и пытается снова (так сказать), чтобы пользователь не увидел никаких проблем.