问题是OutOfMemoryException:
java.lang.OutOfMemoryError at java.util.ArrayList.addAll(ArrayList.java:194) at
de.hackert.wwequiz2014.QuizScreen.CheckIfQuestionIsNew(QuizScreen.java:49) at
这是由于在 CheckIfQuestionIsNew() 方法中向 ArrayList 添加项目引起的。很可能在某处存在无限循环,因为您必须将很多项目(我说的是数千到数百万)添加到ListView 以获得OutOfMemoryException。我将查看您的代码,但我认为您可能会在熟悉代码后更快地识别出确切的错误。
编辑:
我想我发现了错误。
你到底想在这里做什么:
ListDifficulty = QuizIDsPassedD1;
ListDifficulty.addAll(QuizIDsPassedD1);
对我来说没有多大意义。这将一遍又一遍地重复列表中的所有项目,并结合 do/while 循环,这很可能是罪魁祸首。
编辑:
我从CheckIfQuestionIsNew()评论了你的代码:
boolean CheckIfQuestionIsNew(int element) {
List<Integer> ListDifficulty = new ArrayList<Integer>();
// What is this line suppsed to do? You are creating a new ArrayList, it is already empty why would you want to remove somthing here?
ListDifficulty.removeAll(ListDifficulty);
if (QuizDifficulty==1){
// Now you are adding items to the new list for reasons I don't understand
ListDifficulty.addAll(QuizIDsPassedD1);
}else if (QuizDifficulty==2){
// Same here
ListDifficulty.addAll(QuizIDsPassedD2);
}else if (QuizDifficulty==3){
// Same here
ListDifficulty.addAll(QuizIDsPassedD3);
}
if (ListDifficulty.contains(element))
return false;
// Where does this code belong? It is not part of the if statement above
// I added empty lines around it to make it more clear that this is a statement which is not contained in any if statement
ListDifficulty.add(element);
if (QuizDifficulty==1){
// What are you doing here? You remove all the items from the list and than add the List from above?
// This code does absolutely nothing and makes the ifs at the top and the ifs right here completely useless
QuizIDsPassedD1.removeAll((QuizIDsPassedD1));
QuizIDsPassedD1.addAll(ListDifficulty);
}else if (QuizDifficulty==2){
// Same here
QuizIDsPassedD2.removeAll((QuizIDsPassedD2));
QuizIDsPassedD2.addAll(ListDifficulty);
}else if (QuizDifficulty==3){
// Same here
QuizIDsPassedD3.removeAll((QuizIDsPassedD3));
QuizIDsPassedD3.addAll(ListDifficulty);
}
return true;
}
我想我的主要困惑来自于此:
您在 do/while 循环中调用 ShuffleQuiz();,但目的是什么?在这里想要达到什么目的?如果您只是想要一个新问题或之前已经/尚未回答的问题,为什么不实施ShuffleQuiz() 直接返回新问题而不是诉诸于此 - 我猜 - 随机挑选然后检查它是否可以循环?
编辑:
好的,我改进了您的代码。你的旧代码超过 770 行代码,我的改进版代码不到 210 行。首先,我创建了两个类,Question 和 Answer。它们分别保存与一个问题和一个答案相关的所有数据。 Question 类如下所示:
public class Question {
private final int imageResId;
private final int questionTextResId;
private final List<Answer> answers = new ArrayList<Answer>();
public Question(int questionTextResId, int imageResId) {
this.imageResId = imageResId;
this.questionTextResId = questionTextResId;
}
public Question addAnswer(int answerTextResId, boolean correct) {
Answer answer = new Answer(answerTextResId, correct);
this.answers.add(answer);
return this;
}
public int getQuestionTextResId() {
return questionTextResId;
}
public int getImageResId() {
return imageResId;
}
public List<Answer> getAnswers() {
return answers;
}
}
正如您所见,没有什么特别之处。它具有作为成员变量的问题文本的资源 ID、图像的资源 ID 和 Answers 的 List。此外,我添加了一个addAnswer() 方法来方便地添加问题的答案。
Answer 类如下所示:
public class Answer {
private final int answerTextResId;
private final boolean correct;
public Answer(int answerTextResId, boolean correct) {
this.answerTextResId = answerTextResId;
this.correct = correct;
}
public int getAnswerTextResId() {
return answerTextResId;
}
public boolean isCorrect() {
return correct;
}
}
您可以再次看到没有什么特别之处,但这里只是两个成员变量,一个是答案文本的资源 id,另一个是布尔值,如果此答案正确与否。
在向您展示我改进的QuizScreenActivity 的完整代码之前,我将向您解释我所做的所有更改以及它是如何工作的。首先,我为您使用的所有Views 创建了成员变量。你不应该经常打电话给findViewById()。当您将引用保存在成员变量中时,您无需再次调用findViewById():
private Button buttonAntwort1;
private Button buttonAntwort2;
private Button buttonAntwort3;
private Button buttonAntwort4;
private TextView textViewFrage;
private ImageView imageViewBild;
private Button[] answerButtons;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz_screen);
this.imageViewBild = (ImageView) findViewById(R.id.imageViewBild);
this.textViewFrage = (TextView) findViewById(R.id.textViewFrage);
this.buttonAntwort1 = (Button) findViewById(R.id.buttonAntwort1);
this.buttonAntwort2 = (Button) findViewById(R.id.buttonAntwort2);
this.buttonAntwort3 = (Button) findViewById(R.id.buttonAntwort3);
this.buttonAntwort4 = (Button) findViewById(R.id.buttonAntwort4);
this.answerButtons = new Button[] { this.buttonAntwort1, this.buttonAntwort2, this.buttonAntwort3, this.buttonAntwort4 };
createQuestions();
startGame();
}
如您所见,我还创建了一个Button[],其中包含所有可用于回答问题的Buttons。我的解决方案在这方面是完全灵活的。您可以拥有任意数量的Questions 和Answers,每个Question 可以拥有不同数量的Answers。您只需确保Button[] 中有足够的Buttons。如果您想拥有超过 4 个答案的 Question,只需将所需数量的 Buttons 添加到此 Button[],然后添加 Questions,如下所述。其余的都是完全自动的,您不必担心有太多Buttons。所有Question 不需要/不使用的Buttons 都会自动隐藏,使其不可见。
我创建了 3 个Lists,每个都包含一个难度级别的所有问题,另外还有一个List,这将是一个问题队列。当游戏实际运行时,我们一个接一个地通过问题队列。问题队列在游戏开始时生成一次,因此我们不必一直随机选择问题并检查它们是否被回答。
private final List<Question> easyQuestions = new ArrayList<Question>();
private final List<Question> mediumQuesitons = new ArrayList<Question>();
private final List<Question> hardQuestions = new ArrayList<Question>();
private final List<Question> questionQueue = new ArrayList<Question>();
我还添加了 3 种方便的方法,以便轻松地将 Lists 填充为 Questions:
private Question newEasy(int questionTextResId, int imageResId) {
Question question = new Question(questionTextResId, imageResId);
this.easyQuestions.add(question);
return question;
}
private Question newMedium(int questionTextResId, int imageResId) {
Question question = new Question(questionTextResId, imageResId);
this.mediumQuesitons.add(question);
return question;
}
private Question newHard(int questionTextResId, int imageResId) {
Question question = new Question(questionTextResId, imageResId);
this.hardQuestions.add(question);
return question;
}
在createQuestions() 方法中,所有Questions 都将使用这3 种便捷方法创建,正如我已经说过的,我没有复制您在源代码中的Questions,您将不得不添加他们再次在这里:
private void createQuestions() {
newEasy(R.string.question1_text, R.drawable.question1_picture1)
.addAnswer(R.string.question1_answer1, false).addAnswer(R.string.question1_answer2, true)
.addAnswer(R.string.question1_answer3, false).addAnswer(R.string.question1_answer4, false);
newMedium(R.string.question2_text, R.drawable.question2_picture1)
.addAnswer(R.string.question2_answer1, false).addAnswer(R.string.question2_answer2, true)
.addAnswer(R.string.question2_answer3, false).addAnswer(R.string.question2_answer4, false);
newHard(R.string.question3_text, R.drawable.question3_picture1)
.addAnswer(R.string.question3_answer1, false).addAnswer(R.string.question3_answer2, true)
.addAnswer(R.string.question3_answer3, false).addAnswer(R.string.question3_answer4, false);
}
如您所见,如果您想添加简单的Question,只需拨打newEasy(),如果您想添加中等的Question,请拨打newMedium(),如果您想添加困难的@987654378,请拨打newHard() @。您可以简单地以菊花链方式调用addAnswer(),将任意数量的Answers 添加到Question。这一切都应该是不言自明的。
在所有Questions 创建后,startGame() 将被调用。您可以随时通过调用startGame() 重新启动游戏,因此如果您想添加重新启动游戏的功能,您可以非常简单地做到这一点。 startGame() 方法如下所示:
private void startGame() {
Collections.shuffle(this.easyQuestions);
Collections.shuffle(this.mediumQuesitons);
Collections.shuffle(this.hardQuestions);
this.questionQueue.clear();
this.questionQueue.addAll(this.easyQuestions);
this.questionQueue.addAll(this.mediumQuesitons);
this.questionQueue.addAll(this.hardQuestions);
this.questionIndex = 0;
moveToQuestion(0);
}
顶部的Collections.shuffle() 将Lists 打乱,它随机重新排序元素。在中间我们创建了我们的questionQueue。首先我们clear()它删除了之前游戏中的所有问题,然后我们首先添加简单的问题,然后是中等问题,最后是困难问题。我们将questionIndex 重置为0。questionIndex 跟踪我们在quesitonQueue 中的位置。最后在底部,我们调用moveToQuestion(0); 以转到队列中的第一个问题。
moveToQuestion() 方法再次非常简单,但这次由于该方法有点复杂,我将添加 cmets 来解释它。该方法如下所示:
private void moveToQuestion(int index) {
// First we check if we have reached the end of the queue
if(index < this.questionQueue.size()) {
// If not we get the current question
Question question = this.questionQueue.get(index);
// Here we set the question text to the TextView
int questionTextResId = question.getQuestionTextResId();
this.textViewFrage.setText(questionTextResId);
// And here the question image to the ImageView.
int imageResId = question.getImageResId();
this.imageViewBild.setImageResource(imageResId);
// We get the answers from the question and create two count variables for convenience
List<Answer> answers = question.getAnswers();
int answerCount = answers.size();
int buttonCount = this.answerButtons.length;
// We start a loop through all the answer buttons
for(int i = 0; i < buttonCount; i++) {
// We get the current button from the Button[] which contains all the answer buttons
Button button = this.answerButtons[i];
// There might not be as many answers as there are buttons, that's what we check here
if(i < answerCount) {
// If there is an answer for this button make it visible
button.setVisibility(View.VISIBLE);
// We get the answer and bind to the button by calling bindAnswerToButton()
Answer answer = answers.get(i);
bindAnswerToButton(button, answer);
} else {
// If no answer exists for the Button we make it invisible.
button.setVisibility(View.GONE);
}
}
} else {
// We have reached the end of the queue
// You have to decide what happens when the game is won
Toast toast = Toast.makeText(this, R.string.game_won, Toast.LENGTH_SHORT);
toast.show();
}
}
在bindAnswerToButton()中我们设置了Button的文字和OnClickListener:
private void bindAnswerToButton(Button button, Answer answer) {
int answerTextResId = answer.getAnswerTextResId();
button.setText(answerTextResId);
button.setOnClickListener(new AnswerClickListener(answer));
}
如您所见,OnClickListener 是自定义的,并在其构造函数中将答案作为参数。这个自定义的OnClickListener 用于验证我们的答案并检查我们是否选择了正确的答案。自定义的OnClickListener 如下所示:
private class AnswerClickListener implements View.OnClickListener {
private final Answer answer;
private AnswerClickListener(Answer answer) {
this.answer = answer;
}
@Override
public void onClick(View v) {
if(this.answer.isCorrect()) {
gotoNextQuestion();
} else {
// You have to decide what happens when someone picks the wrong answer
Toast toast = Toast.makeText(QuizScreen.this, R.string.toast_wrong_answer, Toast.LENGTH_SHORT);
toast.show();
}
}
}
它真正做的唯一一件事是检查onClick 传递给它的答案是否正确,如果正确,则调用gotoNextQuestion() 以转到下一个问题。如果当前答案不正确,则只会显示Toast。你必须决定在这种情况下你想发生什么。
gotoNextQuestion() 再次只是一个方便的方法,它所做的只是增加我们的questionIndex,然后用增加的questionIndex 调用moveToQuestion 以移动到下一个Quesiton:
private void gotoNextQuestion() {
this.questionIndex++;
moveToQuestion(this.questionIndex);
}
差不多就是这样。这就是整个代码。请记住,您唯一需要做的就是在createQuestions() 中添加所有问题,正如我上面解释的那样。这里是QuizScreenActivity的完整源代码:
public static class QuizScreen extends ActionBarActivity {
private final List<Question> easyQuestions = new ArrayList<Question>();
private final List<Question> mediumQuesitons = new ArrayList<Question>();
private final List<Question> hardQuestions = new ArrayList<Question>();
private final List<Question> questionQueue = new ArrayList<Question>();
private int questionIndex = 0;
private Button buttonAntwort1;
private Button buttonAntwort2;
private Button buttonAntwort3;
private Button buttonAntwort4;
private TextView textViewFrage;
private ImageView imageViewBild;
private Button[] answerButtons;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz_screen);
this.imageViewBild = (ImageView) findViewById(R.id.imageViewBild);
this.textViewFrage = (TextView) findViewById(R.id.textViewFrage);
this.buttonAntwort1 = (Button) findViewById(R.id.buttonAntwort1);
this.buttonAntwort2 = (Button) findViewById(R.id.buttonAntwort2);
this.buttonAntwort3 = (Button) findViewById(R.id.buttonAntwort3);
this.buttonAntwort4 = (Button) findViewById(R.id.buttonAntwort4);
this.answerButtons = new Button[] { this.buttonAntwort1, this.buttonAntwort2, this.buttonAntwort3, this.buttonAntwort4 };
createQuestions();
startGame();
}
private void createQuestions() {
newEasy(R.string.question1_text, R.drawable.question1_picture1)
.addAnswer(R.string.question1_answer1, false).addAnswer(R.string.question1_answer2, true)
.addAnswer(R.string.question1_answer3, false).addAnswer(R.string.question1_answer4, false);
newMedium(R.string.question2_text, R.drawable.question2_picture1)
.addAnswer(R.string.question2_answer1, false).addAnswer(R.string.question2_answer2, true)
.addAnswer(R.string.question2_answer3, false).addAnswer(R.string.question2_answer4, false);
newHard(R.string.question3_text, R.drawable.question3_picture1)
.addAnswer(R.string.question3_answer1, false).addAnswer(R.string.question3_answer2, true)
.addAnswer(R.string.question3_answer3, false).addAnswer(R.string.question3_answer4, false);
}
private Question newEasy(int questionTextResId, int imageResId) {
Question question = new Question(questionTextResId, imageResId);
this.easyQuestions.add(question);
return question;
}
private Question newMedium(int questionTextResId, int imageResId) {
Question question = new Question(questionTextResId, imageResId);
this.mediumQuesitons.add(question);
return question;
}
private Question newHard(int questionTextResId, int imageResId) {
Question question = new Question(questionTextResId, imageResId);
this.hardQuestions.add(question);
return question;
}
private void startGame() {
Collections.shuffle(this.easyQuestions);
Collections.shuffle(this.mediumQuesitons);
Collections.shuffle(this.hardQuestions);
this.questionQueue.clear();
this.questionQueue.addAll(this.easyQuestions);
this.questionQueue.addAll(this.mediumQuesitons);
this.questionQueue.addAll(this.hardQuestions);
this.questionIndex = 0;
moveToQuestion(0);
}
private void moveToQuestion(int index) {
if(index < this.questionQueue.size()) {
Question question = this.questionQueue.get(index);
int questionTextResId = question.getQuestionTextResId();
this.textViewFrage.setText(questionTextResId);
int imageResId = question.getImageResId();
this.imageViewBild.setImageResource(imageResId);
List<Answer> answers = question.getAnswers();
int answerCount = answers.size();
int buttonCount = this.answerButtons.length;
for(int i = 0; i < buttonCount; i++) {
Button button = this.answerButtons[i];
if(i < answerCount) {
button.setVisibility(View.VISIBLE);
Answer answer = answers.get(i);
bindAnswerToButton(button, answer);
} else {
button.setVisibility(View.GONE);
}
}
}
}
private void gotoNextQuestion() {
this.questionIndex++;
moveToQuestion(this.questionIndex);
}
private void bindAnswerToButton(Button button, Answer answer) {
int answerTextResId = answer.getAnswerTextResId();
button.setText(answerTextResId);
button.setOnClickListener(new AnswerClickListener(answer));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.quiz_screen, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private class AnswerClickListener implements View.OnClickListener {
private final Answer answer;
private AnswerClickListener(Answer answer) {
this.answer = answer;
}
@Override
public void onClick(View v) {
if(this.answer.isCorrect()) {
gotoNextQuestion();
} else {
Toast toast = Toast.makeText(QuizScreen.this, R.string.toast_wrong_answer, Toast.LENGTH_SHORT);
toast.show();
}
}
}
}
如果您还有其他问题,请随时提问!