Memory of a TraceView practice

去年二月项目中有一个跳转到一个页面,如果这个页面超过200个表情,打开页面会变得非常卡(加载2~4s才能打开)的一个问题。
这套逻辑是很久之前的Legacy code,不知道经过了几个人手...但直到现在才发现问题...
Whatever,问题出现了肯定要解决,先分析这段Legacy code的具体逻辑...
加载表情步骤是
1.使用以下正则来匹配String中表情。

String string = "\\[(.+?)\\]";

2.将表情匹配出来后,根据匹配后字符找到id,使用id在项目中自带的表情resource中查找到具体表情的png。

3.使用SpannableString,采用ImageSpan将这些png依次嵌入SpannableString文本中。
4.setText(SpannableString)来展示。
一开始以为是接口请求较慢问题,后来监测发现不管多少个表情,接口Http Req/Resp都基本稳定在50ms以内。
看来只能是项目中代码的问题。
由于项目中整个加载解析逻辑不仅仅只有表情,还有解析股票代码,链接,@某人的跳转等等。暂时无法从庞杂的代码中分析出到底那处占用了时间。
遂想到使用TraceView来分析到底哪个方法执行过长。

第一次优化

第一次TraceView发现,是正则的一个matcher方法在表情多的(200+)时候,执行了上w次。正是这个方法的总计incl cpu(?)执行时间超过了3000ms。

那必然是正则匹配里的处理逻辑有问题。查看了一下代码,在匹配时有多处正则匹配逻辑,重点Debug 对表情的正则匹配逻辑。

private static void dealSpannableString(Context context, SpannableString spannableString, Pattern patten, int start) {
        Matcher matcher = patten.matcher(spannableString);
        while (matcher.find()) {
            String key = matcher.group();
            if (matcher.start() < start) {
                continue;
            }
            int id = Expressions.getIdAsName(key);
            if (id != 0) {
                Drawable drawable = context.getResources().getDrawable(id);
//                drawable.setBounds(0, 0, UIUtils.Dp2Px(context, 30),
//                        UIUtils.Dp2Px(context, 30));
                drawable.setBounds(0, 0, drawable.getIntrinsicWidth() * 3 / 5, drawable.getIntrinsicHeight() * 3 / 5);
//                ImageSpan imgSpan = new ImageSpan(drawable);
                int end = matcher.start() + key.length();
//                spannableString.setSpan(imgSpan, matcher.start(), end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                spannableString.setSpan(new VerticalImageSpan(drawable), matcher.start(), end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                if (end < spannableString.length()) {                        //如果整个字符串还未验证完,则继续。。
                    dealSpannableString(context, spannableString, patten, end);
                }
                break;
            }
        }
    }

跟踪代码,才发现解析这里使用了递归。dealSpannableString,又调用了自身。

然而这个递归是毫无必要的,200个表情在第一次递归时,递归剩下的199,第二次递归剩下的198...
直接导致解析N个表情所需的正则匹配次数为(n+1)*n/2。然而其实真正需要匹配的次数应该只是n。所以N=200时,其实方法执行次数上扩大了100倍,总计需要3000+ms,其实应该只需要30ms左右。
去掉递归之后:

while (matcher.find()) {
            String key = matcher.group();
            int id = Expressions.getIdAsName(key);
            if (id != 0) {
                Drawable drawable = context.getResources().getDrawable(id);
                drawable.setBounds(0, 0, drawable.getIntrinsicWidth() * 3 / 5, drawable.getIntrinsicHeight() * 3 / 5);
                ImageSpan imgSpan = new CenterVerticalEmojiExpressionImageSpan(drawable);
                spannableString.setSpan(imgSpan, matcher.start(),
                        matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }

运行,发现确实打开页面速度得到了明显提升。
但是在反复切换打开页面时,感觉似乎还是有一点迟钝,虽然没有3~5秒这么夸张,但是有时会卡顿1秒钟左右。

第二次优化

找到一个打开延迟有1秒左右的页面进行测试。这个页面文本内容比较丰富,有表情,有需要解析跳转的股票代码和链接,还有@某人的标记。

找不到头绪,依然采用TraceView。
看了看这1秒内到底又是哪些个方法执行最多。

看了数据发现... 竟然是setText()。

Debug之~

终于发现原来在for循环匹配股票代码,链接,@标识的时候,setText写到了for循环体内,而且是每块解析的for循环体内,导致这些数据一多,setText执行了非常多次。。。

而setText其实还算是一个比较重量级的方法,里面还调用了

requestLayout();
invalidate();

等等。
修改为将在for循环体内将spannableString拼接起来,在for循环外只进行一次setText。
运行之... hmmmm ... 页面跳转变得非常流畅,感觉不到迟钝了。

总结

性能优化还是要熟练掌握工具的使用哇~ 一开始看TraceView的分析结果,有点不敢相信,总感觉是不是测错了。一个matcher方法,一个setText方法,怎么可能呢?

仔细分析发现还真的是这两个方法。看来数据真的是不会撒谎。(TraceView的结果图就不补了,仅仅是对去年二月的优化做次回顾,懒得重新复现上图了)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343