BGABanner-Android控件開啓無限輪播後 ,快速滑動,ANR現象分析

在使用大神(@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,由於無限循環次數爲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 && ev.getAction() != MotionEvent.ACTION_DOWN) {
          startAutoPlay();
      }
    return handled;
   
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章