1. 问题
某次项目上线前,QA的妹纸忽然发现一个严重的bug,列表无法进行下拉刷新了。使用场景是当前列表数据为空,或者请求失败时,又没有加载到缓存数据的情况,EmptyView页面就无法进行下拉刷新了。关于这个问题,我曾重点解决过,所以当妹纸提出这个bug时,作为程序员的职业习惯第一反应是:这不可能,你看我这好着呢
然而经过几轮测试发现问题是真实存在的,这么简单的一个SwipeRefreshLayout为什么出了这么多幺蛾子呢
2.问题背景
最开始采用SwipeRefreshLayout作为下拉刷新控件,但是产品经理还没来得及定义EmptyView页长什么样就跑路了,所以这个问题就交给程序员自己解决了
最简单的方案就是在Empty页中间放一个按钮,请求失败或者无网络的时候分别提示用户点击重试或者设置网络,这也是大多数APP的选择。
不过即便请求失败或者空数据的情况下,仍然采用下拉刷新的操作来进行retry,在交互上可以保证用户习惯的一致性,而且滑动操作也比点击操作有更好的体验,也是交互和设计的趋势所向
当时第一版开发时间特别紧,所以关于这个EmptyView的实现方案特别挫。直接在xml里包了两个SwipeRefreshLayout,一个用来下拉刷新列表,一个用来下拉刷新EmptyView,然后在代码里控制二者的显示与隐藏。方案虽然很挫,但也没出什么大问题,就这么作为临时方案先用着
第二版的时候把这个方案替换掉了,去掉了EmptyView外面包裹的SwipeRefreshLayout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
<TextView
android:id="@+id/error_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:textColor="@color/subhead_text_color"
android:textSize="@dimen/primary_tip_size"
android:visibility="gone" />
</RelativeLayout>
直接这样,EmptyView依然可以下拉刷新,这里依赖的原理是点击事件的穿透,即便当前显示在上层的视图是EmptyView,如果不给EmptyView添加任何事件,那么事件会向下传递,传递给下面的SwipeRefreshLayout,有时候这种奇技淫巧还是挺有用的
然后这个方案就一直延续着,直到开头提到的QA妹纸发现无法下拉刷新的bug为止,都没有发现什么问题。那这个bug到底是什么原因引起的呢?
3. 思路(我觉得蛮重要的)
我第一反应是,有人动我代码了,于是我compare了当前节点与上次发版的节点,代码一毛一样,没有人动我代码,但是线上的就OK啊。刚开始我跟QA妹纸打包票,半个小时解决问题,而实际上最后和同事们花了近半天时间才解决。
问题走进了死胡同,首先是看了半个小时代码,无法准确定位问题。compare了两个节点的代码,依然无法定位,那还是只能采用笨办法了,排除法。上次发版距本次发版,这中间已经产生了几十个commit,到底是哪次commit后出现了这个问题,这是第一个要定位的问题,于是乎一一checkout验证,当然这也是有技巧的,虽然没验证过到底怎样的查找方式会比较快,习惯性的在这几十个commit中来了个二分查找。每次checkout编译运行测试也是很耗时的,而且这个工作很枯燥,只能发动大家一块来找茬啦
找了好大一会,终于定位到了一个commit,这个commit 之前OK,而之后就悲剧了。那毫无疑问就是它搞得鬼,但这个commit毫不起眼,根本没有什么伤筋动骨的修改。那如果代码上没有大修改,就要注意下AndroidManifeset和gradle了。gradle中增加了些看似无关紧要的引用,肉眼也实在看不出有什么问题,但如果把这些新增的引用包去掉,问题马上就解决了。但新增的这些包与SwipeRefreshLayout可是八竿子打不着啊,但SwipeRefreshLayout是android.support.v4包里的控件,会不会是support.v4包更换了新的版本?
展开External Libraries 发现果然有两个support.v4包
难道是这两个包里的SwipeRefreshLayout实现不太一样,或者有做改动?
找到源码一探究竟,拉出support.v4-23.0.1和support.v4-23.3.0里的SwipeRefreshLayout源码,一看行数都不一样,前者1152行,后者1163行。我勒个去还真是做了修改,难道是升级的时候没有向下兼容吗?compare了下两个版本的代码,发现事件分发那里确实做了些修改
4. 解决办法
猜想大概是这个原因,利用Click-Through来实现下来刷新不能奏效了。但是我仍然想用以前的方案,怎么办呢?
有两个办法,一个是指定使用老版本的support.v4包。二是把老版本的代码copy出来放到自己工程里。最后我们采用了比较优雅一点的方式,在gradle里强制指定了版本
configurations.all {
resolutionStrategy {
force 'com.android.support:support-v4:23.0.1'
}
}
最后一个问题,为什么会有两个support-v4包呢?刚开始的时候用recyclerview,SwipeRefreshLayout这些空间的时候导入了一个support-v4包,这个版本相对较低,是support-v4:23.0.1,后来一直没有更新。随着项目的进行,另外的同学在导入其他第三方包的时候,这个第三方包依赖了一个比较新的support-v4包,support.v4-23.3.0。所以这个时候SwipeRefreshLayout默认使用了比较新的额代码,就导致了开头所提到的那个bug。友情提示:更新引用包是有很大风险的,一定要多测试。有时间再说下另外一次更新包挖的坑