-
微博拿到这个速度回调之后,就可以根据列表的滑动速度来决定是否要在滑动过程中加载图片,一旦列表的滑动速度低于设定的阈值,就开启图片加载;一旦列表的滑动速度高于设定的阈值,就关闭图片加载
-
华为检测到这个应用使用了 SDK,就可以将优化过后的滑动曲线应用在这个 App 的列表 Fling 阶段,提升用户体验
对细节感兴趣的同学可以继续阅读,有能力的同学看完后可以修改 Framework 相关代码,编译一个 SDK,然后自己写个 Demo 接入 SDK,就可以打通我下面所说的所有内容了,我自己在 AOSP 的代码上实现了一遍,Demo 也可以正常运行,有兴趣可以跟我私下交流。
4微博+华为是怎么优化?
现象分析
我们在滑动微博列表的时候,一个滑动操作主要由下面三部分组成
-
手指接触屏幕,上下滑动微博主页列表,但是手指 没有离开屏幕 ,这个阶段我们称之为阶段一,技术术语为 SCROLL_STATE_TOUCH_SCROLL
-
手指上下滑动的时候 离开屏幕 (必须有一个上滑或者下滑的速度),微博列表有了一个惯性,根据惯性的方向继续滑动,这个阶段我们称之为阶段二,技术术语为 SCROLL_STATE_FLING
-
列表惯性滑动后停止,这 个 阶段我们称之为阶段三 , 技术术语为 SCROLL_STATE_I DLE
而华为和微博的优化主要在阶段一和阶段二。
阶段一优化
- 优化前:只要手指不离开屏幕,图片加载功能关闭
- 优化后:只要手指不离开屏幕,列表就不会滑动太快,这时候图片加载功能开启
阶段二优化
-
滑动图片加载优化
-
列表滑动速度太快,这时候图片加载功能关闭
-
列表滑动速度掉落到一个阈值,图片加载功能开启
-
优化前:只要列表滑动不停止,图片加载功能关闭
-
优化后:图片加载功能是否开启取决于当前列表滑动的速度
-
列表 Fling 曲线优化
-
优化前:列表滑动的曲线是默认值,滑动时间比较短,停止的时候比较突兀,不柔和
-
优化后:列表滑动的曲线是华为经过优化的,滑动时间比较长,停止的时候比较柔,不突兀,比较接近 iPhone 的列表滑动曲线
技术分析
技术分析的代码主要来源于微博 apk 的反编译,微博版本 10.8.1,通过反编译的代码可以看到, 微博主页在初始化的时候,会接入华为提供的 PerfSDK,从而获得监听列表滑动速度的能力。
阶段一优化的技术分析
列表的 ScrollStateChange 是标识列表状态的一个回调,微博在 ScrollStateChange 这个回调中会根据当前的状态来决定是否加载图片, 从下面的代码逻辑来看:
-
当 滑动图片加载优化生效 的时候,如果 State != 2,那么就允许 ImageLoader 加载图片,State 为 2 也就是 SCROLL_STATE_FLING,熟悉列表滑动的同学应该知道,SCROLL_STATE_FLING 就是 滑动列表的时候手指松手后列表继续滑动的那一段 ,叫 fling,毕竟只有 fling 的时候才有 Velocity,松手后会根据这个值的大小计算滑动曲线和滑动时长
-
当 滑动图片加载优化不生效 的时候,就到了常规的列表滑动优化:即列表停止之后才开始加载图片 :State !=0,0 即 SCROLL_STATE_IDLE
阶段二优化的技术分析
微博的主页在初始化的时候,会给首页的 ListView 注册一个 HwPerfVelocityCallback,从名字可以看出来,这个回调是监听 Velocity 的,也就是滑动的速度,两个回调:
-
HwPerfonVelocityDownToThreshold : 当速度降低到阈值之后,打开 ImageLoader 的图片加载功能
-
HwPerfonVelocityUpToThreshold:当速度升高到阈值之后,关闭 ImageLoader 的图片加载功能
下图为反编译后的源码
至于滑动曲线,则需要查看华为的 Framework 的代码,由于代码量比较大,这里只贴一下 OverScroller.java 中的 update 方法,具体感兴趣的可以自己去翻一番华为的 Framework 代码:
计算 Distance 的代码
计算 Velocity 的代码
关于滑动曲线的解释,大家可以看这一篇知乎回答,其中对比了 iOS 和 Android 的滑动曲线的不同 :为什么 iOS 的过渡动画看起来很舒服?https://www.zhihu.com/question/291779390/answer/484881732
其他厂商处理
上面图中代码最后一段还有一个判断开关, 如果 boolean a = HwPerfUtil.m14290a 这个返回的是 false,这就是说有可能华为这个优化关闭了,有可能是非华为机器,那么会 判断 Android 版本号和全局 Feature 开关:
对应的 FeedAbManager 就是一个 Feature 管理器,可以在线开关某些 Feature
而 m52580k 的实现如下
可以看到这里还受到一个全局的 Feature 配置:feed_scroll_loadimage_enable,这个 Feature 是服务端可以配置的
这里就是处理其他厂商的逻辑。
最后一个问题:滑动点击
滑动点击是个什么问题呢?列表在滑动的过程中,如果用户点击列表的某一个 Item,那么根据 Android 的事件分发机制,这时候列表的 Item 并不会被点击到,而是整个列表先收到点击事件,然后 触发列表滑动停止;列表停止的时候点击 Item 才会触发 Item 的点击。上面阶段二的优化中,**在优化了滑动曲线之后,列表处于 Fling 状态的时间变长,如果用户想点击去某一个 Item 查看详情,那么需要先点击一下让列表停止,然后再点击一下才能进去,这就是这一节想说的 :滑动点击问题。**滑动(Fling 状态)和点击其实是需要一个平衡的,这个平衡需要开发者自己去把控:滑动(Fling 状态)的时间越短,列表越容易停下,用户点击列表越容易触发 Item 的点击,但是容易停止带来的问题就是不够柔和。想象你在粗糙的水泥地上滑出去一块石头,这石头没有滑动多久就会停止,不管是扔石头的你还是旁边看你扔石头的我,都不会觉得这有什么美感,但是没得选。这个的 代表其实就是 Android 原生的 Fling 曲线。滑动(Fling 状态)的时间越长,滑动(Fling 状态)的时间越长,列表越不容易停下,用户点击列表越不容易触发 Item 的点击,如果曲线优化的好,给人的感觉就是很柔和,符合物理规律,想象你在光滑的冰面上滑出去一块冰,冰面越滑,冰块滑动的时间就越长,越不容易停下。这其中的极端代表就是 iOS 的 Fling 曲线。说 iOS 极端是因为,iOS 的滑动曲线调的太磨叽了,时间长不说,停的异常慢,很多时候你都需要点击一下列表让他先停止,然后再进行下一步的点击动作。而小米的 MIUI12 对这个也进行了调整,效果要比 iOS 好一些,如果再和三方进行类似华为和微博的合作,体验会更上一层楼。滑动点击问题其实也可以通过厂商和 App 合作来解决,比如,当滑动到整个滑动距离的 98%(或者 95%) 之后,用户点击列表不再是让列表停止,而是列表内的 item 响应这个点击。这个思想来源于 Launcher 的代码,Launcher 的每一页在左右滑动的时候,如果滑动还没有停止但是用户比较手速快点击了某个 icon 想启动,那么这时候不会触发 Page 停止,而是直接响应 icon 的点击启动应用操作。5延伸阅读
列表滑动图片加载的性能考虑
前文有提到这个问题,滑动的时候进行图片加载主要有两个问题:
-
如果用户滑动非常快,比如是想找昨天发的某个微博,那么今天发的所有的带图片的微博在用户滑动的时候是没必要加载的,因为用户的目标不是这些图片,而 App 去加载这些图片,而程序员是不会为用户提前加载你未看到的数据,因为加载过多的数据不仅容易发生数据复用、缓存过多、内存溢出等错误,还会对服务器造成不必要的资源请求。
-
如果用户滑动非常快,那么图片加载队列势必有许多无效的资源(对这一刻的用户来说),而用户真正想看的图片反而排在了加载队列后面,造成加载速度变慢,也会影响用户的体验
滑动中加载图片最大的风险其实就是造成卡顿,因为图片加载本身就是一个比较重的操作,而高帧率的手机上,一帧的时间被压缩到很短,任何小的不确定性都有可能造成卡顿。所以厂商+应用的这个优化:快速滑动不加载图片,慢速的时候再加载,然后优化滑动曲线 ,其实对厂商和应用都是非常有益处的、
列表滑动监听背景知识
下面的 AbsListView 的 OnScrollListener 里面标注了列表滑动的三个状态
- 滑动停止:SCROLL_STATE_IDLE
- 手指在屏幕上滑动:SCROLL_STATE_TOUCH_SCROLL
- 手指离开屏幕,列表靠惯性继续滑动:SCROLL_STATE_FLING
两个回调
- 列表状态变化时的回调 :onScrollStateChanged
- 列表滑动时候的回调:onScroll
public interface OnScrollListener {
// The view is not scrolling. Note navigating the list using the trackball counts as being in the idle state since these transitions are not animated.
public static int SCROLL_STATE_IDLE = 0;
//The user is scrolling using touch, and their finger is still on the screen
public static int SCROLL_STATE_TOUCH_SCROLL = 1;
//The user had previously been scrolling using touch and had performed a fling. The animation is now coasting to a stop
public static int SCROLL_STATE_FLING = 2;
// Callback method to be invoked while the list view or grid view is being scrolled. If the view is being scrolled, this method will be called before the next frame of the scroll is rendered. In particular, it will be called before any calls to
public void onScrollStateChanged(AbsListView view, int scrollState);
// Callback method to be invoked when the list or grid has been scrolled. This will be called after the scroll has completed
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount);
}
列表滑动状态的变化
TOUCH_SCROLL、FLING、IDLE 三个状态对应的列表滑动操作如下
- TOUCH_SCROLL:手指滑动 List 阶段,但是手指没有离开屏幕,这时候上下滑动都是 TOUCH_SCROLL
- FLING:手指滑动 List 后抬手到 List 停止的阶段(必须有一个上滑或者下滑的速度,否则不会进入 Fling)
- IDLE:List 停止阶段
这三个状态的变化情况如下
- 手指滑动列表,停止后松手:IDLE -> TOUCH_SCROLL -> IDLE
- 手指滑动列表,松手后列表继续滑动,然后停止:IDLE -> TOUCH_SCROLL -> FLING -> IDLE
列表的 Fling 曲线计算
Fling 触发之后,每一帧都会调用 update 函数来更新 distance 和 mCurrVelocity,所以我们只需要监听 mCurrVelocity 的值,超过一定的阈值,就可以回调给 Appframeworks/base/core/java/android/widget/OverScroller.java
boolean update() {
final long time = AnimationUtils.currentAnimationTimeMillis();
final long currentTime = time - mStartTime;
double distance = 0.0;
switch (mState) {
case SPLINE: { // Fling 状态
final float t = (float) currentTime / mSplineDuration;
final int index = (int) (NB_SAMPLES * t);
float distanceCoef = 1.f;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/de97253b969b30c04fad37c926fe9a45.jpeg)
最后
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长。而不成体系的学习效果低效漫长且无助。时间久了,付出巨大的时间成本和努力,没有看到应有的效果,会气馁是再正常不过的。
所以学习一定要找到最适合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。
如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。
如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!