技术群里的小伙伴发了一条微博, https://weibo.com/1808884742/IApbpEVQr, 博主 @王波粒 发现, Mate 30 Pro 有个很特别的现象(建议先去看一下视频)
这里讨论的是 2020 年观察到的一组差异:部分华为样机上的微博会在主页列表滑动中显示图片,而当时部分其他样机要等到停止后才显示。 这个现象不能推广到当前所有机型;相同客户端版本也可能受到配置、缓存和系统实现的影响。
同一个版本的微博,在不同手机上表现不一样,作为系统优化工程师,我还是想搞清楚的:是不是微博专门做了适配?具体改了什么?下面就从技术的角度来分析这个现象,看看我们能从里面学到什么。
历史版本说明(2026-10-14 核查):本文是 2020 年的案例,原文标注反编译微博 10.8.1。下面保留旧图和当时体验;没有原 APK 哈希、完整华为固件/PerfSDK 版本、运行 trace 和对照统计,不能据此评价今天所有华为或其他品牌设备。图片加载控制、厂商滚动物理参数和主观手感需要分别验证。

这个现象有什么特别呢?
- 列表快速移动时延后部分图片工作,是一种取舍,不是所有 App 的默认最佳策略。下载、解码、变换、缓存和显示是不同阶段,暂停其中一个阶段不等于全部停止。
- 图片在滑动中出现不等于此时才下载,也可能来自缓存或先前任务的回填。滑动中显示图片可减少占位图时间,但也可能增加当时的 CPU、GPU、内存和网络压力,是否改善体验要看用户最终可见内容与帧时序。
- 我当时感觉华为样机松手后的曲线更柔和。这是当时的主观观察;没有相同初速度、内容、刷新率和固件条件下的位移/速度曲线,不能量化为所有机型都更慢、更长或更接近 iOS。
下面是我当时对合作方向的评价,不作为本次性能测量结果:
上面三个是从现象上来说的,下面就从技术上来验证,从最后的结果来看,华为和微博的合作从结果看是很成功的,可以作为一个案例推广到其他头部 App,同时作为 Android 开发者,对华为这种非常细致的体验优化真的是非常敬佩
背景备注
- 我在 2020 年 6 月分析时,观察到部分非华为样机表现为停止后才加载;8 月再次观察时,也能在滑动中显示图片。这个时间线是历史观察,不是当前服务端开关状态。
- 反编译图出现
feed_scroll_loadimage_enable和灰度配置读取,是功能配置路径的证据;没有当时服务端响应,不能证明开关何时、对哪些账号/设备全量开启,也不能保证修改立刻生效。 - 同一 APK 可能因配置、缓存、系统版本和热修复走不同分支。截图里还有 PatchProxy 检查,不能只看版本号就认为运行代码相同。
- 原文样本标签是微博 10.8.1;不同截图中的混淆方法编号并不一致,不能把编号当作稳定 API 或精确构建标识。
结论先行
现有图展示了两部分线索:微博注册速度阈值回调来控制 ImageLoader;华为 Framework 片段包含另一套滚动位移和速度计算。回调如何控制加载,可以从图中读出;“一接入 SDK 就必定启用新曲线、并保证更流畅”,还缺完整调用链与运行测量。
HwPerfVelocityCallback报告速度与阈值的关系,所示方法没有传回每一帧的数值速度。- 低于阈值的回调调用 resume,高于阈值的回调调用 pause。实际会暂停哪些任务、在途请求是否继续,要看该版 ImageLoader 实现,截图本身没有回答。
- Framework 图包含 SmartSlideOverScroller 分支;生效条件、设备范围与 SDK 的连接关系,仍需对应固件源码或 trace 核对。
以下是我当时的 AOSP/Demo 实践记录;本仓库没有附上该 Demo,不能算本次重新复现:
对细节感兴趣的同学可以继续阅读,有能力的同学看完后可以修改 Framework 相关代码,编译一个 SDK,然后自己写个 Demo 接入 SDK,就可以打通我下面所说的所有内容了,我自己在 AOSP 的代码上实现了一遍,Demo 也可以正常运行,有兴趣可以跟我私下交流
微博+华为是怎么优化?
现象分析
以 AbsListView 的普通手势为例,可以观察到三个状态:
SCROLL_STATE_TOUCH_SCROLL = 1:手指拖动列表。仅按下但尚未超过触摸阈值,不一定已进入这个状态。SCROLL_STATE_FLING = 2:符合条件的松手惯性滚动。除了初速度,还要考虑最小 fling 速度、边界和嵌套滚动是否消费了手势。SCROLL_STATE_IDLE = 0:当前没有滚动。原文 SCROLL_STATE_I DLE 的空格是笔误。
手指拖动同样有速度,甚至可能很快。“只有 fling 才有 Velocity”“手指不离开就不会滑得太快”都不成立;这里的策略只是对不同状态选择不同处理。
阶段一优化
原图里,相关优化标志为 false 时,非 IDLE 状态调用 pause;标志为 true 时,非 FLING 状态调用 resume。因此 TOUCH_SCROLL 可以继续加载,但这不是保证不会卡顿,也不能推断所有网络请求都会立刻开始。
阶段二优化
快速 fling 时暂停、减速后恢复,是原速度回调的意图。它能否减少请求数量、降低掉帧,需要检查缓存、取消机制和实际执行分支,不能由两次函数调用直接计算收益。
另一部分是曲线调整。AOSP 默认 fling 本来就使用非线性 spline,并非匀速直线;更长或更柔和的曲线也不自动意味着帧渲染更快。曲线手感与计算/显示是否按时完成,是不同问题。
技术分析
以下按原图逐段看。它们是反编译和 Framework 的局部片段,缺少完整的 SDK 初始化条件、ImageLoader 源码和固件版本。
阶段一优化的技术分析
这一段 onScrollStateChanged 的图片加载分支可整理为:
| 相关优化标志 | 状态 | 图中直接动作 |
|---|---|---|
| true | IDLE / TOUCH_SCROLL | resume |
| true | FLING | 此处分支没有直接调用 pause 或 resume |
| false | TOUCH_SCROLL / FLING | pause |
| false | IDLE | resume |

