问题解决-优化listView卡顿和怎样禁用ListView的fling

前戏非常长,转载请保留出处:http://blog.csdn.net/u012123160/article/details/47720257

问题产生

这算是刚到实习公司接触到的第一个任务。公司某一产品中某个界面的listView高速滑动会有卡顿的现象发生,我的任务就是解决它。

产生原因分析

我一開始的想法比較简单。可能是listview的优化没有做到位,比如convertView的复用、viewHolder的使用等等基础的优化措施。然并卵。好长时间后最终找到了问题发生的相关代码...经过在可疑语句上(onTouchEvent方法中的几个case、onScrollStateChanged方法中的SCROLLSTATEXXX状态下)打印log,发现例如以下几个问题和卡顿现象有关系:

  1. 在每天固定的一个时间段内,listView展示的数据室实时刷新的,也就是server会定时向client发送数据刷新的信息,client再向server发出数据请求,client主要属于被动刷新;其它时间段仅仅是单由client想服务区主动发数据请求。
  2. listView展示的数据是分页请求的。依据滚动位置来动态请求某一段数据。

  3. 依据打印的log。在运行到onScrollStateChanged方法中,也就是说当前listView的状态时滚动状态(包含fling)时,中间可能会插入数据请求的log。
  4. 经过请教导师,问题原因缩小到了数据请求、数据解析更新界面导致的卡顿。

于是再看代码。发现原因在于接受数据、解析数据、更新listView的Adapter内容的代码。没有和listView的滑动状态相关联。即滑动过程中可能就接收到了新数据并刷新了界面,造成卡顿。

问题初解思路

主要是滑动和数据接收更新界面之间的冲突,二者通过一个boolean变量关联一下就可以。即仅仅有在scrollState为IDLE时,才同意进行数据接收和界面刷新(调用notifyAdapterDataChanged方法),其它状态(FLING,TOUCH_SCROLL)都不同意。 而在server活跃的时间段之外,原本的代码中也是在滑动结束后才主动发起数据请求的,所以这方面不用考虑。

问题再发生——数据丢失

可是组长提出,可能会发生这种问题,在滑动过程中数据不接收,会发生数据丢失的问题,这个应用本身对数据的实时性非常高,所以这个也须要解决。

问题再解

測试了一下发现,卡顿的原因主要在于界面的刷新而不是数据接收,于是将控制范围缩小到了:仅仅有在不滑动的状态下才同意更新界面,而数据接收在滑动过程中也可运行。

保留最新接收到的数据就可以。在滑动结束后及时推断有无最新的数据缓存。

问题再再提出——disable fling

组长的老板感觉还是卡顿...这次问清楚了。居然认为界面上滑块的移动有一顿一顿非常难受...我和还有一个前辈对此真是相当无语了。最后没办法了,组长说那就把这个fling的功能给禁止了吧...

fling问题研究

百度搜索关键词“android listView fling”。出现的第一条就是以下这个,【Andorid X 项目笔记】禁用ListView的Fling功能(1),但是也是然并卵...

/** 手势识别类 */
private class TouchGesture extends SimpleOnGestureListener { /** 高速滚动 */
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return true;
} } private OnTouchListener mOnListViewTouchListener = new OnTouchListener() { @Override
public boolean onTouch(View v, MotionEvent event) {
if (mTouchGesture.onTouchEvent(event))
return true;
return false;
}
};

代码中mTouchGesture根本没有onTouchEvent(event)这种方法啊啊啊啊啊啊...一開始我是非常开心的....
然后就是各种百度,各种google,各种“静态流”(stackOverFlow的中文备案名称,我也是醉了),都没有个能用的解决方式。 当中能够使用可是不能达到要求的一段代码我认为不错,留下链接-Android
listview垂直滑动指定距离
,利用反射的原理。从AbsListView中找到一个相对来说比較通用的方法boolean
trackMotionScroll(int deltaY, int incrementalDeltaY)
来达到控制滑动距离的目的。

以后可能用得上。

禁止fling思路之中的一个——切断事件链

整整困扰了我三天,既然不能从正面刚,那从側面试试看吧。于是開始查询事件分发机制、fling的实现方式、fling的触发条件、scroll的相关内容...当中比較实用的是

  1. fling的触发是一系列的ACTIONXXX事件结合的结果,用户触摸屏幕后高速滑动,出现一个ACTIONDOWN,多个ACTIONMOVE和一个ACTIONUP相结合。链接-Android:
    触屏fling/scroll/drag的差别及其具体过程
  2. 触摸事件分发就比較复杂了,主要涉及到三个重要的方法,各自是dispatchTouchEvent涉及事件分发,onInterceptTouchEvent涉及事件拦截和onTouchEvent涉及事件处理。

    遇到几次和这个相关的问题,每次都得查看相关的知识点...总是记不住。

    链接-Android
    触摸事件分发ViewGroup&View
    多看几遍就看懂了。

  3. VelocityTracker这个类是用来測量滑动速度的。

    链接-手势事件:滑动动速度跟踪类VelocityTracker介绍

  4. AbsListView中,滑动达到了一定的速度,就会触发fling。而在AbsListView的源代码中是由一个runnable的自己定义类来完毕的。太复杂所以没怎么看。可是有空了是一定要细致研究的。

