一、背景
Android Bug Fix系列主要是想记录在日常开发中遇到的一些奇奇怪怪的Bug,上一篇聊了线程使用不当引入的泄漏的问题。指路地址:【Android Bug Fix】Thread使用不当导致的泄漏问题。
笔者在最近开发中,再次遇到了一个有意思的Bug,在此记录一下。
二、问题发现 & 解决流程
1、Bug描述
某日开始陆续接到几个用户的反馈,其在操作App时出现了一些UI异常,不同的用户之间还存在一些差别,大致表现如下:
- UI异常,具体表现是在RecyclerView中Item出现了重叠的情况
- 列表卡顿,原本存在的下拉刷新功能不可用
- 点击按钮没有相应
- 等等
2、排查过程
接收到反馈之后,立即开始着手排查,但是说实话开始时完全没有任何头绪。
因为UI异常乃至于一些按钮点击没有任何响应,这个在过往开发中还没有遇到过,直接超纲了。所以最开始入手只能先归纳当前出现的问题的规律,看看能不能找到一些相同点来入手。
Bug出现时机? Bug出现的时机是什么?进一步与用户沟通,发现每个用户反馈出现问题的时机都不同。
- 有说应用从后台切前台就出现了
- 有说用着用着就出现没有规律
- 有说是清理缓存功能之后出现的
用户手机厂商? 怀疑是不是仅在某个厂商品牌上出现问题,结果排查下来发现啥品牌都有,不是具体哪个品牌的锅。
用户使用的App版本? 调查下来发现用户使用的App都是最新版本。
那么至此有理由怀疑问题是新版本引入的,但是新版本是由多个需求分支合入的,具体是哪个需求导致的仅通过看需求描述是看不出来的,每个需求看着都是浓眉大眼的不像坏人。依次将工程切到新版本中每一个需求分支测试也没有复现用户的情况。
好在不负有心人,同事在体验新版本时突然复现了用户的场景,复盘出现Bug前所做的操作,发现应该是和某个需求弹出的弹框有关。 至此确认了UI展示异常是由于App弹出的浮窗导致的。
3、解决Bug
确认了问题是由浮窗导致的,那么接下来就比较好排查解决了。
排查代码逻辑,发现是用户触发逻辑A后App弹出悬浮框,这里有一个异化逻辑,App在此处会先判断是否具有悬浮窗权限:
- 如果具有悬浮窗权限,浮窗的flag则为
TYPE_APPLICATION_OVERLAY
浮窗全局可见,不依赖Activity。 - 如果无悬浮窗权限则浮窗的flag为
TYPE_APPLICATION
,浮窗当前页面可见,依赖Activity。
伪代码:
ini
if (!checkPermission(activity)) {
flag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
WindowManager.LayoutParams.TYPE_APPLICATION;
} else {
flag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY:
WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
}
浮窗的关闭逻辑有以下两种:
- 超时自行关闭
- 用户手动关闭
进一步排查发现,用户还没有手动点击关闭浮窗的习惯,大部分时都需要依赖浮窗的超时逻辑,当浮窗全局可见时没有任何问题,当浮窗在当前页面可见时往往没有达到超时时间,用户就先行关闭了当前页面。由于浮窗非正常关闭,在超时逻辑执行时实际上应用崩溃了。
崩溃堆栈:
css
java.lang.IllegalArgumentException: View=android.widget.LinearLayout{cb1f391 V.E...... ......ID 0,0-332,450 #7f0a0535 app:id/flLl} not attached to window manager
at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:616)
at android.view.WindowManagerGlobal.updateViewLayout(WindowManagerGlobal.java:508)
at android.view.WindowManagerImpl.updateViewLayout(WindowManagerImpl.java:176)
但为什么明明有崩溃,为啥没有发现呢,因为我们应用中的崩溃白名单,具体在Android 复杂项目崩溃率收敛至0.01%实践中有介绍。我们针对 java.lang.IllegalArgumentException not attached to window manager
这个崩溃有在框架中进行防护,所以在测试过程中被忽略了。
至此彻底定位了问题后,就比较好修复了。当浮窗依赖当前页面时,在页面关闭前先将浮窗关闭就可以了。
4、由浮窗引发的泄露
不过将修复代码提交之后,我们发现当前页面关闭时,Logcat警告日志提示存在泄漏:
less
android.view.WindowLeaked: Activity com.netease.xxxxxx has leaked window android.widget.LinearLayout{9815076 V.E...... ......I. 0,0-332,450 #7f0a0535 app:id/flLl} that was originally added here
at android.view.ViewRootImpl.<init>(ViewRootImpl.java:1411)
at android.view.ViewRootImpl.<init>(ViewRootImpl.java:1397)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:469)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:169)
at xxxxxxx
at android.os.Handler.handleCallback(Handler.java:1014)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loopOnce(Looper.java:250)
at android.os.Looper.loop(Looper.java:340)
removeView 与 removeViewImmediate 的区别
进一步排查发现,在关闭浮窗方法中,我们使用的Api是removeView(),其是平滑关闭方式,可以在任意线程通过消息队列的方式关闭浮窗。由于其是异步执行的方式,所以关闭页面时出现了泄漏提示。当我们将接口改为removeViewImmediate()时,这个问题不再复现,至此这个问题完整修复完毕。
三、总结
以上介绍了一下在实际开发过程中,我们是如何遇到问题解决问题的。 整体来讲实际上算是一个小问题,但在各种机缘巧合之下上线后,造成了线上Bug。又由于是UI异常,所以在排查过程中浪费了一些时间。再次特意记录一下。
系列文章