所以不能把第一种情况写成“state == 2 就暂停”;它在这里保留已有加载状态,之后还要看速度回调。图中还处理 async_card、视频预加载等工作,不能把整段代码的性能效果都归给图片开关。PatchProxy 若接管调用,这段可见的普通分支也可能不执行。
阶段二优化的技术分析
原图把 HwPerfSpeedLoader.HwPerfVelocityCallback 传给列表相关工具方法:
HwPerfonVelocityDownToThreshold():调用 ImageLoader.getInstance().resume()。HwPerfonVelocityUpToThreshold():调用 ImageLoader.getInstance().pause()。

这些是阈值事件,不是公开数值流。图中没有展示阈值大小、单位、是否初始即回调或是否有滞回,不能补出一个适用于所有屏幕密度和刷新率的固定阈值。
原文提供的华为 Framework update 片段调用了 SmartSlideOverScroller 的 getUpdateDistance/getUpdateVelocity,并有位移截止处理:

计算 Distance 的片段:

计算 Velocity 的片段:

速度表达式含指数项和归一化时间,但截图没有给出完整参数来源、入口判断与固件 tag。它能说明存在不同实现,不能单凭形式证明总时长一定大于 AOSP、所有应用都启用,或“与 iPhone 一样”。距离计算还包含修正与截止逻辑,不能只拿一条指数式代表整套曲线。
原文引用的关于 iOS 过渡动画的讨论保留作背景阅读;它不替代当前样机的滚动测量。
其他厂商处理
下面的 boolean a 是工具注册方法的结果;false 可能表示不支持、初始化失败或其他条件不满足,不能仅凭这个值断定手机品牌。普通分支的回退条件是 Build.VERSION.SDK_INT >= 28(Android 9)且 FeedAbManager 开关为 true,两个条件缺一不可:

