背景

技术群里的小伙伴发了一条微博, https://weibo.com/1808884742/IApbpEVQr, 博主 @王波粒 发现, Mate 30 Pro 有个很特别的现象(建议先去看一下视频)

这个视频描述和底下的猜测都不对,我这边总结一下这个现象:微博这个 App 在华为的手机上,在主页列表上下滑动的情况下依然可以流畅加载图片,而同一个版本的微博客户端,安装到其他手机上,在主页列表上下滑动的情况下,则必须要等到滑动停止之后才会加载图片

这个现象有什么特别呢 ?

  1. 从技术上讲,滑动列表停止后再加载图片是目前列表滑动优化中一个比较常见的优化项,很多主流 App 也都是这么做的 ,做这种处理主要是因为 如果在列表滑动的时候,碰到图片视频就加载,那么会加载很多无用的图片&&视频,浪费资源不说,还可能会影响真正用户看到的图片的加载速度 (加载一般都有并行上限和队列,队列里面无效的图片太多,后来的图片就得排队等待)。这里比较 特别的就是同一个版本的微博 APK,在华为的机型上与在其他机型上表现不一致,作为一个系统优化工程师,这个还是值得去搞清楚的(大胆猜测是微博针对华为的机型做了优化),那么这个优化的内容是什么?

  2. 从用户体验的角度来讲,列表滑动的同时加载图片,用户可以更早地看到图片,减少图片占位白图的显示时间,可以提升滑动的体验

  3. 第三个现象就得认真体验才会感觉到:华为手机上的微博在松手后的滑动曲线和其他手机上的微博在松手后的滑动曲线是不一样的,华为的微博列表松手后的滑动曲线速度更慢,更柔和,结束的时候也不会太突兀,与系统默认的列表滑动曲线明显不一样

上面三个是从现象上来说的,下面就从技术上来验证,从最后的结果来看,华为和微博的合作毫无疑问是很成功的,可以作为一个案例推广到其他头部 App,同时作为 Android 开发者,对华为这种非常细致的体验优化真的是非常敬佩

背景备注

  1. 由于 “列表滑动的同时加载图片” 这个功能由微博官方服务器控制,可以随时开启或者关闭,所以文章中所说的 “同一个版本的微博客户端,安装到其他手机上,在主页列表上下滑动的情况下,则必须要等到滑动停止之后才会加载图片” 这个现象在 “列表滑动的同时加载图片” 这个功能开启后,现象就会变成 “主页列表上下滑动的时候就会加载图片”

  2. 在 2020-6 月左右分析这个问题的时候,“列表滑动的同时加载图片” 这个功能还是关闭的,只有华为手机做了优化才有效果,其他手机是 “滑动停止之后才会加载图片”

  3. 在 2020-8 月再看这个问题的时候,“列表滑动的同时加载图片” 这个功能在其他手机上已经开启

  4. 华为的 PerfSDK 还有效果么?答案是有,具体分析可以看下文,因为有了这个 SDK,不仅对微博有好处(减少图片加载个数),对华为也有好处(提升微博主页列表在华为手机上的滑动体验,即 Fling 曲线优化) ;而粗暴开启 “列表滑动的同时加载图片” 的其他手机,如果性能不足,开启后反而会增加卡顿出现的概率(微博官方应该有性能监控数据可以看到)

  5. 反编译的微博版本:10.8.1

结论先行

‘’微博这个 App 在华为的手机上,在主页列表上下滑动的情况下依然可以流畅加载图片 ‘’这个现象是因为华为和微博做了联合优化,主要是为了优化微博列表滑动时候的用户体验,其优化点如下

  1. 华为提供了一个简单的接口打包成 SDK 提供给微博,这个接口可以让微博的列表监听到列表的当前速度(Velocity),在速度高于阈值或者低于阈值的时候,都会及时通知 App

  2. 微博拿到这个速度回调之后,就可以根据列表的滑动速度来决定是否要在滑动过程中加载图片,一旦列表的滑动速度低于设定的阈值,就开启图片加载;一旦列表的滑动速度高于设定的阈值,就关闭图片加载

  3. 华为检测到这个应用使用了 SDK,就可以将优化过后的滑动曲线应用在这个 App 的列表 Fling 阶段,提升用户体验

对细节感兴趣的同学可以继续阅读,有能力的同学看完后可以修改 Framework 相关代码,编译一个 SDK,然后自己写个 Demo 接入 SDK,就可以打通我下面所说的所有内容了,我自己在 AOSP 的代码上实现了一遍,Demo 也可以正常运行,有兴趣可以跟我私下交流

微博+华为是怎么优化?

现象分析

