这周印度那边的同事在钉钉群里反馈说,有主播的APP会在某个用户进直播间发了一串神秘消息后奔溃,而且这个用户知道这个方法就一直捣乱,管理员将这些用户的号封了,他有注册新的账号继续搞,扰乱直播秩序。-_-||
顺便说一下,笔者目前在一家搞直播的公司,主要是海外业务如印度、东南亚、日本等国家以及中国台湾省,国内业务也有,但营收主要还是来自以上国家和地区。
看到群里反馈的消息后,我的第一反应是~~黑人脸,纳尼?还有这种事?操作方式不对吧?或者将手机朝向东方试试?心里这么想,但问题还是得解决。他们那边提供了主播ID和用户ID,我就上Fabric上去查了一下,发现这个主播在他们提供的时间节点附近是有奔溃,日志如下:
Fatal Exception: java.lang.IndexOutOfBoundsException: measureLimit (32) is out of start (36) and limit (32) bounds at android.text.TextLine.handleRun + 1113(TextLine.java:1113) at android.text.TextLine.drawRun + 509(TextLine.java:509) at android.text.TextLine.draw + 280(TextLine.java:280) at android.text.Layout.drawText + 581(Layout.java:581) at android.text.Layout.draw + 333(Layout.java:333) at android.widget.TextView.onDraw + 8108(TextView.java:8108) at android.view.View.draw + 21870(View.java:21870) at android.view.View.updateDisplayListIfDirty + 20743(View.java:20743) at android.view.View.draw + 21596(View.java:21596) at android.view.ViewGroup.drawChild + 4558(ViewGroup.java:4558) at android.view.ViewGroup.dispatchDraw + 4333(ViewGroup.java:4333) at android.view.View.draw + 21873(View.java:21873) at android.view.View.updateDisplayListIfDirty + 20743(View.java:20743) at android.view.View.draw + 21596(View.java:21596) at android.view.ViewGroup.drawChild + 4558(ViewGroup.java:4558) at android.view.ViewGroup.dispatchDraw + 4333(ViewGroup.java:4333) at android.view.View.updateDisplayListIfDirty + 20729(View.java:20729) at android.view.View.draw + 21596(View.java:21596) at android.view.ViewGroup.drawChild + 4558(ViewGroup.java:4558) at android.support.v7.widget.RecyclerView.drawChild + 4703(RecyclerView.java:4703) at android.view.ViewGroup.dispatchDraw + 4333(ViewGroup.java:4333) at android.view.View.draw + 21873(View.java:21873) at android.support.v7.widget.RecyclerView.draw + 4107(RecyclerView.java:4107) at android.view.View.updateDisplayListIfDirty + 20743(View.java:20743) at android.view.View.draw + 21596(View.java:21596) at android.view.ViewGroup.drawChild + 4558(ViewGroup.java:4558) at android.view.ViewGroup.dispatchDraw + 4333(ViewGroup.java:4333) at android.view.View.updateDisplayListIfDirty + 20729(View.java:20729) at android.view.View.draw + 21596(View.java:21596) at android.view.ViewGroup.drawChild + 4558(ViewGroup.java:4558) at android.view.ViewGroup.dispatchDraw + 4333(ViewGroup.java:4333) at android.view.View.updateDisplayListIfDirty + 20729(View.java:20729) at android.view.ViewGroup.recreateChildDisplayList + 4542(ViewGroup.java:4542) at android.view.ViewGroup.dispatchGetDisplayList + 4514(ViewGroup.java:4514) at android.view.View.updateDisplayListIfDirty + 20698(View.java:20698) at android.view.ViewGroup.recreateChildDisplayList + 4542(ViewGroup.java:4542) at android.view.ViewGroup.dispatchGetDisplayList + 4514(ViewGroup.java:4514) at android.view.View.updateDisplayListIfDirty + 20698(View.java:20698) at android.view.ViewGroup.recreateChildDisplayList + 4542(ViewGroup.java:4542) at android.view.ViewGroup.dispatchGetDisplayList + 4514(ViewGroup.java:4514) at android.view.View.updateDisplayListIfDirty + 20698(View.java:20698) at android.view.ViewGroup.recreateChildDisplayList + 4542(ViewGroup.java:4542) at android.view.ViewGroup.dispatchGetDisplayList + 4514(ViewGroup.java:4514) at android.view.View.updateDisplayListIfDirty + 20698(View.java:20698) at android.view.ViewGroup.recreateChildDisplayList + 4542(ViewGroup.java:4542) at android.view.ViewGroup.dispatchGetDisplayList + 4514(ViewGroup.java:4514) at android.view.View.updateDisplayListIfDirty + 20698(View.java:20698) at android.view.ViewGroup.recreateChildDisplayList + 4542(ViewGroup.java:4542) at android.view.ViewGroup.dispatchGetDisplayList + 4514(ViewGroup.java:4514) at android.view.View.updateDisplayListIfDirty + 20698(View.java:20698) at android.view.ViewGroup.recreateChildDisplayList + 4542(ViewGroup.java:4542) at android.view.ViewGroup.dispatchGetDisplayList + 4514(ViewGroup.java:4514) at android.view.View.updateDisplayListIfDirty + 20698(View.java:20698) at android.view.ViewGroup.recreateChildDisplayList + 4542(ViewGroup.java:4542) at android.view.ViewGroup.dispatchGetDisplayList + 4514(ViewGroup.java:4514) at android.view.View.updateDisplayListIfDirty + 20698(View.java:20698) at android.view.ViewGroup.recreateChildDisplayList + 4542(ViewGroup.java:4542) at android.view.ViewGroup.dispatchGetDisplayList + 4514(ViewGroup.java:4514) at android.view.View.updateDisplayListIfDirty + 20698(View.java:20698) at android.view.ViewGroup.recreateChildDisplayList + 4542(ViewGroup.java:4542) at android.view.ViewGroup.dispatchGetDisplayList + 4514(ViewGroup.java:4514) at android.view.View.updateDisplayListIfDirty + 20698(View.java:20698) at android.view.ViewGroup.recreateChildDisplayList + 4542(ViewGroup.java:4542) at android.view.ViewGroup.dispatchGetDisplayList + 4514(ViewGroup.java:4514) at android.view.View.updateDisplayListIfDirty + 20698(View.java:20698) at android.view.ThreadedRenderer.updateViewTreeDisplayList + 725(ThreadedRenderer.java:725) at android.view.ThreadedRenderer.updateRootDisplayList + 731(ThreadedRenderer.java:731) at android.view.ThreadedRenderer.draw + 840(ThreadedRenderer.java:840) at android.view.ViewRootImpl.draw + 3981(ViewRootImpl.java:3981) at android.view.ViewRootImpl.performDraw + 3755(ViewRootImpl.java:3755) at android.view.ViewRootImpl.performTraversals + 3064(ViewRootImpl.java:3064) at android.view.ViewRootImpl.doTraversal + 1927(ViewRootImpl.java:1927) at android.view.ViewRootImpl$TraversalRunnable.run + 8558(ViewRootImpl.java:8558) at android.view.Choreographer$CallbackRecord.run + 949(Choreographer.java:949) at android.view.Choreographer.doCallbacks + 761(Choreographer.java:761) at android.view.Choreographer.doFrame + 696(Choreographer.java:696) at android.view.Choreographer$FrameDisplayEventReceiver.run + 935(Choreographer.java:935) at android.os.Handler.handleCallback + 873(Handler.java:873) at android.os.Handler.dispatchMessage + 99(Handler.java:99) at android.os.Looper.loop + 214(Looper.java:214) at android.app.ActivityThread.main + 7094(ActivityThread.java:7094) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run + 494(RuntimeInit.java:494) at com.android.internal.os.ZygoteInit.main + 975(ZygoteInit.java:975)
妈耶!奔溃还挺多!
emmmmm……这该如何是好,大家应该可以看出来,日志里全是系统API的调用栈,根本就看不出来是应用程序的那里报了错,这就很难查了。我也顺着报错的系统源代码去查,也看不出来那里出了问题,TextView相关的系统代码太长,分析起来费时费力。
与此同时,我也让server端查了一下直播间日志,看看这个用户到底发了什么玩意,我们也试试能不能复现。一段时间之后,数据出来了,不出所料,这个用户用他的几个账号,不断得在直播间发一串“神秘”的代码:
_ヽ \\ Λ_Λ \( 'ㅅ' ) > ⌒ヽ / へ\ / / \\ レ ノ ヽ_つ / / / /| ( (ヽ _ヽ \\ Λ_Λ \( 'ㅅ' ) > ⌒ヽ / へ\ / / \\ レ ノ ヽ_つ / / / /| ( (ヽ ⊂_ヽ \\ Λ_Λ \( 'ㅅ' ) > ⌒ヽ / へ\ / / \\ レ ノ ヽ_つ / / / /| ( (ヽ ⊂_ヽ \\ Λ_Λ \( 'ㅅ' ) > ⌒ヽ / へ\ / / \\ レ ノ ヽ_つ / / / /| ( (ヽ ⊂_ヽ \\ Λ_Λ \( 'ㅅ' ) > ⌒ヽ / へ\ /
我们欣喜若狂,以为这就能复现问题了。但让人没想到的是,将这段神秘的字符串发送到直播间后,并没有出现任何异常。
我一边绝望着,一边把这个用户注册的所有子账号都关注了,看能不能找到什么共同点:
等等!我好想发现了什么,昵称你面几乎都包含一个字符:ء这是什么鬼?管它呢,我也把昵称改成这个试试。如我所愿,改成这个字符之后,进直播间发那一串什么代码,程序果然奔溃了。-_-||
在我进行昵称编辑的时候,发现这个字符是显示在输入框最右侧的,而且再输入内容都会显示到它的左边。原来是阿拉伯字母,Right-To-Left,真相终于水落石出了。
查看APP代码后发现,聊天消息和昵称以及其他一些徽章图片等都是在同一个textview里面,通过spannable来进行排版的。阿拉伯字母的出现打乱了spannable顺序,引起了这次事故。(正常在TextView显示阿拉伯字母和那串神秘字符串,是不会出问题的,除非用了一些spannable)。
找到原因了就解决吧,我的思路是强制将阿拉伯字母也以LTR(left to right)的方式排版(或者强制以本地语言的书写方式进行排版),因为我们目前没有做中东版本,没有未阿拉伯语系的用户提供服务,所以这样还是可以接受的。(这样做是有风险的,操作需谨慎)
<stylename="ForceLocalDirection"> <itemname="android:textDirection">locale</item> <itemname="android:textAlignment">gravity</item> </style>
附:
阿拉伯字母表
如何是rtl如何支持?我们目前也遇到类似的问题,bugly后台也是一堆的奔溃日志,这是一个系统的bug,但是目前app要支持阿语环境,请问有解决方案吗?
如果要全面支持RTL的话,layout也得是RTL的。
1.先傻瓜式地将所有资源文件转换为RTL支持的。
在工程上点击右键–>Refactor–>Add Right-To-Left(RTL) Support…
执行此操作后,res目录下面的资源文件(如布局文件)中的paddingLeft/paddingRight/marginLeft/marginRight会转换为paddingStart/paddingEnd/marginStart/marginEnd。
2.检查所有硬编码的关于布局的调整,主要也是上面提到的paddingLeft等,还有drawableLeft/drawableRight等,需要手动修改。
如果不需要将布局做成RTL的,只是想RTL LTR混排的话,直接强制指定android:textDirection即可。