TextView在android4.1和4.1.1上报ArrayIndexOutOfBoundsException的分析

本文探讨了一个在Android设备上显示表情输入时遇到的ArrayIndexOutOfBoundsException异常,特别是针对4.1和4.1.1版本。通过分析异常信息,发现异常发生在文本布局生成过程中,涉及到表情符号的换行处理。文章提出了一个解决方案,即在TextView的onMeasure方法中进行异常捕获,以避免崩溃。此外,提供了检测和工作绕过异常的方法,以及一些工作绕过的实现思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

很久以前做的表情输入及显示,用的系统的SpannableString,以前都好端端的没问题,最近突然报出个棘手的bug,在4.1和4.1.1的手机上显示某位用户的评论时,程序直接挂掉,

异常信息:

 

9-17 16:38:27.429: E/AndroidRuntime(10425): FATAL EXCEPTION: main
09-17 16:38:27.429: E/AndroidRuntime(10425): java.lang.ArrayIndexOutOfBoundsException: length=116; index=125
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.text.MeasuredText.addStyleRun(MeasuredText.java:168)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.text.MeasuredText.addStyleRun(MeasuredText.java:204)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.text.StaticLayout.generate(StaticLayout.java:297)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.text.StaticLayout.<init>(StaticLayout.java:156)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.text.StaticLayout.<init>(StaticLayout.java:96)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.text.StaticLayout.<init>(StaticLayout.java:75)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.TextView.makeSingleLayout(TextView.java:5942)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.TextView.makeNewLayout(TextView.java:5782)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.TextView.onMeasure(TextView.java:6139)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.measure(View.java:15264)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4918)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1390)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.measureVertical(LinearLayout.java:681)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.measure(View.java:15264)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4918)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.measure(View.java:15264)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.ListView.setupChild(ListView.java:1893)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.ListView.makeAndAddView(ListView.java:1803)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.ListView.fillDown(ListView.java:681)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.ListView.fillFromTop(ListView.java:742)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.ListView.layoutChildren(ListView.java:1629)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.AbsListView.onLayout(AbsListView.java:2224)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.layout(View.java:13846)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.ViewGroup.layout(ViewGroup.java:4466)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1649)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1507)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.onLayout(LinearLayout.java:1420)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.layout(View.java:13846)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.ViewGroup.layout(ViewGroup.java:4466)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1649)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1507)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.onLayout(LinearLayout.java:1420)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.layout(View.java:13846)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.ViewGroup.layout(ViewGroup.java:4466)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.layout(View.java:13846)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.ViewGroup.layout(ViewGroup.java:4466)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1649)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1507)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.onLayout(LinearLayout.java:1420)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.layout(View.java:13846)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.ViewGroup.layout(ViewGroup.java:4466)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at com.component.SinglePreviewContainer.onLayout(SinglePreviewContainer.java:124)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.layout(View.java:13846)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.ViewGroup.layout(ViewGroup.java:4466)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1649)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1507)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.onLayout(LinearLayout.java:1420)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.layout(View.java:13846)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.ViewGroup.layout(ViewGroup.java:4466)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1649)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1638)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.onLayout(LinearLayout.java:1422)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.layout(View.java:13846)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.ViewGroup.layout(ViewGroup.java:4466)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.RelativeLayout.onLayout(RelativeLayout.java:948)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.layout(View.java:13846)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.ViewGroup.layout(ViewGroup.java:4466)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.View.layout(View.java:13846)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.view.ViewGroup.layout(ViewGroup.java:4466)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1649)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1507)
09-17 16:38:27.429: E/AndroidRuntime(10425): 	at android.widget.Linear

  原因:当TextView显示的表情恰好要被换行符截断的时候,会报异常(目前只在4.1和4.1.1上出现)

 

  

 解决方式:

重写TextView 在onMeasure()中捕获异常

 

public class PatchedTextView extends TextView {
	public PatchedTextView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}
	public PatchedTextView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	public PatchedTextView(Context context) {
		super(context);
	}
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		try{
			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		}catch (ArrayIndexOutOfBoundsException e){
			setText(getText().toString());
			super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
		}
	}	
	@Override
	public void setGravity(int gravity){
		try{
			super.setGravity(gravity);
		}catch (ArrayIndexOutOfBoundsException e){
			setText(getText().toString());
			super.setGravity(gravity); 
		}
	}
	@Override
	public void setText(CharSequence text, BufferType type) {
		try{
			super.setText(text, type);
		}catch (ArrayIndexOutOfBoundsException e){
			setText(text.toString());
		}
	}
}

 

 

  引用牛人分析:

 

Here's some more details on the bug and how to work around it.  I've attempted accuracy but please call out any mistakes.