我们在滑动微博列表的时候,一个滑动操作主要由下面三部分组成

  1. 手指接触屏幕,上下滑动微博主页列表,但是手指 没有离开屏幕 ,这个阶段我们称之为阶段一,技术术语为 SCROLL_STATE_TOUCH_SCROLL

  2. 手指上下滑动的时候 离开屏幕 (必须有一个上滑或者下滑的速度),微博列表有了一个惯性,根据惯性的方向继续滑动,这个阶段我们称之为阶段二,技术术语为 SCROLL_STATE_FLING

  3. 列表惯性滑动后停止,这 个 阶段我们称之为阶段三 , 技术术语为  SCROLL_STATE_I DLE

而华为和微博的优化主要在阶段一和阶段二

阶段一优化

  1. 优化前:只要手指不离开屏幕,图片加载功能关闭

  2. 优化后:只要手指不离开屏幕,列表就不会滑动太快,这时候图片加载功能开启

阶段二优化

  1. 滑动图片加载优化

    1. 列表滑动速度太快,这时候图片加载功能关闭

    2. 列表滑动速度掉落到一个阈值,图片加载功能开启

    3. 优化前:只要列表滑动不停止,图片加载功能关闭

    4. 优化后:图片加载功能是否开启取决于当前列表滑动的速度

  2. 列表 Fling 曲线优化

    1. 优化前:列表滑动的曲线是默认值,滑动时间比较短,停止的时候比较突兀,不柔和

    2. 优化后:列表滑动的曲线是华为经过优化的,滑动时间比较长,停止的时候比较柔,不突兀,比较接近 iPhone 的列表滑动曲线

技术分析

技术分析的代码主要来源于微博 apk 的反编译,微博版本 10.8.1,通过反编译的代码可以看到, 微博主页在初始化的时候,会接入华为提供的 PerfSDK,从而获得监听列表滑动速度的能力

阶段一优化的技术分析

列表的 ScrollStateChange 是标识列表状态的一个回调,微博在 ScrollStateChange 这个回调中会根据当前的状态来决定是否加载图片, 从下面的代码逻辑来看

  1. 滑动图片加载优化生效 的时候,如果 State != 2,那么就允许 ImageLoader 加载图片,State 为 2 也就是 SCROLL_STATE_FLING,熟悉列表滑动的同学应该知道,SCROLL_STATE_FLING  就是 滑动列表的时候手指松手后列表继续滑动的那一段 ,叫 fling,毕竟只有 fling 的时候才有 Velocity,松手后会根据这个值的大小计算滑动曲线和滑动时长

  2. 滑动图片加载优化不生效 的时候,就到了常规的列表滑动优化:即列表停止之后才开始加载图片 :State !=0,0 即 SCROLL_STATE_IDLE

阶段二优化的技术分析

微博的主页在初始化的时候,会给首页的 ListView 注册一个 HwPerfVelocityCallback,从名字可以看出来,这个回调是监听 Velocity 的,也就是滑动的速度,两个回调:

  1. HwPerfonVelocityDownToThreshold : 当速度降低到阈值之后,打开 ImageLoader 的图片加载功能

  2. HwPerfonVelocityUpToThreshold:当速度升高到阈值之后,关闭 ImageLoader 的图片加载功能

下图为反编译后的源码

至于滑动曲线,则需要查看华为的 Framework 的代码,由于代码量比较大,这里只贴一下 OverScroller.java 中的 update 方法,具体感兴趣的可以自己去翻一番华为的 Framework 代码

计算 Distance 的代码

计算 Velocity 的代码

关于滑动曲线的解释,大家可以看这一篇知乎回答,其中对比了 iOS 和 Android 的滑动曲线的不同 :为什么 iOS 的过渡动画看起来很舒服?

其他厂商处理

上面图中代码最后一段还有一个判断开关, 如果 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 的点击启动应用操作

延伸阅读

列表滑动图片加载的性能考虑

前文有提到这个问题,滑动的时候进行图片加载主要有两个问题:

  1. 如果用户滑动非常快,比如是想找昨天发的某个微博,那么今天发的所有的带图片的微博在用户滑动的时候是没必要加载的,因为用户的目标不是这些图片,而 App 去加载这些图片,而程序员是不会为用户提前加载你未看到的数据,因为加载过多的数据不仅容易发生数据复用、缓存过多、内存溢出等错误,还会对服务器造成不必要的资源请求。

  2. 如果用户滑动非常快,那么图片加载队列势必有许多无效的资源(对这一刻的用户来说),而用户真正想看的图片反而排在了加载队列后面,造成加载速度变慢,也会影响用户的体验

滑动中加载图片最大的风险其实就是造成卡顿,因为图片加载本身就是一个比较重的操作,而高帧率的手机上,一帧的时间被压缩到很短,任何小的不确定性都有可能造成卡顿

