解决EditText卡死问题(焦点冲突),debug复盘

一句话总结

edittext和marquee(跑马灯)的常规实现方法有冲突,造成了长达2天的debug历程。

背景

电话簿做了个搜索框筛选联系人的功能,测试组反映用是能用,但是很不好用,经常卡死。

描述如下:输入若干数字后,删掉,再输入字母,卡住不动;隐藏软键盘,再点击无法弹出

看上去十分玄学,似乎和输入框有关系,又和输入的内容有关系,而且不是一开始就搜不了,是搜之后卡了。
于是进行了以下查错猜想:

猜想1 软键盘问题:

  • 1.1 inputMethodManager.showSoftInput()失效
    既然软键盘弹不出来,也许是因为打开方式不对,看到参数写着SHOW_FORCED,可能它吃软不吃硬呢?
    验证:改参数
    改了各种参数,该弹不出来还是弹不出来
  • 1.2 inputMethodManager.showSoftInput()方法没用对
    验证:虽然摸不着头脑,但是试了试im.toggleSoftInput()
    这次能弹出来了,可喜可贺,不过会出现两个问题:多次点击输入框,软键盘会显示-隐藏-显示-隐藏;虽然弹出了软键盘,但是软键盘根本操作不了输入框,按退格也不会减少字符

猜想2 焦点丢失:

其实这里接近了答案,但是后面几个猜想走了弯路。
也许是在输入的过程中,edittext突然抽风了,把自己焦点整没了,导致软键盘找不到输入的位置,因此根据showSoftInput的第一个参数不予显示,而toggleSoftInput不带view参数,可以出来但是也找不到。
验证:字符每次变化都调用edit.requestFocus(),并使用getCurrentFocus()验证焦点是否在编辑框上
根据log来看,getCurrentFocus==eidt从来不会返回false,也就是说想象中的焦点丢失并没有产生

此时在多次试错中发现了一些必现路径,于是发消息问师父
奇特的触发方式
我的直觉是:输入框和搜出来的联系人有关联,可是这怎么可能呢?两个区域并没有联系,列表只不过读取了输入框的字符而已,它们不存在相互操作。师父看后回应“有点邪门”。
抱着疑虑进行接下来的查错

猜想3 输入框回调有问题:

粗略地看了一眼,输入框只不过绑定了TextWatcher,在字符改变之后用那些字符串做了搜索。这里能有什么错呢?抱着死马当活马医的心态,居然得到了突破。

  • 验证3.1 把edit的绑定回调给注释了(不执行搜索操作)
    edit居然恢复了丝滑,我又试着把im.toggleSoftInput改回了当初以为错漏百出的模样,还是十分丝滑。如果能不执行搜索操作那么就完美了!
  • 验证3.2 恢复到之前,把edit的搜索操作注释了(直接显示搜到了0条记录)
    edit依然丝滑。在这里证明了edit卡住果然和搜索操作有关系
  • 验证3.3 恢复到之前,只注释了“搜到之后加入列表”,添加一条log“搜到一条记录”并打印
    edit依然丝滑。虽然列表不显示,但是log出得又快又好,证明搜索的环节是没有卡住的。

猜想4 搜索列表频繁刷新:

记得使用recyclerview的更新整个列表的方法的时候,这段文字常常被标黄,安卓认为应该进行局部更新而不是全部刷新,容易引发性能问题。虽然这里使用的是listview,我此刻也不禁想起了这条警告。

  • 验证4.1 注释了adapter.notifyDatasetChanged()
    并没什么变化,惊人的是就算全局没有触发一次这个方法,列表也会根据我的数据改变而改变。搜索了一会原因,大意是listview有其它的刷新机制。这让我想到了是否是这种隐藏刷新太多了,导致了卡死呢?
    这就是adapter直接内置在页面类中的弊端了,共享数据,然后数据实时改变,不知道什么时候就跑断腿了。
  • 验证4.2 使用一个临时变量装结果集,搜索完成后再将结果集内容转移到adapter依赖的变量中
    结果是卡死的情况少了,但是仍然存在,由于完全是乱按,只靠脑子记了几个必现数字,此时一部分的操作不再卡了。但是想必还有其他的原因导致edittext输入问题。

师父提了个建议:也许是搜索这个行为比较耗时,导致了一些系统级别的错误,可以试试把搜索放在另一个线程里,用handler更新ui。
我的内心的声音告诉我很可能不是这个原因,因为我的“验证3.3”执行了搜索操作,但是并没有复现卡死问题,而系统级别的错误为什么只影响这一个edittext呢?点击其他的或者切换页面都很正常,而且退出搜索再点进来又能体验一段时间的搜索功能。我有一个想法:在数据改变时UI的绘制导致了某些冲突,因此当列表改变时输入框有概率卡住
不过改为handler处理也有好处,可以把edit和listview分开,代码结构更加清楚。

  • 验证4.3 搜索在另一个线程处理,使用弱引用的handler更新ui
    不出所料地没有改善bug情况。

此刻debug已经进行了1天,没有找到直接原因,网络上也缺乏相关资料查询,心中十分苦闷(这也是作者写这篇文章的理由之一)
接下来验证内心的想法。当我看到列表子项的标签类型的时候,内心电光火石一般,暗道“果然如此”