那么从上面这几点能够总结出一个结论,就是fling是由touch事件来控制的。而touch事件是由view&viewGroup来进行分发、拦截、对应的。今天早上起床的一个念头就是解决这个问题的方法:

拦截掉touch事件来从源头上打破触发fling的事件链,从而达到禁止fling的目的

说干就干,到了公司。找到问题发生的listView。找到容纳该listView的viewGroup(是一个LinearLayout的子类)。将那三个touch函数写下了,打印log分析,从哪里拦截,拦截哪个事件比較好。

终于决定在dispatchTouchEvent中拦截。在ACTION_UP中推断当前滑动在y(纵轴)方向上的速度,当速度达到某一个阀值后,return
false
,也就是将能够出发fling的事件链中最后一个事件给消灭掉。不传给子view。

试了一下。果然好使!

总结

对公司项目代码还是不熟,大部分事件都花在定位问题代码上面了。再加上解决这个问题的积累还是不够。不能从根本上考虑,处处碰壁之后才想到从源头上来解决。

事实上我感觉解决这个问题的根本还是看一个人的知识积累和知识整合的能力。“熟读唐诗三百首”就是这个道理。

额外补充

8月18日补充

提交代码后。发现效果还是不太理想。

因为在事件分发的时候截断了事件链,导致在手指滑动屏幕过程中。速度一旦过快,屏幕就会停止响应,卡顿效果甚至比之前还要严重。而原本预期的效果应该是这样:

低速滑动能够触发fling。

快速滑动,能够滑动可是fling效果不明显,即有一种手指离开马上停止的效果。

无奈之下又返回头来看源代码,有之前查找的资料做铺垫,在AbsListView这个类中寻找和fling相关的代码块,细致搜索之下,有所发现。

改进过程——利用反射来从源代码的角度解决fling

AbsListView类中有例如以下成员变量,和fling有关。

  • private
    FlingRunnable mFlingRunnable;
    这是一个Runnable类型的对象,其内是用于对ListView运行fling的运动。包含其開始、运动、遇到边界、结束等等操作。
  • private
    int mMinimumVelocity;
    触发fling的最低速度,其赋值语句为mMinimumVelocity
    = configuration.getScaledMinimumFlingVelocity();
    ,而在configuration这个类中,相关的值被定义为50,即每秒在屏幕上运动50dip单位。

  • private
    int mMaximumVelocity;
    和fling的最快速度、运动距离相关,赋值和上句类似,值为8000。velocityTracker.computeCurrentVelocity(1000,
    mMaximumVelocity)
    ,前一个參数表示速率的基本时间单位。单位为毫秒。在此即为1秒。第二个參数表示速率超过mMaximumVelocity的都依照mMaximumVelocity的值来计算。

  • private
    float mVelocityScale = 1.0f;
    与fling的最远距离相关,最远距离的计算公式为velocityTracker.getYVelocity(mActivePointerId)
    * mVelocityScale
    ,就是实际速度与mVelocityScale的乘积。

那么知道了上面的一些属性之后,就能够在代码中动态的改动这些值,来实际的控制fling的触发速度、fling的最远移动距离等效果了。

可是注意一点,这些熟悉都是私有属性,所以还须要通过反射来获取AbsListView这个抽象父类进行改动。

以下上相关代码。

public void initFlingArgument(ListView listView,int maxVelocity,int minVelocity,double scale){

    if(listView == null){
return null;
}
/*获取父类。假设是所要改动的类是ListView的子类的话,再获取一次父类就可以*/
Class cls = listView.getSuperclass();
if(cls == AbsListView.class){
try{
//fling的最小响应速度
Field f_min = AbsListView.class.getDeclaredField("mMinimumVelocity");
//fling最大响应速度
Field f_max = AbsListView.class.getDeclaredField("mMaximumVelocity");
//fling scale
Field f_scale = AbsListView.class.getDeclaredField("mVelocityScale");
//设置属性可达
f_min.setAccessible(true);
f_max.setAccessible(true);
f_scale.setAccessible(true);
//设置详细值
f_max.set(listView,maxVelocity);
f_min.set(listView,minVelocity);
f_scale.set(listView,scale); }catch(NoSuchFieldException e){
e.printStackTrace();
}catch(IllegalArgumentException e){
e.printStackTrace();
}catch(IllegalAccessException e){
e.printStackTrace();
}
}//end if
}//end method