所以厂商+应用的这个优化:快速滑动不加载图片,慢速的时候再加载,然后优化滑动曲线 ,其实对厂商和应用都是非常有益处的

列表滑动监听背景知识

下面的 AbsListView 的 OnScrollListener 里面标注了列表滑动的三个状态

  1. 滑动停止:SCROLL_STATE_IDLE

  2. 手指在屏幕上滑动:SCROLL_STATE_TOUCH_SCROLL

  3. 手指离开屏幕,列表靠惯性继续滑动:SCROLL_STATE_FLING

两个回调

  1. 列表状态变化时的回调 :onScrollStateChanged

  2. 列表滑动时候的回调: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 三个状态对应的列表滑动操作如下

  1. TOUCH_SCROLL:手指滑动 List 阶段,但是手指没有离开屏幕,这时候上下滑动都是 TOUCH_SCROLL

  2. FLING:手指滑动 List 后抬手到 List 停止的阶段(必须有一个上滑或者下滑的速度,否则不会进入 Fling)

  3. IDLE:List 停止阶段

这三个状态的变化情况如下

  1. 手指滑动列表,停止后松手:IDLE  -> TOUCH_SCROLL -> IDLE

  2. 手指滑动列表,松手后列表继续滑动,然后停止:IDLE  -> TOUCH_SCROLL -> FLING -> IDLE

列表的 Fling 曲线计算

Fling 触发之后,每一帧都会调用 update 函数来更新 distance  和 mCurrVelocity,所以我们只需要监听 mCurrVelocity  的值,超过一定的阈值,就可以回调给 App

frameworks/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; float velocityCoef = 0.f; if (index < NB_SAMPLES) { final float t_inf = (float) index / NB_SAMPLES; final float t_sup = (float) (index + 1) / NB_SAMPLES; final float d_inf = SPLINE_POSITION[index]; final float d_sup = SPLINE_POSITION[index + 1]; velocityCoef = (d_sup - d_inf) / (t_sup - t_inf); distanceCoef = d_inf + (t - t_inf) * velocityCoef; } distance = distanceCoef * mSplineDistance; mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000.0f; break; } case BALLISTIC: { // 列表滑到 底 之后的 拉伸阶段 final float t = currentTime / 1000.0f; mCurrVelocity = mVelocity + mDeceleration * t; distance = mVelocity * t + mDeceleration * t * t / 2.0f; break; } case CUBIC: { // 列表滑到底拉伸 之后的 回弹阶段 final float t = (float) (currentTime) / mDuration; final float t2 = t * t; final float sign = Math.signum(mVelocity); distance = sign * mOver * (3.0f * t2 - 2.0f * t * t2);  mCurrVelocity = sign * mOver * 6.0f * (- t + t2);  break; } } mCurrentPosition = mStart + (int) Math.round(distance); return true;
}

厂商应用联合优化

微博这个优化就是厂商和应用之间联合优化的一个案例,应用对用户体验的极致追求,让这种合作在未来会变得更加频繁,像微信、快手、抖音这些...

下面这个招聘是拼多多的一个 JD,看职位描述是专门对接厂商的优化,也可以看出应用对厂商的合作越来越重视。之前厂商和应用是魔高一尺道高一丈的关系,互相攻防导致最终体验受损的还是用户;而现在这种厂商和应用合作的关系,不仅提升了双方的体验,也会带动 Android 生态圈向好的方面去发展

技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。

推荐阅读:

音视频面试基础题

OpenGL ES 学习资源分享

一文读懂 YUV 的采样与格式

OpenGL 之 GPUImage 源码分析

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

