Android输入数字至EditText时Runtime Error崩溃问题排查
卡路里与千焦转换器崩溃问题排查解决
问题概述
开发实时转换的卡路里与千焦转换器,用户在两个EditText输入数字时需实时完成转换,但输入数字后应用立即冻结并因运行时错误崩溃,经Logcat定位问题出在caloriesText.setText(cal.toString())和kilojoulesText.setText(kj.toString())两行代码。
错误日志分析
从Logcat可以看到明显的无限递归调用,最终导致栈溢出崩溃:
at com.example.energyconverter.MainActivity$onCreate$textWatcher$1.onTextChanged(MainActivity.kt:41) at android.widget.TextView.sendOnTextChanged(TextView.java:10789) at android.widget.TextView.setText(TextView.java:6401) at android.widget.TextView.setText(TextView.java:6227) at android.widget.EditText.setText(EditText.java:121) at android.widget.TextView.setText(TextView.java:6179) at com.example.energyconverter.MainActivity$onCreate$textWatcher$1.onTextChanged(MainActivity.kt:47) ...
日志中onTextChanged与setText反复交替调用,说明触发了无限循环。
代码问题根源
原代码中同一个TextWatcher绑定到两个EditText上,形成了循环触发链:
- 用户在
kilojoulesText输入内容,触发onTextChanged回调 - 计算后调用
caloriesText.setText(),触发caloriesText的TextWatcher回调 - 回调中判断是
caloriesText的文本变化,调用kilojoulesText.setText(),再次触发kilojoulesText的TextWatcher - 循环往复直到栈溢出
另外,s == kilojoulesText.text这种引用比较的方式并不可靠,不同时机的CharSequence可能不是同一个对象。
解决方案
添加布尔标记isUpdating,在更新EditText文本时暂时禁用TextWatcher逻辑,避免递归触发:
- 开始更新前将
isUpdating设为true - 更新完成后将
isUpdating设为false - 在
onTextChanged开头判断isUpdating,如果为true则直接返回,跳过本次处理
修改后的完整代码
MainActivity.kt
package com.example.energyconverter import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.widget.EditText class MainActivity : AppCompatActivity() { private val KJ_TO_CAL = 4.184 private val CAL_TO_KJ = 1.0 / KJ_TO_CAL private lateinit var kilojoulesText: EditText private lateinit var caloriesText: EditText // 标记是否正在更新文本,避免递归触发TextWatcher private var isUpdating = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) kilojoulesText = findViewById(R.id.kilojoules_text) caloriesText = findViewById(R.id.calories_text) val textWatcher = object : TextWatcher { override fun afterTextChanged(s: Editable?) {} override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { // 如果正在更新文本,直接返回 if (isUpdating) return // 根据焦点判断当前输入的EditText when { kilojoulesText.hasFocus() -> { val kj = s.toString().toDoubleOrNull() isUpdating = true caloriesText.setText(kj?.let { String.format("%.2f", it * KJ_TO_CAL) } ?: "") isUpdating = false } caloriesText.hasFocus() -> { val cal = s.toString().toDoubleOrNull() isUpdating = true kilojoulesText.setText(cal?.let { String.format("%.2f", it * CAL_TO_KJ) } ?: "") isUpdating = false } } } } kilojoulesText.addTextChangedListener(textWatcher) caloriesText.addTextChangedListener(textWatcher) } }
额外优化说明
- 使用
hasFocus()判断当前输入的EditText,比直接比较CharSequence更可靠 - 添加空值处理,输入清空时同步清空另一个输入框
- 用
String.format("%.2f", value)格式化输出,避免显示过长小数
内容的提问来源于stack exchange,提问作者JTek
相关产品推荐
相关产品推荐

