首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[063]微信越滑越卡

[063]微信越滑越卡

作者头像
王小二
发布2021-04-02 09:45:50
9130
发布2021-04-02 09:45:50
举报

背景

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

一、卡顿的原因分析

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)

@Override
public 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开源代码贡献代码。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 一、卡顿的原因分析
  • 二、FlingRunnable堆积的原因
    • 看看这4个是怎么来的:
    • 三、代码分析
      • 3.1 onTouchDown
        • 3.1.1 mFlingRunnable.flywheelTouch
          • 3.2 onTouchUp
            • 3.3 FlingRunnable#run
              • 小结:
              • 四、对比分析
                • 4.1 为什么Google Pixel不存在这个BUG
                  • 4.2 为什么我们的手机会存在这个BUG
                  • 五、解决方案
                  • 总结
                  相关产品与服务
                  腾讯云代码分析
                  腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档