华为手机刷微博体验更好?技术角度的分析和思考相关推荐

  1. 华为手机刷微博体验更好?技术角度的分析和思考,Android基础72问

    在 2020-6 月左右分析这个问题的时候,"列表滑动的同时加载图片" 这个功能还是关闭的,只有华为手机做了优化才有效果,其他手机是 "滑动停止之后才会加载图片" ...

  2. 拉卡拉手机刷卡器音频通讯技术原理初步分析

    拉卡拉手机刷卡器音频通讯技术原理初步分析  kimmking@163.com http://blog.csdn.net/kimmking/article/details/8712161 1.      ...

  3. 微博超话显示服务器有点累,刚才手机刷微博,刷新了一下首页... - @菜菜_fz 的微博精选 - 微博国际站...

    刚才手机刷微博,刷新了一下首页,哗啦啦连着十四条微博都是那个深蓝色的"情况通报"图,空前一致. 在还没有搞清楚究竟是怎么回事的那一瞬间,我还以为是微博又崩出新境界了.崩得所有人的微 ...

  4. 华为手机刷屏老显示服务器出错,华为手机刷机出现update exception emmc is readonly解决方法...

    华为手机刷机出现update exception emmc is readonly解决方法 大家在给华为手机强制sd卡刷机失败时提示 updat exception EMMC is readonly ...

  5. 鸿蒙系统适配机型_余承东:华为手机鸿蒙系统体验比安卓更好,主流应用已完成适配...

    在近日召开的IFA2019大会上,华为无疑是最闪耀的那颗星,为我们带来了全球首款内置5G基带的5G旗舰芯片麒麟990,把整个5G手机的技术革新向前推进了一步.不过在麒麟990发布之后华为终端CEO余承 ...

  6. 电大数据库应用技术形考3_华为荣耀路由3体验:Wi-Fi6技术成熟应用,真正的平民好路由...

    路由器:大家日常上网必不可少的重要工具,而路由器的好坏,将直接决定网络信号强弱.网速快慢,直接影响大家的上网体验.随着Wi-Fi 6技术的成熟和千兆网络的普及,众多路由器和手机厂商新发布的高端产品也纷 ...

  7. android 开发 华为手机型号,华为手机用户可以体验Android P了!9款华为机型开放EMUI 9.0升级...

    [天极网手机频道]根据往年的惯例,华为手机应该会在国内首发Android P.不过令人意外的是,这一次一加手机抢先为用户升级了Android P系统.不过华为毕竟是国产手机力的老大,基于Android ...

  8. android auto荣耀10,手机资讯导报:这8款华为手机能升级“很吓人的技术”

    科技.数码.互联网新闻如今都成为了大众所关注的热点了,因为在我们的生活当中如今已经是处处与这些相关了,不论是手机也好,电脑也好,又或者是智能手表也好,与之都相关,那么今天小编也是为大家来推荐一篇关于互 ...

  9. 旧的华为手机刷Android9,华为老机型内测安卓9.0,这更新速度快到没谁了!

    原标题:华为老机型内测安卓9.0,这更新速度快到没谁了! 安卓8.0的全面普及还没过去多久,如今安卓9.0又来了.在8月7日的时候,谷歌正式宣布发布安卓9.0系统正式版本,并且取名代号为Pie,而且还 ...

  10. 华为手机刷linux系统,华为Harmony OS 2.0手机Beta版刷机包流出:电脑助手一键刷入/手动...

    原标题:华为 Harmony OS 2.0 手机 Beta 版刷机包流出,Mate 30 Pro 可用(附下载地址) 月24日消息 IT360 论坛今日放出了三款 Mate 30 Pro 机型的华为鸿 ...

最新文章

  1. KMP----next数组 最长相同前后缀 递归求法解释
  2. live555工程建立与调试
  3. Gym - 100625E Encoded Coordinates 矩阵快速幂
  4. 开发用户导航栏和权限信息接口
  5. 【LeetCode】TreeNode类实现解析(java实现)
  6. steam成就解锁器_MC技术指南如何使用SAM成就解锁?
  7. 7000 亿!华为正式宣布,全世界为之颤抖!
  8. 3S基础知识:在VC++中嵌入MapX的集成二次开发
  9. MP3每一帧的采样个数和采样率如何理解?(神文)
  10. 如何在EXCEL表格中加斜线表头
  11. 【调剂】长江大学张菲菲教授招收硕士生
  12. 【渝粤教育】国家开放大学2018年秋季 2129T药物化学 参考试题
  13. 计算机软件实习每日学习打卡(2)20201203
  14. [经典之作]vml经典之作
  15. 图像处理:Yxy、XYZ颜色空间介绍及RGB转换公式
  16. 我以过来人的身份告诉你手工测试人员如何转测试开发?
  17. 骑行318、 2016.7.25
  18. ANSYS APDL学习(9):命令流报错No *Do trips needed, enter *ENDDO .解决办法
  19. 鼠标滚轮失灵解决方法2种实测
  20. 第一范式、第二范式、第三范式以及BC范式

热门文章

  1. S4 HANA CO和FI自动集成:成本中心分配分摊业务实践-KSV5/KSU5
  2. 11 Django REST Framework 针对基于类的视图添加 @csrf_exempt
  3. 腾讯视频下载的qlv格式知否怎么转换成MP4
  4. 在未来最容易被淘汰的12个职业和最难被淘汰的12个职业
  5. node.js+uni计算机毕设项目鲸落图书商城小程序LW(程序+小程序+LW)
  6. [Typecho]更换头像源及显示QQ显示头像
  7. kubernetes in action读书笔记(四)ConfigMap、Secret、滚动升级、downwardAPI、Deployment、Statefulset
  8. Postgis使用工具raster2pgsql批量导入栅格数据(二)
  9. 免费的caj转word批量转换方法
  10. uniapp如何引入全局js