MAUI中Entry控件处理手机号格式化时粘贴+91开头号码触发IndexOutOfBoundsException异常问题排查
嘿,我之前也碰到过MAUI Entry做手机号格式化时,粘贴特定前缀号码崩溃的情况,先看看你遇到的异常:
Pending exception java.lang.IndexOutOfBoundsException: setSpan (15 ... 15) ends beyond length 12
(Throwable with no stack trace)
JNI DETECTED ERROR IN APPLICATION: JNI CallObjectMethodA called with pending exception java.lang.IndexOutOfBoundsException: setSpan (15 ... 15) ends beyond length 12
(Throwable with no stack trace)
in call to CallObjectMethodA
from void crc6477f0d89a9cfd64b1.EntryRendererBase_1.n_onTextChanged(java.lang.CharSequence, int, int, int)
这个问题本质是Android端EditText的内部文本处理冲突,咱们一步步来分析解决:
问题根源
当你在TextChanged事件里直接修改Entry的Text属性时,系统此时还在处理原文本的Span(用于光标定位、文本选中标记等)。尤其是粘贴操作会一次性替换大量文本,原文本的索引位置和新文本的长度不匹配,导致系统在更新Span时使用了旧的索引值,超出了新文本的有效长度范围,最终抛出这个越界异常。
可行解决方案
方案1:延迟执行格式化,避开同步冲突
不要在TextChanged事件里直接修改文本,而是用Dispatcher延迟执行格式化逻辑,让系统先完成当前的文本更新流程:
private void Entry_TextChanged(object sender, TextChangedEventArgs e) { var entry = sender as Entry; if (entry == null) return; // 避免重复触发,先判断新旧文本是否一致 if (e.NewTextValue == e.OldTextValue) return; // 用Dispatcher延迟执行,让系统先处理完内部文本更新 Dispatcher.Dispatch(async () => { await Task.Delay(10); // 短延迟确保系统完成处理 var formattedText = FormatPhoneNumber(entry.Text); // 再次判断,防止循环触发TextChanged if (entry.Text != formattedText) { entry.Text = formattedText; // 将光标移到文本末尾,提升用户体验 entry.CursorPosition = formattedText.Length; } }); } // 你的手机号格式化方法示例 private string FormatPhoneNumber(string rawText) { // 先过滤非数字和+的无效字符 var cleanText = new string(rawText.Where(c => char.IsDigit(c) || c == '+').ToArray()); if (string.IsNullOrEmpty(cleanText)) return ""; if (cleanText.StartsWith("+91")) { // +91 12345 12345格式处理 if (cleanText.Length > 3) { var part1 = cleanText.Substring(0, 3); var part2 = cleanText.Length > 8 ? cleanText.Substring(3, 5) : cleanText.Substring(3); var part3 = cleanText.Length > 8 ? cleanText.Substring(8) : ""; return $"{part1} {part2} {part3}".Trim(); } return cleanText; } else if (cleanText.StartsWith("+1")) { // +1 123 123 1234格式处理 if (cleanText.Length > 2) { var part1 = cleanText.Substring(0, 2); var part2 = cleanText.Length > 5 ? cleanText.Substring(2, 3) : cleanText.Substring(2); var part3 = cleanText.Length > 8 ? cleanText.Substring(5, 3) : ""; var part4 = cleanText.Length > 8 ? cleanText.Substring(8) : ""; return $"{part1} {part2} {part3} {part4}".Trim(); } return cleanText; } else { // 123 123 1234格式处理 if (cleanText.Length > 3) { var part1 = cleanText.Substring(0, 3); var part2 = cleanText.Length > 6 ? cleanText.Substring(3, 3) : cleanText.Substring(3); var part3 = cleanText.Length > 6 ? cleanText.Substring(6) : ""; return $"{part1} {part2} {part3}".Trim(); } return cleanText; } }
方案2:自定义Android Renderer,底层控制文本更新
如果方案1的延迟仍有问题,可以直接自定义Android端的Renderer,绕过MAUI的TextChanged事件,在EditText的TextWatcher里直接处理,从底层避免冲突:
[assembly: ExportRenderer(typeof(Entry), typeof(CustomPhoneEntryRenderer))] namespace YourAppNamespace.Platforms.Android { public class CustomPhoneEntryRenderer : EntryRenderer { private TextWatcher _textWatcher; protected override void OnElementChanged(ElementChangedEventArgs<Entry> e) { base.OnElementChanged(e); if (Control != null) { // 移除默认的TextWatcher,避免冲突 if (_textWatcher != null) { Control.RemoveTextChangedListener(_textWatcher); } _textWatcher = new PhoneTextWatcher(Element); Control.AddTextChangedListener(_textWatcher); // 禁止系统文本自动纠正,避免干扰格式化 Control.InputType = Android.Text.InputTypes.ClassPhone | Android.Text.InputTypes.TextFlagNoSuggestions; } } protected override void Dispose(bool disposing) { if (Control != null && _textWatcher != null) { Control.RemoveTextChangedListener(_textWatcher); } base.Dispose(disposing); } private class PhoneTextWatcher : Java.Lang.Object, ITextWatcher { private readonly Entry _entry; private bool _isUpdating; public PhoneTextWatcher(Entry entry) { _entry = entry; } public void AfterTextChanged(IEditable s) { if (_isUpdating) return; _isUpdating = true; var rawText = s.ToString(); var formattedText = FormatPhoneNumber(rawText); // 直接修改Editable,避免触发额外的TextChanged事件 s.Replace(0, s.Length(), formattedText); _isUpdating = false; } public void BeforeTextChanged(ICharSequence s, int start, int count, int after) { // 无需处理 } public void OnTextChanged(ICharSequence s, int start, int before, int count) { // 无需处理 } // 复用上面的FormatPhoneNumber方法,此处省略重复代码 private string FormatPhoneNumber(string rawText) { var cleanText = new string(rawText.Where(c => char.IsDigit(c) || c == '+').ToArray()); if (string.IsNullOrEmpty(cleanText)) return ""; if (cleanText.StartsWith("+91")) { if (cleanText.Length > 3) { var part1 = cleanText.Substring(0, 3); var part2 = cleanText.Length > 8 ? cleanText.Substring(3, 5) : cleanText.Substring(3); var part3 = cleanText.Length > 8 ? cleanText.Substring(8) : ""; return $"{part1} {part2} {part3}".Trim(); } return cleanText; } // 其他格式处理逻辑... return cleanText; } } } }
关键注意点
- 避免循环触发:一定要判断格式化后的文本和当前文本是否一致,否则会无限触发
TextChanged事件 - 光标位置优化:修改文本后记得将光标移到末尾,否则用户输入体验会很差
- 过滤无效字符:格式化前先过滤掉非数字和+的字符,避免无效文本导致的索引计算错误
备注:内容来源于stack exchange,提问作者Matthew Pans

