Android getSharedPreferences startActivityForResult 间歇性错误
Android getSharedPreferences startActivityForResult intermittent error
我有一个游戏 main
activity 使用 startActivityForResult
调用其他三个活动 -- 第一个 (SignInActivity
) return 是用户名,或允许创建一个新的;第二个 (LevelChooser
) 使用 getSharedPreferences
查找具有该用户名的首选项文件,或创建一个新文件,显示用户目前的进度(解锁级别,获得星星),并允许用户选择玩任何解锁级别;第三个 (GameActivity
) 如果在返回(通过 main
)到 LevelChooser
之前成功完成关卡,则更新用户的首选项文件。在 LevelChooser
中,我已将 onBackPressed
覆盖为 return 您的 SignInActivity;在 GameActivity
中,onFinish
被覆盖,因此无论它如何发生,您都可以返回到 LevelChooser
。
现在,十分之九这一切都完全按预期工作,但有时却没有:有时,LevelChooser
没有看到用户的实际星级和等级,而是显示了一组不正确的和理论上不可能的值(例如,一个级别显示为锁定,但完成时获得三颗星)。如果您选择了一个关卡,然后在您第一次打开游戏时退出该关卡,这种情况经常(但并非总是)发生:然后它会允许您玩任何显示为已解锁的关卡,但 GameActivity
无法保存您的如果您完成关卡,返回 LevelChooser
时会显示相同的错误关卡;或者,如果您退出 LevelChooser
并重新选择相同的用户名,它会返回到预期的行为。我还设法通过反复启动关卡并退出关卡来重现错误——如果你尝试了足够多的次数,它最终会出错。对于我自己的用户名(并且,我认为,对于所有用户)错误信息总是相同的,即问题是间歇性的,但当它发生时,不是随机的。
我试过调试,但出于某种原因,(a) 问题只发生在我的 phone 上,而不是模拟器上,以及 (b) 调试时(与 运行 相对) ) 在我的 phone 上它要么工作正常,要么如果出错,只是终止(AFAIR 甚至没有 "X has stopped" 对话框)而不是显示错误的级别屏幕。我在调试中看到的唯一一件事是 LevelChooser
activity 的 onCreate
有时会执行多次。
因为问题是间歇性的并且不能直接重现,我想知道我是否无意中假设某些异步进程 has/will 已经及时、线性地完成,并且通常(但不总是) ) 义务;否则,我认为我未能理解与 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级然后退出后的截图:
事实证明,问题不在于 getSharedPreferences()
本身,而是由 getIntent().getStringExtra()
编辑的变量 user
return 有时——在某些情况下我发现很难确定或可靠地复制——null
,如 this question。然后将该字符串用作 getSharedPreferences()
的参数,从而为用户 null
生成一组保存的结果。每次问题再次出现时,都会显示这些结果(显示在问题的第二张图片中)。
解决方案——虽然它仍然没有解释为什么 getStringExtra()
应该 return null
——是通过将以下代码放入 onCreate
需要有效用户的两个活动:
if(getIntent().getExtras()==null || getIntent().getStringExtra("user")==null) {
Intent intent = new Intent();
setResult(0, intent);
finish();
}
如果这是在新 activity 的布局加载之前执行的,它 returns 到 Main Activity 并再次尝试(可以这么说)而用户看不到任何问题。
我有一个游戏 main
activity 使用 startActivityForResult
调用其他三个活动 -- 第一个 (SignInActivity
) return 是用户名,或允许创建一个新的;第二个 (LevelChooser
) 使用 getSharedPreferences
查找具有该用户名的首选项文件,或创建一个新文件,显示用户目前的进度(解锁级别,获得星星),并允许用户选择玩任何解锁级别;第三个 (GameActivity
) 如果在返回(通过 main
)到 LevelChooser
之前成功完成关卡,则更新用户的首选项文件。在 LevelChooser
中,我已将 onBackPressed
覆盖为 return 您的 SignInActivity;在 GameActivity
中,onFinish
被覆盖,因此无论它如何发生,您都可以返回到 LevelChooser
。
现在,十分之九这一切都完全按预期工作,但有时却没有:有时,LevelChooser
没有看到用户的实际星级和等级,而是显示了一组不正确的和理论上不可能的值(例如,一个级别显示为锁定,但完成时获得三颗星)。如果您选择了一个关卡,然后在您第一次打开游戏时退出该关卡,这种情况经常(但并非总是)发生:然后它会允许您玩任何显示为已解锁的关卡,但 GameActivity
无法保存您的如果您完成关卡,返回 LevelChooser
时会显示相同的错误关卡;或者,如果您退出 LevelChooser
并重新选择相同的用户名,它会返回到预期的行为。我还设法通过反复启动关卡并退出关卡来重现错误——如果你尝试了足够多的次数,它最终会出错。对于我自己的用户名(并且,我认为,对于所有用户)错误信息总是相同的,即问题是间歇性的,但当它发生时,不是随机的。
我试过调试,但出于某种原因,(a) 问题只发生在我的 phone 上,而不是模拟器上,以及 (b) 调试时(与 运行 相对) ) 在我的 phone 上它要么工作正常,要么如果出错,只是终止(AFAIR 甚至没有 "X has stopped" 对话框)而不是显示错误的级别屏幕。我在调试中看到的唯一一件事是 LevelChooser
activity 的 onCreate
有时会执行多次。
因为问题是间歇性的并且不能直接重现,我想知道我是否无意中假设某些异步进程 has/will 已经及时、线性地完成,并且通常(但不总是) ) 义务;否则,我认为我未能理解与 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级然后退出后的截图:
事实证明,问题不在于 getSharedPreferences()
本身,而是由 getIntent().getStringExtra()
编辑的变量 user
return 有时——在某些情况下我发现很难确定或可靠地复制——null
,如 this question。然后将该字符串用作 getSharedPreferences()
的参数,从而为用户 null
生成一组保存的结果。每次问题再次出现时,都会显示这些结果(显示在问题的第二张图片中)。
解决方案——虽然它仍然没有解释为什么 getStringExtra()
应该 return null
——是通过将以下代码放入 onCreate
需要有效用户的两个活动:
if(getIntent().getExtras()==null || getIntent().getStringExtra("user")==null) {
Intent intent = new Intent();
setResult(0, intent);
finish();
}
如果这是在新 activity 的布局加载之前执行的,它 returns 到 Main Activity 并再次尝试(可以这么说)而用户看不到任何问题。