FeedAbManager 中可见多个 Feature 读取入口:

对应方法读取 feed_scroll_loadimage_enable:

灰度配置片段区分 SYNC_WITH_SERVER、REMAIN_UNCHANGE_INAPPLIFECYCLE 和 NOTCACHED 等策略;不能把方法名称理解为每次回调都立即请求服务器。

这条回退路径可以允许滑动中加载,但图中未显示它获得了同样的速度阈值回调或厂商曲线。因此两个路径的行为和性能需要分别测,不能把“图片出现了”当作 SDK 生效证明。
最后一个问题:滑动点击
在 Android 10 AbsListView中,fling 期间 ACTION_DOWN 的拦截和触摸状态转换会让手势先参与停止/接管滚动,而不是普通 Item 点击。这可以解释一些场景需要先停再点;它不是所有 Android 列表、所有状态都必须点击两次的规则。
更长的 fling 可能延长可被中断的时间,但点击是否成功还取决于事件分发、目标稳定性、触摸阈值和控件实现。下面保留原文的手感比喻与当时系统比较,它们属于主观体验:
滑动(Fling 状态)的时间越短,列表越容易停下,用户点击列表越容易触发 Item 的点击,但是容易停止带来的问题就是不够柔和。想象你在粗糙的水泥地上滑出去一块石头,这石头没有滑动多久就会停止,不管是扔石头的你还是旁边看你扔石头的我,都不会觉得这有什么美感,但是没得选。这个的 代表其实就是 Android 原生的 Fling 曲线
滑动(Fling 状态)的时间越长,列表越不容易停下,用户点击列表越不容易触发 Item 的点击,如果曲线优化的好,给人的感觉就是很柔和,符合物理规律,想象你在光滑的冰面上滑出去一块冰,冰面越滑,冰块滑动的时间就越长,越不容易停下。这其中的极端代表就是 iOS 的 Fling 曲线。说 iOS 极端是因为,iOS 的滑动曲线调的太磨叽了,时间长不说,停的异常慢,很多时候你都需要点击一下列表让他先停止,然后再进行下一步的点击动作。而小米的 MIUI12 对这个也进行了调整,效果要比 iOS 好一些,如果再和三方进行类似华为和微博的合作,体验会更上一层楼
我当时借鉴 Launcher,提出过“完成 95% 或 98% 距离后允许点击”的设计想法,不是已经验证 MIUI 12/华为按该阈值落地。距离进度不等于剩余时间或瞬时速度;实际设计还应检查目标是否稳定、手指移动量和误触率,并保留正确的点击及无障碍语义,不能简单透传事件给正在移动的 Item。
延伸阅读
列表滑动图片加载的性能考虑
图片工作涉及请求调度、下载、解码、变换、内存/磁盘缓存、纹理上传和 View 更新。快速滚动时,可优先处理当前可见内容、限制预取窗口,并取消或降低已离屏请求的优先级;预加载本身是常见策略,不能笼统说“程序员不会加载用户未看到的数据”。
队列也不一定严格 FIFO。“前面 49 张没结束,第 50 张就必定等待”取决于并发、优先级、取消和缓存;列表复用是正常机制,错图通常需要检查请求与 Item 的绑定,而不是把复用本身当作错误。暂停后恢复还可能形成突发工作,应验证而非假定必然更省资源。
现代项目可以先使用公开滚动回调和图片库的生命周期/请求管理接口,不必依赖某个厂商私有 SDK。阈值策略应明确速度单位、取绝对值的方式、初始状态、暂停/恢复的滞回和离屏清理;不要通过反射读取 ListView 私有 Scroller。自己持有 OverScroller 时可按其公开接口读取状态,手势速度则可使用 VelocityTracker。
验收可结合 Macrobenchmark 的帧指标和 Perfetto,固定内容、缓存冷热、网络、账号/灰度、设备刷新模式及滑动输入,比较帧超时、可见图片出现时间、请求/字节数和内存,而不是只凭视频观感或平均 fps。本文没有这些对照数据,不报告新的收益数字。
列表滑动监听背景知识
AbsListView.OnScrollListener的接口摘要如下;注释按回调含义整理,省略完整文档:
1 | public interface OnScrollListener { |
onScrollStateChanged 反映状态变化,不是逐帧速度回调。onScroll 在滚动或布局更新后报告可见范围,滚动过程中会多次触发;官方注释中的 “after the scroll has completed” 不能误读成只在最后停稳时触发。AOSP 的 invokeOnItemScrollListener 调用位置也能验证这一点。
AndroidX 对照:RecyclerView.OnScrollListener(1.0.0 起)使用 IDLE、DRAGGING、SETTLING;SETTLING 不只代表手指松开的 fling,也可能是程序发起的平滑滚动。onScrolled 的 dx/dy 是本次位移,不是速度;布局改变可产生 dx=dy=0 的回调。若估算速度,需要时间间隔和有效样本,不能照搬 AbsListView 常量名称或仅用状态值判断快慢。
列表滑动状态的变化
典型手势可能是:
- 拖动后低速松手:IDLE → TOUCH_SCROLL → IDLE。
- 松手满足 fling 条件:IDLE → TOUCH_SCROLL → FLING → IDLE。
这不是穷尽的状态机:新的按下、边界、嵌套滚动、程序滚动都可能改变路径。手指未离屏也会由 VelocityTracker 估算速度;是否开始 fling 还需满足最小速度等条件。
列表的 Fling 曲线计算
下面补全 AOSP android-10.0.0_r1 OverScroller.SplineOverScroller.update() 的零时间/结束判断。它是 Framework 内部方法,依赖类中字段,不是可直接粘贴给 App 的完整类;也不是前面华为修改版的源码。
1 | boolean update() { |
SPLINE 对采样曲线插值,mSplineDistance / mSplineDuration * 1000 在该分支把像素/毫秒换为像素/秒。BALLISTIC 是越界后的减速阶段,CUBIC 是返回有效范围的回弹,不能直接等同于新系统的 EdgeEffect 拉伸动画。
更新由滚动调用链驱动,FlingRunnable 会继续安排后续工作;“每帧更新”描述的是预期调度,不保证每个屏幕刷新都执行一次。按经过时间计算位置,可在调度延迟后跳到对应进度。mCurrVelocity 是内部轴状态,不能假定所有分支都可直接作为统一的物理速度合同;公开 getCurrVelocity 返回 X/Y 速度向量的模,不提供方向。
厂商应用联合优化
微博这个优化就是厂商和应用之间联合优化的一个案例,应用对用户体验的极致追求,让这种合作在未来会变得更加频繁,像微信、快手、抖音这些…
下面这个招聘是拼多多的一个 JD,看职位描述是专门对接厂商的优化,也可以看出应用对厂商的合作越来越重视。之前厂商和应用是魔高一尺道高一丈的关系,互相攻防导致最终体验受损的还是用户;而现在这种厂商和应用合作的关系,不仅提升了双方的体验,也会带动 Android 生态圈向好的方面去发展

本文其他地址
微信公众号 - https://mp.weixin.qq.com/s/wJKOvU7CqP3vM0TG7rO66g
知乎专栏(求个赞) - https://zhuanlan.zhihu.com/p/191460094
关于我 && 博客
下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
- 博主个人介绍 :里面有个人的微信和微信群链接。
- 本博客内容导航 :个人博客内容的一个导航。
- 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
- Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。
一个人可以走的更快 , 一群人可以走的更远