Crash Manifestation:
When Android is rendering long lines of text to the screen, it needs to figure out where to perform a line-wrap.  A crash will occur if all of the following are true:
1.  The text contains spans that are MetricAffectingSpans (ex: StyleSpan is one such subclass).  Other types of spans do not invoke the crash (ex: URLSpan).
2.  There is a word where a MetricAffectingSpans starts or stops in the middle of that word (ex: the first half of a word is bolded while the second half is not).
3.  A line-wrap is needed in the non-spanned part of the word.
4.  The code is running on Android 4.1 or 4.1.1.

What makes this bug show up on some devices and not others depends on several factors such as screen pixel dimensions, dpi, font size, the text and spans themselves, and others.

This shows up both in custom layouts and in simple dialogs (ex: dialog's built by AlertDialogBuilder).

So, what exactly is meant by a "word" in this case?  A word is a sequence of characters that can not be split across a line-wrap.  So then, where can line-wraps occurs?  It has already been pointed out that spaces allow line-wraps to occur.  However, there are other characters that also allow line-wraps.
1.  Spaces, Tabs, and Newline characters always allow line-wraps (i.e. ' ', '\t', and '\n').
2.  The characters '.' ',' ';' and ':' allow line-wraps IF there is not a digit immedidately before or after them  (as defined by Character.isDigit()).  When these 4 characters separate two words, these characters are considered part of the first of the two words.
3.  The characters '/' and '-' allow line-wraps IF there is not a digit immediately after them (as defined by Character.isDigit()).  As above, when these 2 characters separate two words, these characters are considered part of the first of the two words.
4.  Ideographs allow line-wraps if they are adjacent, except for non-starters while only wrap after the non-starter.  See Android's 4.1.1 StaticLayout.java file for details.

Here are some example strings that were put through textView.setText( Html.fromHtml(TEXT) ):
"hello <b>world</b>"         Safe
"hello.<b>world</b>"         Safe
"hello.<b>1orld</b>"         May crash
"hell7.<b>world</b>"         May crash
"hello-<b>world</b>"         Safe
"hello-<b>1orld</b>"         May crash
"<b>hello</b> world"         Safe
"<b>hello.</b>world"         Safe
"<b>hello</b>.world"         May crash
"<b>hell7.</b>world"         May crash
"<b>hello.</b>1orld"         May crash
"<b>hello-</b>world"         Safe
"<b>hello</b>-world"         May crash
"<b>hell7-</b>world"         Safe
"<b>hello-</b>1orld"         May crash

The crashes are limited to any span that is or derived from MetricAffectingSpans.  This means that the following spans are SAFE to use because they are not derived from MetricAffectingSpans:
MaskFilterSpan, RasterizerSpan, clickableSpan, URLSpan, BackgroundColorSpan, ForegroundColorSpan, StrikethroughSpan, SuggestionSpan, UnderlineSpan

There are two ways for detecting if the crash can even occur:
1.  Look for Build.VERSION to be equal to "4.1" or "4.1.1".  This check is easy, but your code might implement a work around on such device's where the crash wouldn't show up (ex: screen size meant that line-wraps didn't occur in any of the bad places).
2.  Wrap the call to setText() for the View with a try{ ... } catch( IndexOutOfBoundsException e ){ ... }.  Do this either around the call to setText or create a subclass that overrides setText() and calls super.setText().

Work-arounds if detected (from simplist to complex):
0.  If you have complete control over the text, re-write the text to fit the "characters that allow line-wraps" rules listed above.
1.  Remove all the spans from the text (ex: text.toString()).  This is easy but it removes ALL spans.
2.  Remove just the MetricAffectingSpans.  Call text.getSpans(0, text.length(), MetricAffectingSpans.class) and then text.removeSpan() for each one returned.
3.  Find all the MetricAffectingSpans and detect whether each span has a space/tab/newline before and after them.  If not, then insert a space/tab/newline before and/or after each span.
4.  Find all the MetricAffectingSpans and do the following:
        If the span is a StyleSpan for bold, replace the span with a MaskFilterSpan( 
            new BlurMaskFilter((float) 0.5, BlurMaskFilter.Blur.SOLID ) )
        If the span is a StyleSpan for italic, make the so-so replacement with ForegroundColorSpan(0xFF808080)
        If the span is something else, remove the span entirely.
5.  Find all the MetricAffectingSpans and detect whether each span allows a line-wrap immediately before and after them according to the "characters that allow line-wraps" rules listed above.  Modify the string accordingly.
6.  Other work-arounds are possible.

   

链接 https://code.google.com/p/android/issues/detail?id=35466

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值