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
相关产品推荐
相关产品推荐