总结

这下应该几乎相同了吧...

8月20日补充

仍然不符合要求。在真机上測试,流畅程度仍然不尽人意。昨天做完导师分配的还有一个任务后。接着研究这个。

新的要求

有可能是前期没有把需求搞明确。之后提出的要求变成了这样:低速滑动,有fling的效果。快速滑动手指。listView仍然能够滚动一段距离。然后停止,注意是停止。不是逐渐停止(也就是说停止fling)。

我的思考

相比于之前的实现,新的要求在于滚动一段距离后马上停止。不同意有减速停止的效果。那么解决问题的关键还是关于fling的实现上,或者还有一个办法就是直接不使用fling,在高速滑动。手指离开屏幕后(触发ACTION_UP)。用代码操作ListView进行滚动。

  • 思路1:在源代码中找找有没有控制fling时间的參数或者方法。

  • 思路2:在竖直速度超过预定的阀值后。断掉fling的事件链(为什么不用上一次的方法来让mVelocityScale这个属性值为0呢,我的考虑是滑动是一个不断触发的过程,被调用的频率非常高。而反射的效率非常低,所以会影响界面的响应,或者其它的副作用,不如直接断开事件链效率高),然后调用ListView的smoothScrollXXXX()系列的方法来进行自主控制的滚动。
  • 思路3:之前在网上看过一篇关于Scroller的介绍和基本运用,就想着能不能用Scroller的fling或者startScroll方法,链接-
    android scroller overscroller使用方法

解决途径

实际动手開始写代码,发现和想象的根本不是一回事。

之前想的被一一排除。

  • 排除思路1:源代码中控制fling的是一个Runnable类,并没有fling时间的相关设置或者參数,是由start(int)方法和endFling()方法来分别启动和结束fling的。

  • 排除思路2:并没有什么卵用。可能是用的方法不正确,可是没有试出结果来...只是有个方法叫setSelection(int)能够直接把可视范围确定在给定序号的item上,可是这没有滚动效果...
  • 排除思路3:没有细致看博客说明...scroller控制的是view的内容,并非view本身,导致我參数设置。一调用方法,listView的内容就不见了...和滚动是两个概念。

最后解决:既然之前能用反射把fling的參数设置了,那么也能够调用AbsListView里面的FlingRunnable对象的endFling()方法。再延时个几百毫秒的调用一下,停止掉fling。

相关代码例如以下:

private Field mFlingEndField = null;
private Method mFlingEndMethod = null;
private Handler mStopFlingHandler = new Handler();
//放于初始化方法中被调用
public void stopFlingInit(){
try{
mFlingEndField = AbsListView.class.getDeclaredField("mFlingRunnable");
mFlingEndField.setAccessible(true);
mFlingEndMethod = mFlingEndField.getType().getDeclaredMethod("endFling");
mFlingEndMethod.setAccessible(true);
}catch(NoSuchFieldException e){
e.printStackTrace();
}catch(NoSuchMethodException e){
e.printStackTrace();
}catch(IllegalArgumentException e){
e.printStackTrace();
}catch(IllegalAccessException e){
e.printStackTrace();
}
}
//在须要停止fling的地方调用
public void stopFling(ListView listView){
if(mFlingEndMethod != null){
try{
mFlingEndMethod.invoke(mFlingEndField.get(listView));
}cache(InvocationTargetException e){
e..printStackTrace();
}
}
}
ListView listView = this;/*这些我都是写在自己定义的ListView内部的*/ Runnable mStopFilingRunnable = new Runnable(){
public void run(){
stopFling(listView);
}
} //用法,300毫秒后停止fling
mStopFlingHandler.postDelayed(mStopFilingRunnable,300);

结束

虚拟机上达到要求,可是真机上是不是还是然并卵...这就不知道了。

但我有种预感,这事没完。

