【问题标题】:Simple do-while loop causes crash简单的 do-while 循环导致崩溃
【发布时间】:2014-05-12 14:03:53
【问题描述】:

我正在开发一款适用于 Android 的问答游戏。我正在尝试生成尚未选择的随机测验问题。

My Main class(这里不适合,抱歉)。

您可以在public void GenerateQuiz() 中找到导致崩溃的do-while。 该应用程序在没有 do-while 功能的情况下可以正常工作,因此它一定有问题。 它只是在 6./7./8 上不断使应用程序崩溃。随机问题,但至少有效。 它所做的只是检查该随机问题是否已经被问过。

如果是 -> 生成一个新的随机问题,直到它是一个新问题并且之前没有被问过。

如果没有 -> 那将是下一个问题。

片段:

public void GenerateQuiz() {
    do{
        QuizID = ShuffleQuiz();
    }while(CheckIfQuestionIsNew(QuizID)==false);

有 3 个难度:简单、中等和困难。他们每个人只有 10 个问题 = 总共 30 个问题。问题由随机生成的 1-10 个 INT 生成。 用户完成 10 个问题后,应用程序会将难度更改为下一个最高难度。示例:如果第 10 题(简单)的回答正确,则将难度更改为中等。完成第 10 题 MEDIUM 后,它将变为 HARD。

更新 LogCat 崩溃后的错误消息:

http://pastebin.com/fPiNrCEr

05-12 16:45:00.232 14067-14067/? E/ClockAlarmWidget﹕ [AlarmWidgetIdManager] getListItem() : itemIndex=0, widgetID:1 05-12 16:45:00.232 14067-14067/? E/ClockAlarmWidget﹕[AlarmWidgetIdManager] getListItem() : ItemIndex 超过 ListItemCount。项目索引=1 05-12 16:45:00.232 14067-14067/? E/ClockAlarmWidget﹕[AlarmWidgetIdManager] getListItem() : itemIndex=1, widgetID:1

来源:

 boolean CheckIfQuestionIsNew(int element) {
        List<Integer> ListDifficulty = new ArrayList<Integer>();

        //#########GET ARRAYLIST#########
        //Determine the Difficulty since each Difficulty got it's own arraylist.
        if (QuizDifficulty==1){//Example: If Difficulty==1, copy it's arrays to the new list of array.
            ListDifficulty.addAll(QuizIDsPassedD1);
        }else if (QuizDifficulty==2){
            ListDifficulty.addAll(QuizIDsPassedD2);
        }else if (QuizDifficulty==3){
            ListDifficulty.addAll(QuizIDsPassedD3);
        }

        if (ListDifficulty.contains(element))
            return false;

        //#########UPDATE ARRAYLIST#########
        // If Question was not asked before then --> Add the new question ID to the arraylist
        ListDifficulty.add(element);


        //#########SAVE NEW ARRAYLIST#########
        //Now it needs to determine the difficulty aggain to update its arraylist with the new items.
        if (QuizDifficulty==1){
            QuizIDsPassedD1.removeAll((QuizIDsPassedD1));//Remove All (Double Make Sure)
            QuizIDsPassedD1.addAll(ListDifficulty);//Transfer new Arraylist to the difficultyies array list
        }else if (QuizDifficulty==2){
            QuizIDsPassedD2.removeAll((QuizIDsPassedD2));
            QuizIDsPassedD2.addAll(ListDifficulty);
        }else if (QuizDifficulty==3){
            QuizIDsPassedD3.removeAll((QuizIDsPassedD3));
            QuizIDsPassedD3.addAll(ListDifficulty);
        }
        return true;
    }

【问题讨论】:

  • 如果您遇到任何异常,请始终将 logcat 包含在您的问题中。没有它,我们只能猜测问题可能是什么。

标签: android while-loop


【解决方案1】:

问题是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 行。首先,我创建了两个类,QuestionAnswer。它们分别保存与一个问题和一个答案相关的所有数据。 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 和 AnswersList。此外,我添加了一个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。我的解决方案在这方面是完全灵活的。您可以拥有任意数量的QuestionsAnswers,每个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();
            }
        }
    }
}

如果您还有其他问题,请随时提问!

【讨论】:

  • 很高兴能帮上忙。但是,您可以做的是接受我的回答。这向其他人表明我的答案是解决您的问题的答案,我们都从中获得了一些声誉。您可以找到更多信息here
【解决方案2】:

改为使用下面的代码,在此之前检查 CheckIfQuestionIsNew(QuizID) 是否返回布尔值

 do{
        QuizID = ShuffleQuiz();
    }while(CheckIfQuestionIsNew(QuizID));

【讨论】:

    【解决方案3】:

    在“DO/WHILE”循环中,大括号之间的代码至少执行一次。因此,如果您在其中遇到异常,那应该是因为您不应该进入循环。考虑改用 while 子句吗?

    如果这是导致崩溃的大括号之间的行,请添加方法的代码。

    请同时添加 logcat。

    【讨论】:

    • 它没有给我任何错误信息。整个 VOID 可以在我上面发布的链接中看到。我可以得到一些while循环的例子吗?
    • 你是什么意思它没有给你任何错误信息?你说你的应用程序崩溃了,所以你的 logcat 中必须有堆栈跟踪,除非你在 try/catch 的某个地方吃掉异常。
    • 不要尝试在此处发布 logcat,将其编辑到您的问题中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-11
    • 1970-01-01
    • 2020-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多