BGABanner-Android控件开启无限轮播后 ,快速滑动,ANR现象分析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/lylddingHFFW/article/details/89212664

前言:作者已进行了优化点击查看详情
提bug记录:https://github.com/bingoogolapple/BGABanner-Android/issues/213

在使用大神(@bingoogolapple)的第三方控件时BGABanner-Android出现的两小个bug记录,issues中也有人遇到过,但无人回复,坐等大神更新,以下为个人处理方案,如有问题请指正

CSDN:https://blog.csdn.net/lylddingHFFW/article/details/89212664

一 使用场景:使用BGABanner控件,当其宽度较小时,比如只有20dp,这样是很容易滑动到左右边界的。默认ViewPager左右是保留1个Item。

复现bug1:开启无限轮播后,同时手指快速左滑动,一直滑动到viewpager Item不能移动,并松开时,出现anr。(复现概率很大)
复现bug2:开启无限轮播后,同时手指多次快速左滑动,可能出现停止无限轮播现象。(复现几率一般)

二 bug分析及处理方案
1.1 bug1分析:
在BGABanner类代码中,是利用onPageScrolled获取滑动的位置mPageScrollPosition。当ActionUP时在handleAutoPlayActionUpOrCancel中判断左滑还是右滑,来更新滑动位置。

@Override
public void handleAutoPlayActionUpOrCancel(float xVelocity) {
    if (mViewPager != null) {
        if (mPageScrollPosition < mViewPager.getCurrentItem()) {
            // 往右滑
            if (xVelocity > VEL_THRESHOLD || (mPageScrollPositionOffset < 0.7f && xVelocity > -VEL_THRESHOLD)) {
                mViewPager.setBannerCurrentItemInternal(mPageScrollPosition, true);
            } else {
                mViewPager.setBannerCurrentItemInternal(mPageScrollPosition + 1, true);
            }
        } else{
            // 往左滑
            if (xVelocity < -VEL_THRESHOLD || (mPageScrollPositionOffset > 0.3f && xVelocity < VEL_THRESHOLD)) {
                mViewPager.setBannerCurrentItemInternal(mPageScrollPosition + 1, true);
            } else {
                mViewPager.setBannerCurrentItemInternal(mPageScrollPosition, true);
            }
        } 
      }
}

public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    mPageScrollPosition = position;
    mPageScrollPositionOffset = positionOffset;
    .....
 }

滑动现象:获取滑动位置mPageScrollPosition。在滑动过程中onPageScrolled中返回的position,一直是左边Item的位置,这个可以看ViewPager的原理。infoForCurrentScrollPosition返回当前滑动的位置。

因此正常现象如下:
1:只显示当前Item时,mPageScrollPosition = = getCurrentItem();
2:向右滑动时:mPageScrollPosition < getCurrentItem()且mPageScrollPosition +1 = getCurrentItem();
3:向左滑动时:mPageScrollPosition == getCurrentItem();

/**
 * @return Info about the page at the current scroll position.
 * This can be synthetic for a missing middle page; the 'object' field can be null.
 */
private ItemInfo infoForCurrentScrollPosition() {
    final int width = getClientWidth();
    final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
    final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
    int lastPos = -1;
    float lastOffset = 0.f;
    float lastWidth = 0.f;
    boolean first = true;

    ItemInfo lastItem = null;
    //从左到右遍历
    for (int i = 0; i < mItems.size(); i++) {
        ItemInfo ii = mItems.get(i);
        float offset;
        if (!first && ii.position != lastPos + 1) {
            // Create a synthetic item for a missing page.
            ii = mTempItem;
            ii.offset = lastOffset + lastWidth + marginOffset;
            ii.position = lastPos + 1;
            ii.widthFactor = mAdapter.getPageWidth(ii.position);
            i--;
        }
        offset = ii.offset;

        final float leftBound = offset;
        final float rightBound = offset + ii.widthFactor + marginOffset;
        if (first || scrollOffset >= leftBound) {
            if (scrollOffset < rightBound || i == mItems.size() - 1) {
                return ii;
            }
        } else {
            return lastItem;
        }
        first = false;
        lastPos = ii.position;
        lastOffset = offset;
        lastWidth = ii.widthFactor;
        lastItem = ii;
    }

    return lastItem;
}

问题出现: 但是手指快速左滑动,一直滑动到viewpager Item不能移动,并松开时,onPageScrolled中返回的position值又加1了。。。。

log如下:offset==0后手指还处于快速左滑状态。

此时的异常现象 :mPageScrollPosition = getCurrentItem()+1且xVelocity 快速左滑速度超过阈值VEL_THRESHOLD ,代码进入左滑的if()分支,传入的值为mPageScrollPosition + 1 即 getCurrentItem()+2
那么ViewPager默认是保留左右两个Item的,简单说就是要去找左边第二个没保存Item的position,那么就会一直找不到这Item对应的position(实际上在对比position时,position值由getCurrentItem()+1递减到0,而getCurrentItem()+2不在此范围),由于无限循环次数最大为Integer.MAX_VALUE,执行时间可能超10秒,就报ANR。

mViewPager.setBannerCurrentItemInternal(mPageScrollPosition + 1, true);

1.2 bug1处理方案: 明确左右滑动条件,对异常情况进行处理。

if (mPageScrollPosition < mViewPager.getCurrentItem()) {
    // 往右滑
    if (xVelocity > VEL_THRESHOLD || (mPageScrollPositionOffset < 0.7f && xVelocity > -VEL_THRESHOLD)) {
        mViewPager.setBannerCurrentItemInternal(mPageScrollPosition, true);
    } else {
        mViewPager.setBannerCurrentItemInternal(mPageScrollPosition + 1, true);
    }
} else if (mPageScrollPosition == mViewPager.getCurrentItem()) {
    // 往左滑
    if (xVelocity < -VEL_THRESHOLD || (mPageScrollPositionOffset > 0.3f && xVelocity < VEL_THRESHOLD)) {
        mViewPager.setBannerCurrentItemInternal(mPageScrollPosition + 1, true);
    } else {
        mViewPager.setBannerCurrentItemInternal(mPageScrollPosition, true);
    }
} else{
    //其他
    mViewPager.setBannerCurrentItemInternal(mPageScrollPosition, true);
}

2.1 bug2处理方案:
这个bug是在复现第一个bug1是偶现的,主要就是当多次快速滑动时,BGABanner控件dispatchTouchEvent有时返回false,因此只接受了ActionDown事件,即停止了无限循环。而后续的up和cancel事件没有接受,即没有从新开启无限循环。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (mAutoPlayAble) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
            //停止循环
                stopAutoPlay();
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
            //开启循环
                startAutoPlay();
                break;
        }
    }
    //处理偶然的不分发事件的情况handled ==false
    boolean handled = super.dispatchTouchEvent(ev);
     if (!handled&&mAutoPlayAble) {
          startAutoPlay();
      }
    return handled;
   
}

猜你喜欢

转载自blog.csdn.net/lylddingHFFW/article/details/89212664
今日推荐