本文共 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/+/1645426void 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!