android问题及其解决-优化listView卡顿和怎样禁用ListView的fling的更多相关文章

  1. Android 卡顿优化 1 卡顿解析

    1, 感知卡顿 用户对卡顿的感知, 主要来源于界面的刷新. 而界面的性能主要是依赖于设备的UI渲染性能. 如果我们的UI设计过于复杂, 或是实现不够好, 设备又不给力, 界面就会像卡住了一样, 给用户 ...

  2. 彻底解决 Intellij IDEA 卡顿 优化笔记,重要的快捷键

    由于工作中经常出现分支各种切换,使用Eclipse便不再像以前那么舒服了,不停的修改工作空间,每次修改完工作空间又是一堆一堆的个性化设置,来回的切换,真的很累.我们做软件的,怎么能不去尝试新鲜的呢,毕 ...

  3. 彻底解决 intellij IDEA 卡顿 优化笔记

    由于工作中经常出现分支各种切换,使用Eclipse便不再像以前那么舒服了,不停的修改工作空间,每次修改完工作空间又是一堆一堆的个性化设置,来回的切换,真的很累.我们做软件的,怎么能不去尝试新鲜的呢,毕 ...

  4. android viewpager fragment切换时界面卡顿解决办法

    目前开发的程序在切换View时界面卡顿现象比较严重,影响用户体验,当前项目共就四个View,每个View也只是按钮,所以可以同时加载,不让其它view销毁. 只需在Adapter中重载destroyI ...

  5. 性能优化 BlockCanary 卡顿监测 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  6. Android 教你如何发现 APP 卡顿

    最近部门打算优化下 APP 在低端机上的卡顿情况,既然想优化,就必须获取卡顿情况,那么如何获取卡顿情况就是本文目的. 一般主线程过多的 UI 绘制.大量的 IO 操作或是大量的计算操作占用 CPU,导 ...

  7. 解决eclipse 保存卡顿的问题

    开发十年,就只剩下这套Java开发体系了 >>>   eclipse 如果启动慢,还可以接收. 可是如果是 保存的时候卡顿, 有时候会 卡顿 3秒-5 秒的,感觉到写代码特别的不顺畅 ...

  8. 解决XMind运行卡顿

    问题 XMind是一款很好用的脑图工具,它是基于eclipse开发的,而且基础功能是免费的.最近我安装了XMind 8 Pro,但是发现在Mac上运行有卡顿. 解决方式 解决这个问题的思路也很简单,软 ...

  9. 【C++】解决vs2015经常卡顿的办法

    VS2015经常性的卡顿,参考了zhihu里问答的办法,编译和使用的时候的确快多了 为什么vs2015经常卡顿? https://www.zhihu.com/question/34911426 感谢z ...

随机推荐

  1. 【Codeforces Round #459 (Div. 2) B】 Radio Station

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 用map模拟一下映射就好了. [代码] #include <bits/stdc++.h> using namespace ...

  2. windows下plsql 设置 里面timestamp显示的格式

    http://blog.csdn.net/cwjcsu/article/details/9216981

  3. matlab中tic和toc使用方法

    tic和toc用来记录matlab命令运行的时间.  tic用来保存当前时间,而后使用toc来记录程序完毕时间. 两者往往结合使用,使用方法例如以下: 程序代码: tic  operations  t ...

  4. 在JAVA中怎样跳出当前的多重嵌套循环?

    在JAVA中怎样跳出当前的多重嵌套循环?         这道题是考察大家对于break语句的应用.同一时候也是对你多重嵌套循环的使用进行考察.在java中,要想跳出多重循环,能够在外循环语句前面定义 ...

  5. StdTranslator - Translate PDMS to STD for STAAD.Pro

    StdTranslator - Translate PDMS to STD for STAAD.Pro eryar@163.com STAAD.Pro是由美国世界著名的工程咨询和CAD软件开发公司—R ...

  6. 一次失败的PHP扩展开发之旅

    一次失败的PHP扩展开发之旅 By warezhou 2014.11.19 缘起 经过不断的持续迭代.我们部门的协程版网络框架(CoSvrFrame)最终出炉了!这本来是件喜大普奔的事情.可是随着新业 ...

  7. SpringBoot与Dubbo的整合-zookeeper和监控中心搭建

    对于Dubbo的应用已经是十分普遍,自从阿里巴巴开源以来,国内许多公司就采用了dubbo的架构来开发项目.不过再dubbo十分火的时候,突然就停止更新了, 只有当当网还在其基础进行了拓展(dubbox ...

  8. 61.C++文件操作实现硬盘检索

    #include <iostream> #include <fstream> #include <memory> #include <cstdlib> ...

  9. Linux下截图技巧

           在需要Linux显示图片的场合,最普通的方法,会考虑用数码相,或是用Vmware,或VPc来抓拍,这样以来会比较麻烦,Linux也自带了些工具例如Gimp,ksnapshot这里我介绍一 ...

  10. 在英语中,in,on,at的用法是?

    在几点:at xx:xx ,在那月: in  Dec./Jan.……, 在那日:on Thur./Mon.……,on Jan. 16th 时间前面 at, 年月日前面如果有具体的日子,就是具体的哪一天 ...