You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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上,形成了循环触发链:

  1. 用户在kilojoulesText输入内容,触发onTextChanged回调
  2. 计算后调用caloriesText.setText(),触发caloriesText的TextWatcher回调
  3. 回调中判断是caloriesText的文本变化,调用kilojoulesText.setText(),再次触发kilojoulesText的TextWatcher
  4. 循环往复直到栈溢出

另外,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 10:21:02