Android中Testme Activity启动时触发NullPointerException问题求助
解决Testme Activity启动时的RuntimeException(NullPointerException)
兄弟,我一眼就瞅出问题根源了——你在类的成员变量声明阶段直接调用了findViewById()!这时候Activity还没完成初始化,连布局都还没加载,根本找不到对应的控件,直接就触发空指针了。
问题原因详解
Android的Activity生命周期里,findViewById()的调用必须在setContentView()之后,因为只有加载了布局文件,系统才能根据ID找到对应的控件实例。你现在把txtquestion、answer1这些控件变量放在类的成员位置直接初始化,这时候Activity的上下文还没准备好,Window对象都是null,也就出现了报错里的Attempt to invoke virtual method 'android.view.Window$Callback android.view.Window.getCallback()' on a null object reference。
修复步骤
- 先声明控件变量,不要直接初始化:把所有控件变量的声明留在类的成员位置,但去掉
findViewById()的调用。 - 在
setContentView()之后初始化控件:在onCreate()方法里,调用完setContentView(R.layout.activity_testme);之后,再逐个调用findViewById()初始化控件。
修改后的完整代码示例
public class Testme extends AppCompatActivity { // 只声明控件变量,不初始化 private TextView txtquestion; private Button answer1; private Button answer2; private Button answer3; private Button answer4; int qnum = 0; private List<TopicQuestion> questionsList = new ArrayList<>(); private boolean correct = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_testme); // 在这里初始化所有控件 txtquestion = findViewById(R.id.txtquestion); answer1 = findViewById(R.id.btnans1); answer2 = findViewById(R.id.btnans2); answer3 = findViewById(R.id.btnans3); answer4 = findViewById(R.id.btnans4); readQuestions(); } private void readQuestions() { InputStream is = getResources().openRawResource(R.raw.questions); BufferedReader reader = new BufferedReader( new InputStreamReader(is, Charset.forName("UTF-8")) ); String line = ""; try { while ( (line = reader.readLine()) != null) { //Split the data String[] tokens = line.split(","); //Read the data TopicQuestion question = new TopicQuestion(); question.setTopic(tokens[0]); question.setQuestion(tokens[1]); question.setAns1(tokens[2]); question.setAns2(tokens[3]); question.setAns3(tokens[4]); question.setAns4(tokens[5]); questionsList.add(question); } } catch (IOException e) { Log.wtf("MyActivity","Error reading question file on line" + line, e); e.printStackTrace(); } // 初始化第一题的内容 updateQuestionUI(); answer1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { correct = questionsList.get(qnum).getAns1().equals(answer1.getText().toString()); // 点击后再判断是否切换题目 checkAnswerAndUpdate(); } }); answer2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { correct = questionsList.get(qnum).getAns2().equals(answer2.getText().toString()); checkAnswerAndUpdate(); } }); answer3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { correct = questionsList.get(qnum).getAns2().equals(answer3.getText().toString()); // 这里你之前写的是getAns1,应该是笔误吧? checkAnswerAndUpdate(); } }); answer4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { correct = questionsList.get(qnum).getAns4().equals(answer4.getText().toString()); // 同样这里之前是getAns1,应该是笔误 checkAnswerAndUpdate(); } }); } // 抽取出更新题目UI的方法,避免重复代码 private void updateQuestionUI() { if (qnum >= questionsList.size()) { // 防止数组越界,比如题目做完了的处理 txtquestion.setText("题目已完成!"); return; } TopicQuestion currentQ = questionsList.get(qnum); txtquestion.setText(currentQ.getQuestion()); answer1.setText(currentQ.getAns1()); answer2.setText(currentQ.getAns3()); answer3.setText(currentQ.getAns2()); answer4.setText(currentQ.getAns4()); } // 抽取出判断答案并更新题目的方法 private void checkAnswerAndUpdate() { if (correct) { qnum +=1; } updateQuestionUI(); } }
额外的小建议
- 我还注意到你原来的
readQuestions()方法里,在设置完点击事件后直接判断correct的值,这时候correct初始是false,而且用户还没点击任何按钮,这个判断完全没有意义,我把这部分逻辑移到了点击事件里,每次用户点击后再判断是否切换题目。 - 另外你在answer3和answer4的点击事件里,判断的是
getAns1(),这明显是笔误,我已经改成了对应的getAns2()和getAns4(),你可以根据实际需求调整。 - 新增了
updateQuestionUI()方法,把重复的更新UI代码抽出来,既简洁又方便维护,还加了防止数组越界的判断。
内容的提问来源于stack exchange,提问作者Christian Webb
相关产品推荐
相关产品推荐

