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

EditText添加TextChangedListener后选中文本崩溃(仅Android6.0.1)

Android 6.0.1 中 AppCompatEditText 选中文本崩溃问题

在Android 6.0.1系统下,给AppCompatEditText添加TextChangedListener后,尝试选中文本时应用崩溃,但Android 11无此问题。崩溃原因为空指针异常,触发点是调用android.widget.Editor$SelectionStartHandleView.showAtLocation(int)时对象为null。

相关代码

XML布局

<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.AppCompatEditText
        android:id="@+id/edit1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</androidx.appcompat.widget.LinearLayoutCompat>

MainActivity.kt代码

binding.edit1.addTextChangedListener(object : TextWatcher {
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
    override fun afterTextChanged(s: Editable?) {}
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        binding.edit1.removeTextChangedListener(this)
        binding.edit1.setText(binding.edit1.text.toString() + "1")
        binding.edit1.addTextChangedListener(this)
    }
})

崩溃日志

E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.zref.experiment, PID: 4643
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Editor$SelectionStartHandleView.showAtLocation(int)' on a null object reference
    at android.widget.Editor$SelectionModifierCursorController.onTouchEvent(Editor.java:4866)
    at android.widget.Editor.onTouchEvent(Editor.java:1223)
    at android.widget.TextView.onTouchEvent(TextView.java:8292)
    at android.view.View.dispatchTouchEvent(View.java:9332)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254)
    at com.android.internal.policy.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2404)
    at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1738)
    at android.app.Activity.dispatchTouchEvent(Activity.java:2787)
    at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
    at com.android.internal.policy.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2365)
    at android.view.View.dispatchPointerEvent(View.java:9552)
    at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4230)
    at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4096)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3642)
    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3695)
    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3661)
    at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3787)
    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3669)
    at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3844)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3642)
    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3695)
    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3661)
    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3669)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3642)
    at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5922)
    at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5896)
    at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5857)
    at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6025)
    at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
    at android.os.MessageQueue.nativePollOnce(Native Method)
    at android.os.MessageQueue.next(MessageQueue.java:323)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5417)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:772)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:662)

问题分析与解决方案

问题根源

在onTextChanged中直接调用setText会触发文本内容重置,同时在Android 6.0.1的系统UI实现中,选中文本时的SelectionHandleView尚未完成初始化就被触发显示,导致空指针。另外,频繁移除/添加TextWatcher的操作会干扰EditText的内部状态同步,尤其是在低版本系统中。

解决方案

  • 改用afterTextChanged直接修改Editable:避免调用setText触发完整的文本重置流程,同时通过标志位防止循环触发:
binding.edit1.addTextChangedListener(object : TextWatcher {
    private var isUpdating = false

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
    override fun afterTextChanged(s: Editable?) {
        if (isUpdating) return
        isUpdating = true
        s?.append("1")
        isUpdating = false
    }
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
  • 保存并恢复选中状态(若必须用setText):修改文本前保存当前选中位置,修改后恢复,避免干扰SelectionHandleView的初始化:
binding.edit1.addTextChangedListener(object : TextWatcher {
    private var isUpdating = false

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
    override fun afterTextChanged(s: Editable?) {
        if (isUpdating) return
        isUpdating = true
        val start = binding.edit1.selectionStart
        val end = binding.edit1.selectionEnd
        binding.edit1.setText(binding.edit1.text.toString() + "1")
        binding.edit1.setSelection(start, end)
        isUpdating = false
    }
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
  • 移除不必要的TextWatcher操作:无需每次修改都移除再添加,通过isUpdating标志位即可防止循环触发,减少对EditText内部状态的干扰。

内容的提问来源于stack exchange,提问作者zihadrizkyef

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 09:01:22