微信越滑越卡
发布日期:2021-06-30 18:50:45 浏览次数:4 分类:技术文章

本文共 5460 字,大约阅读时间需要 18 分钟。

这篇文章是我的朋友小二哥写的,他解决了一个问题并提交了一个补丁给google合入,当然了,这也是他第一次提交补丁被google采纳。我觉得这才是做技术的样子,二哥也很喜欢篮球,,为二哥吹一波。哈哈。

关于这次补丁的提交如下文,喜欢的同学可以好好看看。


背景

在一个已经加载完成很长的微信聊天记录中,持续不断的滑动,慢慢的微信会越滑越卡。

一、卡顿的原因分析

Choreographer#doFrame的animation中会堆积大量的Callback-AbsListView#FlingRunnable

从而导致了最后这一帧的绘制超时,导致了卡顿。

二、FlingRunnable堆积的原因

一次滑动会触发一个Down事件,多个Move事件,一个Up事件。

从下图可以发现,这次滑动,导致animation的FlingRunnable从3个增加到了4个

看看这4个是怎么来的:

3个是来自于之前的FlingRunnable,新增的一个来自于Up事件触发的。

三、代码分析

3.1 onTouchDown

Touch Down事件会触发mFlingRunnable.flywheelTouch()

private void onTouchDown(MotionEvent ev) {        ...        if (mTouchMode == TOUCH_MODE_OVERFLING) {        ...        } else {            ...            if (!mDataChanged) { //ListView的数据没有更新                if (mTouchMode == TOUCH_MODE_FLING) {//ListView处于Fling的状态                    // Stopped a fling. It is a scroll.                    createScrollingCache();                    mTouchMode = TOUCH_MODE_SCROLL;                    mMotionCorrection = 0;                    motionPosition = findMotionRow(y);                    mFlingRunnable.flywheelTouch();//跳转到3.1.1            ...

3.1.1 mFlingRunnable.flywheelTouch

flywheelTouch会postdelay一个mCheckFlywheel延迟40ms。

当mCheckFlywheel被执行的时候,会去判断ListView当前的滑动速度。

如果Math.abs(yvel) >= mMinimumVelocity,将会再次postdelay一个mCheckFlywheel,让ListView继续滑动一段时间。
如果Math.abs(yvel) < mMinimumVelocity,将会endFling(),这就是为什么ListView滑动之后慢慢停止的逻辑。

endFling中将会removeCallbacks(this)和removeCallbacks(mCheckFlywheel)

private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds        void flywheelTouch() {            postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);        }        private final Runnable mCheckFlywheel = new Runnable() {            @Override            public void run() {                ...                if (Math.abs(yvel) >= mMinimumVelocity                        && scroller.isScrollingInDirection(0, yvel)) {                    // Keep the fling alive a little longer                    //yvel > mMinimumVelocity继续滑动,将mCheckFlywheel在推迟40ms                    postDelayed(this, FLYWHEEL_TIMEOUT);                } else {                    endFling();                    mTouchMode = TOUCH_MODE_SCROLL;                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);                }            }        };        void endFling() {            ...            removeCallbacks(this);            removeCallbacks(mCheckFlywheel);            ...        }

3.2 onTouchUp

在onTouchUp中将会执行mFlingRunnable.start(-initialVelocity),从而postOnAnimation(this);

private void onTouchUp(MotionEvent ev) {        switch (mTouchMode) {        ...        case TOUCH_MODE_SCROLL:        ...            if (!dispatchNestedPreFling(0, -initialVelocity)) {                if (mFlingRunnable == null) {                    mFlingRunnable = new FlingRunnable();                }                reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);                mFlingRunnable.start(-initialVelocity);//跳到下面的start方法                dispatchNestedFling(0, -initialVelocity, true);            } else {                mTouchMode = TOUCH_MODE_REST;                reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);            }        ...    }    void start(int initialVelocity) {        ...        postOnAnimation(this);        ...    }

3.3 FlingRunnable#run

FlingRunnable的run方法,如果ListView处于TOUCH_MODE_SCROLL或者TOUCH_MODE_FLING的状态,并且还有更多的内容,就会继续postOnAnimation(this)

@Overridepublic void run() {    switch (mTouchMode) {    default:        endFling();        return;    case TOUCH_MODE_SCROLL:        if (mScroller.isFinished()) {            return;        }        // Fall through    case TOUCH_MODE_FLING: {        ...        if (more && !atEnd) {            if (atEdge) invalidate();            mLastFlingY = y;            postOnAnimation(this);        } else {            ...        }        break;    }    ...    }}

小结:

onTouchDown会postdelay 40ms一个mCheckFlywheel,mCheckFlywheel将会检查ListView是否应该停止

onTouchUp会postOnAnimation(FlingRunnable),让ListView开始Fling起来。

每一个FlingRunnable又会再次触发一个postOnAnimation(FlingRunnable)。

四、对比分析

4.1 为什么Google Pixel不存在这个BUG

原来Google Pixel每次滑动Down和Move事件的间隔绝大多数情况下大于40ms,从而导致mCheckFlywheel中endFling可以在持续的滑动中被有效的执行,这样子就不会导致FlingRunnable的堆积

4.2 为什么我们的手机会存在这个BUG

原来我们的手机TP采样率比较高,接近180hz,Down和Move的时间间隔竟然在9ms左右,从而导致了mCheckFlywheel永远被postdelay,无法有效的执行endFling,这样子就导致了FlingRunnable的堆积

五、解决方案

在FlingRunnable.start中调用postOnAnimation之前removeCallbacks(this),避免FlingRunnable的堆积

这个方案已经被merge进了Android官方主分支中:
https://android-review.googlesource.com/c/platform/frameworks/base/+/1645426

void start(int initialVelocity) {    int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;    mLastFlingY = initialY;    mScroller.setInterpolator(null);    mScroller.fling(0, initialY, 0, initialVelocity,            0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);    mTouchMode = TOUCH_MODE_FLING;    mSuppressIdleStateChangeCall = false;    removeCallbacks(this);//修复的patch    postOnAnimation(this);    if (PROFILE_FLINGING) {        if (!mFlingProfilingStarted) {            Debug.startMethodTracing("AbsListViewFling");            mFlingProfilingStarted = true;        }    }    if (mFlingStrictSpan == null) {        mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");    }}

总结

这是我作为android工程师第一次成功提交代码到Android官方主分支,还是值得纪念的,可惜提交的账户不是我自己的,而是公司账户,因为自己的账户很有可能Google工程师不会review你的提交。有了一次就会有第二次,期待我下次继续为Android开源代码贡献代码。


推荐阅读:

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

转载地址:https://linus.blog.csdn.net/article/details/115410117 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:大佬为何可以把单片机描述得如此形象生动?
下一篇:无法连接上 cn.archive.ubuntu.com:80 (123.129.214.98)。 - connect (111: 拒绝连接)

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年04月20日 09时30分44秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

Boundary loss 损失函数 2019-04-30
神经网络调参实战(一)—— 训练更多次数 & tensorboard & finetune 2019-04-30
tensorflow使用tensorboard进行可视化 2019-04-30
神经网络调参实战(二)—— activation & initializer & optimizer 2019-04-30
凸优化 convex optimization 2019-04-30
数据库索引 & 为什么要对数据库建立索引 / 数据库建立索引为什么会加快查询速度 2019-04-30
IEEE与APA引用格式 2019-04-30
research gap 2019-04-30
pytorch训练cifar10数据集查看各个种类图片的准确率 2019-04-30
Python鼠标点击图片,获取点击点的像素坐标 2019-04-30
路径规划(一) —— 环境描述(Grid Map & Feature Map) & 全局路径规划(最优路径规划(Dijkstra&A*star) & 概率路径规划(PRM&RRT)) 2019-04-30
神经网络调参实战(四)—— 加深网络层次 & 批归一化 batch normalization 2019-04-30
数据挖掘与数据分析(三)—— 探索性数据分析EDA(多因子与复合分析) & 可视化(1)—— 假设检验(μ&卡方检验&方差检验(F检验))&相关系数(皮尔逊&斯皮尔曼) 2019-04-30
RRT算法(快速拓展随机树)的Python实现 2019-04-30
路径规划(二) —— 轨迹优化(样条法) & 局部规划(人工势能场法) & 智能路径规划(生物启发(蚁群&RVO) & 强化学习) 2019-04-30
D*算法 2019-04-30
强化学习(四) —— Actor-Critic演员评论家 & code 2019-04-30
RESTful API 2019-04-30
优化算法(四)——粒子群优化算法(PSO) 2019-04-30
数据挖掘与数据分析(三)—— 探索性数据分析EDA(多因子与复合分析) & 可视化(2)——回归分析(最小二乘法&决定系数&残差不相关)&主成分分析&奇异值分解 2019-04-30