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

Android Firebase上报Triggered ANR Root Blocking问题排查

Android Firebase 标记的ANR问题修复

问题背景

开发Android应用过程中出现触发ANR的异常,Crashlytics归类标签为Triggered ANR、Root Blocking,多次尝试修复未解决,以下是Crashlytics采集的崩溃堆栈及关联业务代码。

崩溃堆栈

main (runnable): tid=1 systid=32748
#00 pc 0x47a6ec libart.so
#01 pc 0x57f510 libart.so
#02 pc 0x59c35c libart.so
#03 pc 0x5801ec libart.so
#04 pc 0x61f318 libart.so
at dalvik.system.VMRuntime.newUnpaddedArray(VMRuntime.java)
at com.android.internal.util.ArrayUtils.newUnpaddedIntArray(ArrayUtils.java:61)
at android.text.AutoGrowArray$IntArray.ensureCapacity(AutoGrowArray.java:210)
at android.text.AutoGrowArray$IntArray.append(AutoGrowArray.java:199)
at android.text.MeasuredParagraph.applyMetricsAffectingSpan(MeasuredParagraph.java:612)
at android.text.MeasuredParagraph.buildForStaticLayout(MeasuredParagraph.java:427)
at android.text.PrecomputedText.createMeasuredParagraphs(PrecomputedText.java:436)
at android.text.StaticLayout.generate(StaticLayout.java:683)
at android.text.StaticLayout.(StaticLayout.java:591)
at android.text.StaticLayout.(StaticLayout.java:49)
at android.text.StaticLayout$Builder.build(StaticLayout.java:416)
at android.widget.TextView.makeSingleLayout(TextView.java:9311)
at android.widget.TextView.makeNewLayout(TextView.java:9130)
at android.widget.TextView.checkForRelayout(TextView.java:9817)
at android.widget.TextView.setText(TextView.java:6403)
at android.widget.TextView.setText(TextView.java:6218)
at android.widget.TextView.setText(TextView.java:6170)
at com.gobillion.android.commerce.firebase.FirebaseRealtimeDBSetup$1$1$1.run(FirebaseRealtimeDBSetup.java)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:257)
at android.app.ActivityThread.main(ActivityThread.java:8183)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:626)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1015)

关联代码

主逻辑代码:

try {  
    Handler handler = new Handler();
    new Thread(new Runnable() {
        @Override
        public void run() {
            List<Data> dataList = new ArrayList<>();
            for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()) {
                if (dataSnapshot1.hasChildren()) {
                    Data data = dataSnapshot1.getValue(UserMessageData.class);
                    dataList.add(data);
                }
            }
            final Spannable messageString = convertIntoSpannable(context, dataList);
            handler.post(new Runnable() {
                @Override
                public void run() {
                    if (ObjectUtil.isNotNull(messageString) && messageString.length() > 0) {
                        parent.setVisibility(View.VISIBLE);
                        tickertextView.setText(messageString);
                    } else {
                        parent.setVisibility(View.GONE);
                    }
                }
            });
        }
    }).start();
} catch (Exception e) {
    e.printStackTrace();
    parent.setVisibility(View.GONE);
}

方法1:Spannable拼接逻辑

public static SpannableStringBuilder convertIntoSpannable(Context context, List<Data> dataList){
    SpannableStringBuilder messageSpannable = new SpannableStringBuilder();
    if(!dataList.isEmpty()) {
        for(Data data: dataList){
            if(ObjectUtil.isNotNullOrNotEmpty(data.message))
                messageSpannable.append(TextViewUtils.setBulletString(data.message,context)) ;
        }
    }
    return messageSpannable;
}

方法2:Html转Spanned逻辑

public static  Spanned setBulletString(String message, Context context) {
    return Html.fromHtml(String.format(
            context.getString(R.string.dot_data) + message + "\t\t"), new Html.ImageGetter() {
        @Override
        public Drawable getDrawable(String source) {
            Drawable d = ResourcesCompat.getDrawable(context.getResources(), R.drawable.green_dot, null);
            d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
            return d;
        }
    }, null);
}

根因定位

  • 主线程阻塞完全发生在tickertextView.setText()触发的文本排版流程:当传入的Spannable文本过长、携带的Span对象过多时,StaticLayout需要逐段做文本度量、Span效果计算,数组扩容、段落测量的耗时随文本长度线性增长,一旦主线程阻塞超过5秒就会触发ANR。
  • 现有实现存在明显性能浪费:逐段调用Html.fromHtml()解析生成Span,每处理一条消息就会重复执行Html语法解析、Drawable加载逻辑,生成的Spannable携带大量冗余对象,进一步拉长了排版耗时。
  • 现有子线程处理只做了数据拉取和Spannable拼接,最耗时的文本测量、布局计算全量压在主线程执行,没有真正分担主线程压力。另外在子线程创建绑定UI资源的Drawable、Span对象本身也存在线程安全隐患。

修复方案

  • 控制单次渲染的文本长度:不要把拉取到的所有消息无截断塞给TextView,单次渲染的消息条数控制在20条以内、总字符数不超过1000,多余内容做分段加载或者折叠展示,从源头减少排版计算量。
  • 替换高耗时的Html解析实现:复用Drawable资源,手动拼接Span,避免重复解析、重复创建资源带来的开销,参考实现:
// 全局复用绿点Drawable,避免每条消息重复加载
private static Drawable sGreenDotCache;
public static SpannableStringBuilder convertIntoSpannable(Context context, List<Data> dataList){
    SpannableStringBuilder messageSpannable = new SpannableStringBuilder();
    if (sGreenDotCache == null) {
        sGreenDotCache = ResourcesCompat.getDrawable(context.getResources(), R.drawable.green_dot, null);
        sGreenDotCache.setBounds(0, 0, sGreenDotCache.getIntrinsicWidth(), sGreenDotCache.getIntrinsicHeight());
    }
    if(!dataList.isEmpty()) {
        int renderCount = Math.min(dataList.size(), 20);
        for (int i = 0; i < renderCount; i++) {
            Data data = dataList.get(i);
            if(ObjectUtil.isNotNullOrNotEmpty(data.message)) {
                String content = "• " + data.message + "\t\t";
                SpannableString spanStr = new SpannableString(content);
                spanStr.setSpan(new ImageSpan(sGreenDotCache), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                messageSpannable.append(spanStr);
            }
        }
    }
    return messageSpannable;
}
  • 用PrecomputedText把文本测量逻辑移到子线程:在子线程生成Spannable后,匹配目标TextView的字体大小、行高、宽度等参数完成文本预计算,再把预计算结果传给主线程setText,直接分担主线程的排版耗时。
  • 优化TextView配置:如果是跑马灯组件,关闭不必要的字体内边距、自动换行等配置,非必要不使用复杂Span效果,进一步降低排版计算复杂度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 18:36:51