我的代码在99%的时间内都能正常工作,但是如果我将键盘捣烂足够长的时间,有时我可以得到它以抛出IndexOutOfBounds异常。我无法确切地告诉您如何重现它,因为如果没有字面的键盘混音,我就无法自己重现它。
范围与我的不同editText上的字符限制匹配,但是我无法弄清楚是什么导致操作尝试执行超出范围的操作。
该错误已由pushDigitToNextEditText,moveCursorIfNecessary和pushDigitToFront方法(间接触发),但是在我的所有堆栈跟踪代码中都没有发生此错误,只是Android源代码。 (怎么可能?)
我对这种特殊的outOfBounds异常了解很少,我已经准备好放弃了。我不敢相信实际用户会滥用编辑文本框的方式来使它引发错误,但是谁知道呢?
出于同样的原因,我无法告诉您如何重现它,我不知道所需的最少代码量是多少。这就是全部。我添加了堆栈跟踪之一,但我可以给您更多。我有很多
package com.example.phonenumberdemo
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
import com.example.phonenumberdemo.databinding.ActivityMainBinding
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setUpPhoneEditTexts()
}
private var digitDeletedFlag = false
private var userIsNotMakingChanges = false
private var prevEditText1CursorPosition: Int? = null
private var prevEditText2CursorPosition: Int? = null
private fun setUpPhoneEditTexts() {
phoneEditText1.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
if (userIsNotMakingChanges) {
return
}
if (phoneEditText1.text.length == 3) {
val lastDigit = chopOffLastDigit(1)
pushDigitToNextEditText(lastDigit, 2)
moveCursorIfNecessary(1)
}
if (digitDeletedFlag) {
pullDigitFromNextEditText(1)
digitDeletedFlag = false
}
prevEditText1CursorPosition = null
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
if (p3 < p2 && !userIsNotMakingChanges) {
digitDeletedFlag = true
}
prevEditText1CursorPosition = phoneEditText1.selectionStart
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
phoneEditText2.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
if (userIsNotMakingChanges) {
return
}
if (phoneEditText2.selectionEnd == 0) {
moveCursorBackTo(1)
}
if (phoneEditText2.text.length == 4) {
val lastDigit = chopOffLastDigit(2)
pushDigitToNextEditText(lastDigit, 3)
moveCursorIfNecessary(2)
}
if (digitDeletedFlag) {
pullDigitFromNextEditText(2)
digitDeletedFlag = false
}
prevEditText2CursorPosition = null
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
if (p3 < p2 && !userIsNotMakingChanges) {
digitDeletedFlag = true
}
prevEditText2CursorPosition = phoneEditText2.selectionStart
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
phoneEditText3.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
if (userIsNotMakingChanges) {
return
}
if (phoneEditText3.selectionEnd == 0) {
moveCursorBackTo(2)
}
if (phoneEditText3.text.length == 5) {
chopOffLastDigit(3)
}
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
}
private fun chopOffLastDigit(editTextNum: Int): Char {
val editText = getEditText(editTextNum)
val maxLength = getMaxLength(editTextNum)
val lastDigit = editText.text[maxLength]
userIsNotMakingChanges = true
editText.text.delete(maxLength, maxLength + 1)
userIsNotMakingChanges = false
return lastDigit
}
private fun pushDigitToNextEditText(digit: Char, nextEditTextNum: Int) {
val nextEditText = getEditText(nextEditTextNum)
val nextNextEditTextNum = getNextEditTextNum(nextEditTextNum)
val maxLength = getMaxLength(nextEditTextNum)
userIsNotMakingChanges = true
var bumpedDigit: Char? = null
if (nextEditText.text.length == maxLength) {
bumpedDigit = nextEditText.text[maxLength - 1]
nextEditText.text.delete(maxLength - 1, maxLength)
}
nextEditText.text.insert(0, digit.toString())
if (bumpedDigit != null && nextNextEditTextNum != null) {
pushDigitToNextEditText(bumpedDigit, nextNextEditTextNum)
}
userIsNotMakingChanges = false
}
private fun moveCursorBackTo(prevEditTextNum: Int) {
val prevEditText = getEditText(prevEditTextNum)
prevEditText.requestFocus()
prevEditText.setSelection(prevEditText.length())
}
private fun moveCursorIfNecessary(editTextNum: Int) {
if (editTextNum == 1 && prevEditText1CursorPosition!! > 1) {
phoneEditText2.requestFocus()
phoneEditText2.setSelection(prevEditText1CursorPosition!! - 2)
} else if (editTextNum == 2 && prevEditText2CursorPosition!! > 2) {
phoneEditText3.requestFocus()
phoneEditText3.setSelection(prevEditText2CursorPosition!! - 3)
}
}
private fun pullDigitFromNextEditText(editTextNum: Int) {
val editText = getEditText(editTextNum)
val maxLength = getMaxLength(editTextNum)
val nextEditTextNum = getNextEditTextNum(editTextNum)
val nextEditText = getNextEditText(editTextNum)
if (editText.text.length != maxLength - 1) {
return
}
if (nextEditText?.text.isNullOrEmpty()) {
return
}
userIsNotMakingChanges = true
val pulledDigit = nextEditText!!.text[0]
editText.text.append(pulledDigit)
if (editText.selectionEnd == editText.text.length) {
editText.setSelection(editText.selectionEnd - 1)
}
nextEditText.text.delete(0, 1)
nextEditText.setText(nextEditText.text.toString())
pullDigitFromNextEditText(nextEditTextNum!!)
userIsNotMakingChanges = false
}
private fun getEditText(editTextNum: Int): EditText {
return when (editTextNum) {
1 -> phoneEditText1
2 -> phoneEditText2
3 -> phoneEditText3
else -> error("invalid editTextNum")
}
}
private fun getMaxLength(editTextNum: Int): Int {
return when (editTextNum) {
1 -> 2
2 -> 3
3 -> 4
else -> error("invalid editTextNum")
}
}
private fun getNextEditTextNum(editTextNum: Int): Int? {
return when (editTextNum) {
1 -> 2
2 -> 3
3 -> null
else -> error("invalid editTextNum")
}
}
private fun getNextEditText(editTextNum: Int): EditText? {
return when (editTextNum) {
1 -> phoneEditText2
2 -> phoneEditText3
3 -> null
else -> error("invalid editTextNum")
}
}
}
布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enter your phone number to create an account or to log in."
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="32dp"
android:textAlignment="center"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:textSize="24sp" android:id="@+id/text_block"/>
<TextView
android:id="@+id/phoneTextView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="(5"
android:textSize="32sp" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:textColor="@color/colorPrimaryDark"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toStartOf="@+id/phoneEditText1"
app:layout_constraintHorizontal_chainStyle="packed" android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@+id/text_block"/>
<EditText
android:id="@+id/phoneEditText1"
android:layout_width="43sp"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLength="3"
android:textColor="@color/colorPrimaryDark"
app:layout_constraintStart_toEndOf="@+id/phoneTextView1"
app:layout_constraintTop_toTopOf="@+id/phoneTextView1"
app:layout_constraintBottom_toBottomOf="@+id/phoneTextView1" android:textSize="32sp"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintEnd_toStartOf="@+id/phoneTextView2"/>
<TextView
android:id="@+id/phoneTextView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text=")"
app:layout_constraintStart_toEndOf="@+id/phoneEditText1"
android:textSize="32sp"
android:textColor="@color/colorPrimaryDark"
app:layout_constraintBottom_toBottomOf="@+id/phoneTextView1"
app:layout_constraintTop_toTopOf="@+id/phoneTextView1" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toStartOf="@+id/phoneEditText2"/>
<EditText
android:id="@+id/phoneEditText2"
android:layout_width="63sp"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLength="4"
android:ems="10"
app:layout_constraintStart_toEndOf="@+id/phoneTextView2"
app:layout_constraintBottom_toBottomOf="@+id/phoneTextView1"
android:textColor="@color/colorPrimaryDark"
app:layout_constraintTop_toTopOf="@+id/phoneTextView1" android:textSize="32sp"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintEnd_toStartOf="@+id/phoneTextView3"/>
<TextView
android:id="@+id/phoneTextView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="-"
app:layout_constraintStart_toEndOf="@+id/phoneEditText2"
app:layout_constraintBottom_toBottomOf="@+id/phoneTextView1"
app:layout_constraintTop_toTopOf="@+id/phoneTextView1"
android:textColor="@color/colorPrimaryDark"
android:textSize="32sp" app:layout_constraintEnd_toStartOf="@+id/phoneEditText3"
app:layout_constraintHorizontal_bias="0.5"/>
<EditText
android:id="@+id/phoneEditText3"
android:layout_width="84sp"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLength="5"
android:ems="10"
app:layout_constraintStart_toEndOf="@+id/phoneTextView3"
app:layout_constraintBottom_toBottomOf="@+id/phoneTextView1"
app:layout_constraintTop_toTopOf="@+id/phoneTextView1" android:textSize="32sp"
android:textColor="@color/colorPrimaryDark"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
堆栈跟踪
2020-01-07 20:56:27.344 16142-16142/com.example.phonenumberdemo E/MessageQueue-JNI: java.lang.IndexOutOfBoundsException: measureLimit (4) is out of start (0) and limit (3) bounds
at android.text.TextLine.handleRun(TextLine.java:1106)
at android.text.TextLine.measureRun(TextLine.java:528)
at android.text.TextLine.measure(TextLine.java:326)
at android.text.Layout.getHorizontal(Layout.java:1184)
at android.text.Layout.getHorizontal(Layout.java:1162)
at android.text.Layout.getPrimaryHorizontal(Layout.java:1133)
at android.text.Layout.getPrimaryHorizontal(Layout.java:1123)
at android.widget.Editor$SelectionHandleView.getHorizontal(Editor.java:5562)
at android.widget.Editor$SelectionHandleView.getHorizontal(Editor.java:5553)
at android.widget.Editor$HandleView.getCursorHorizontalPosition(Editor.java:4706)
at android.widget.Editor$HandleView.positionAtCursorOffset(Editor.java:4685)
at android.widget.Editor$SelectionHandleView.positionAtCursorOffset(Editor.java:5466)
at android.widget.Editor$HandleView.show(Editor.java:4596)
at android.widget.Editor$SelectionModifierCursorController.initHandles(Editor.java:5788)
at android.widget.Editor$SelectionModifierCursorController.show(Editor.java:5760)
at android.widget.Editor$SelectionModifierCursorController.enterDrag(Editor.java:5800)
at android.widget.Editor.selectCurrentWordAndStartDrag(Editor.java:2187)
at android.widget.Editor.access$7800(Editor.java:144)
at android.widget.Editor$SelectionModifierCursorController.onTouchEvent(Editor.java:5848)
at android.widget.Editor.onTouchEvent(Editor.java:1475)
at android.widget.TextView.onTouchEvent(TextView.java:10056)
at android.view.View.dispatchTouchEvent(View.java:12513)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:440)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1830)
at android.app.Activity.dispatchTouchEvent(Activity.java:3400)
at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:398)
at android.view.View.dispatchPointerEvent(View.java:12752)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5106)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4909)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4585)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4453)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4642)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4453)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7092)
2020-01-07 20:56:27.344 16142-16142/com.example.phonenumberdemo D/AndroidRuntime: Shutting down VM
2020-01-07 20:56:27.351 16142-16142/com.example.phonenumberdemo E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.phonenumberdemo, PID: 16142
java.lang.IndexOutOfBoundsException: measureLimit (4) is out of start (0) and limit (3) bounds
at android.text.TextLine.handleRun(TextLine.java:1106)
at android.text.TextLine.measureRun(TextLine.java:528)
at android.text.TextLine.measure(TextLine.java:326)
at android.text.Layout.getHorizontal(Layout.java:1184)
at android.text.Layout.getHorizontal(Layout.java:1162)
at android.text.Layout.getPrimaryHorizontal(Layout.java:1133)
at android.text.Layout.getPrimaryHorizontal(Layout.java:1123)
at android.widget.Editor$SelectionHandleView.getHorizontal(Editor.java:5562)
at android.widget.Editor$SelectionHandleView.getHorizontal(Editor.java:5553)
at android.widget.Editor$HandleView.getCursorHorizontalPosition(Editor.java:4706)
at android.widget.Editor$HandleView.positionAtCursorOffset(Editor.java:4685)
at android.widget.Editor$SelectionHandleView.positionAtCursorOffset(Editor.java:5466)
at android.widget.Editor$HandleView.show(Editor.java:4596)
at android.widget.Editor$SelectionModifierCursorController.initHandles(Editor.java:5788)
at android.widget.Editor$SelectionModifierCursorController.show(Editor.java:5760)
at android.widget.Editor$SelectionModifierCursorController.enterDrag(Editor.java:5800)
at android.widget.Editor.selectCurrentWordAndStartDrag(Editor.java:2187)
at android.widget.Editor.access$7800(Editor.java:144)
at android.widget.Editor$SelectionModifierCursorController.onTouchEvent(Editor.java:5848)
at android.widget.Editor.onTouchEvent(Editor.java:1475)
at android.widget.TextView.onTouchEvent(TextView.java:10056)
at android.view.View.dispatchTouchEvent(View.java:12513)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:440)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1830)
at android.app.Activity.dispatchTouchEvent(Activity.java:3400)
at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:398)
at android.view.View.dispatchPointerEvent(View.java:12752)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5106)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4909)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4585)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4453)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4642)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4453)
2020-01-07 20:56:27.351 16142-16142/com.example.phonenumberdemo E/AndroidRuntime: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7092)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7061)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7022)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7195)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:186)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:326)
at android.os.Looper.loop(Looper.java:160)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2020-01-07 20:56:26.272 16142-16166/com.example.phonenumberdemo D/TextClassifierService: No configured system TextClassifierService
2020-01-07 20:56:27.373 16142-16142/com.example.phonenumberdemo I/Process: Sending signal. PID: 16142 SIG: 9
似乎很奇怪(在您的chopOffLastDigit
方法中)
val lastDigit = editText.text[maxLength];
例如,如果editText
的maxLength为4,则最后一个索引为3,而不是4。