猜想5 跑马灯焦点冲突(真实原因):

列表子项由首字母、联系人姓名、联系人号码等组成,其中联系人姓名和联系人号码并不是一般是TextView(但是在adapter中写的是Textview 所以一直没有注意),而是继承了TextView的自定义FocusableTextView。结合该视图代码内容查阅资料我了解到,这个自定义文本框类的写法是网络上推荐的跑马灯实现方法。
什么是跑马灯效果?
跑马灯的一般实现方法
可以发现,跑马灯是一种动画效果,而这个效果需要文本有焦点才能播放。
通讯录的跑马灯效果不难理解,这是为了看清号码的全貌,当文本过长时也不会被隐藏或者撑破容器。
但是编辑框也需要焦点,查阅了大量资料,换着法儿问ai都没有给出确切原理。

推测如下:当编辑的内容改变之后,列表搜到了一些号码,它们显示出来的时候是强制获取焦点的,UI更新,软键盘有可能无法与编辑框配对。

这解释了猜想2中为什么输入框一直有焦点也无法打开软键盘,也能说明编辑到半路突然没反应是怎么一回事。
两类非常简单的视图,一个很常用的功能碰撞在一起居然能发生这样的变化,让编辑框根据输入的内容卡死,不做以上大量测试是如何也推理不出来的。

解决办法

不使用跑马灯功能

规避办法,也是我用来验证猜想5的。把layout资源文件的FocusableTextView改成TextView就完成了。结果令人满意,输入框不卡了,外观上也天衣无缝,毕竟搜索框这么长也不太可能超过字数。
紧急修复的话选它准没错,不过我仍然想要个两全,总不能让编辑框和能滚动的文本框死生不复见吧,这没有逻辑。

跑马灯动态改变焦点获取(未实现)

尝试在FocusableTextView里面放一个静态变量,让它改变文本框的isFocused()返回值,当需要编辑的时候就禁用它们的焦点。
什么时候需要编辑?点击编辑框的时候,这很好理解。
那么什么时候恢复跑马灯的焦点呢?一个通俗的想法是用户隐藏软键盘的时候就是查看列表,这时候应该恢复跑马灯。但遗憾的是隐藏软键盘没有相关回调,根据查到的资料,这和相应的输入法有关。
上网查隐藏软键盘的监听,主流的办法是监听窗口高度,当显示的窗口高度小于屏幕某个值就认为弹出了软键盘。虽然听上去有点滑稽,我还是尝试采用该办法,发现没有用:因为临界值不好确定,弹出或隐藏软键盘的时候列表已经被绘制了一部分,无法改变输入框焦点冲突的尴尬境遇。
同时,软键盘的事件和绘制ui是两回事,就算明确知道了软键盘要隐藏或者显示的时机,也不代表可以提前阻止焦点获取。

属性动画模拟跑马灯(未实现)

这是一个一眼可行的方法。设置属性动画在视图出现的时候就循环播放不需要焦点,自然不会冲突。但是敢想不一定敢做,作者就没做,不敢做的原因如下:

  • 什么时候播放跑马灯?
    如果文字比较短,让它跑起来显得很愚蠢。所以需要先测容器和文字的宽度,容器的宽度易得,文字的宽度还需要一支画笔(Paint),还得测量一下确定。
  • 怎么跑?
    跑多块算舒服呢?这个值需要经过大量测试,还会受到字体等的影响,也许需要一个函数来表示,有一定工作量,但是它只是一个杜撰出来的数字,没有含金量可言。
  • 性能问题
    屏幕上如果有20条记录,那人名加号码就有40个动画循环播放,如果再加入长度判断还得时不时控制它不动,想想都很不理智。(不过主管在背后说我们的芯片特别好怎么造都没事,我假装没听出来他的潜台词。)

改变跑马灯焦点获取方式

这个解决方式的灵感来自于一次偶然的搜索:
解决TextView跑马灯效果和EditText冲突-张海龙_China
虽然博主的意思是它们焦点冲突搞得跑马灯不跑了,而我要解决的问题是输入框卡了,但是运用了他的代码之后,bug解决了。
这个方式不需要修改资源文件,也不限制输入框和跑马灯效果同屏,因为它修改了TextView焦点获取方式,不再让focused恒等于真,而是使用了被选中的方式。(虽然我仍然不清楚原理,但是这个方法相当于为跑马灯和输入框划分了两条赛道,让它们不会再干预彼此)
化用博主思路,附上跑马灯FocusedTextView关键代码如下,期待这个方式以后能够成为主流的跑马灯写法。

@SuppressLint("AppCompatCustomView")
public class FocusableTextView extends TextView {

    public FocusableTextView(Context context) {
        super(context);
        initMarquee();
    }

    public FocusableTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initMarquee();
    }

    public FocusableTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initMarquee();
    }

    public FocusableTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initMarquee();
    }

    //移除焦点,使用选中效果
    private void initMarquee(){
        this.setFocusable(false);
        this.setSelected(true);
    }



//    @Override
//    public boolean isFocused() {
//        return true;
        return super.isFocused();
//    }
}

后记

一杯茶,一包薯片,一个bug,改好几天~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值