美文网首页
ViewPager ANR的问题探索

ViewPager ANR的问题探索

作者: leilifengxingmw | 来源:发表于2020-02-29 19:39 被阅读0次

完整示例代码

复现ANR的场景

先定义一个适配器继承自PagerAdapter

public class ViewPageAdapter extends PagerAdapter {

    private static final String TAG = "ViewPageAdapter";

    //轮播图片链接
    private List imgUrls;
    //图片数量
    private int count;
    private Context context;
    //用来加载图片
    private ImageLoader imageLoader;

    public ViewPageAdapter(Context context, ImageLoader imageLoader, List imgUrls) {
        this.context = context;
        this.imageLoader = imageLoader;
        this.imgUrls = imgUrls;
        count = imgUrls.size();
    }

    @Override
    public int getCount() {
        //注释1处,返回Integer.MAX_VALUE
        return Integer.MAX_VALUE;
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
        return view == o;
    }

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        Log.d(TAG, "instantiateItem: position = " + position);
        //注释2处 取余获取正确的图片链接
        position %= count;
        ImageView imageView = new ImageView(context);
        imageView.setLayoutParams(new RelativeLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        //使用Glide加载图片
        imageLoader.displayImage(context, imgUrls.get(position), imageView);
        //这里的container就是ViewPager,调用ViewPager的addView方法,添加子View
        container.addView(imageView);
        return imageView;
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        container.removeView((View) object);
    }
}

在注释1处,getCount方法返回Integer.MAX_VALUE。

@Override
public int getCount() {
    //注释1处,返回Integer.MAX_VALUE
    return Integer.MAX_VALUE;
}

注释2处,取余获取正确的图片链接

@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
    Log.d(TAG, "instantiateItem: position = " + position);
    //注释2处 取余获取正确的图片链接
    position %= count;
  //...
}

使用适配器

private void initMultiBanner() {
    List<String> multiImgs = new ArrayList<>();
    multiImgs.add(Images.imageUrls[0]);
    multiImgs.add(Images.imageUrls[1]);
    multiImgs.add(Images.imageUrls[2]);
    ViewPageAdapter adapter = new ViewPageAdapter(this, new GlideImageLoader(), multiImgs);
    binding.viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
          @Override
          public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

          }

          @Override
          public void onPageSelected(int position) {
              currentPosition = position;
              Log.d(TAG, "onPageSelected: position = " + position);
          }

          @Override
          public void onPageScrollStateChanged(int state) {

          }
      });
    //注释1处
    binding.viewPager.setAdapter(adapter);
    int middle = Integer.MAX_VALUE >> 1;
    //取divisiblePosition为可以整除multiImgs的size的值
    int divisiblePosition = middle - (middle % multiImgs.size());
    binding.viewPager.setCurrentItem(divisiblePosition);
}

我们重点看一下上面方法的注释1处

binding.viewPager.setAdapter(adapter);
int middle = Integer.MAX_VALUE >> 1;
//取divisiblePosition为可以整除multiImgs的size的值
int divisiblePosition = middle - (middle % multiImgs.size());
binding.viewPager.setCurrentItem(divisiblePosition);

我们这样做的目的是为了让ViewPager可以左右无限切换实现轮播图。(大约可以切换Integer.MAX_VALUE 一半的次数),并且显示第一张轮播图。

举个例子

  1. 假设轮播图有3张,Integer.MAX_VALUE为100
  2. middle = Integer.MAX_VALUE>>1=50
  3. divisiblePosition = 50 - (50 % 3)= 50 - 2 =48,此时显示的正好是第一张图。

到现在还是没有问题的,下面我们手动切换一下ViewPager显示的item。

binding.btnChangeCurrentItem.setOnClickListener(
      new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //注释1处,设置ViewPager当前item为0
                binding.viewPager.setCurrentItem(0);
            }
        }
);

在上面注释1处,我们调用ViewPager的setCurrentItem方法,显示第一个item。然后我们点击几次返回键,这时候我们发现应用就ANR了。

其实我们猜也猜得到为什么ANR,肯定是因为从divisiblePosition到0之间有一个for循环执行次数太多导致的。(Integer.MAX_VALUE >> 1 = 1073741823,十亿多,这可是10个小目标啊,哈哈)。

接下来我们分析一下ANR的具体原因。首先我们先根据Android ANR 定位与分析这篇文章中的方法找到ANR所在的具体类和行数。

anr_position.png

我们打开代码一看,在ViewPager的populate方法中果然有一个for循环。


anr_position1.png

在这个例子中,我们是让ViewPager的item减小了。我们再试试增大ViewPager的item。

binding.btnChangeCurrentItem.setOnClickListener(
      new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //注释1处,设置ViewPager当前item为Integer.MAX_VALUE-1
                binding.viewPager.setCurrentItem(Integer.MAX_VALUE-1);
            }
        }
);

同样也会ANR,如下图所示


anr_pos2.png

这次是在1171行,我们打开代码看一看,ViewPager的populate方法中也是一个for循环。


anr_pos3.png

接下来我们就一第一种ANR进行分析。

首先看一下ViewPager的setAdapter方法精简版

    public void setAdapter(@Nullable PagerAdapter adapter) {
        //...
        final PagerAdapter oldAdapter = mAdapter;
        mAdapter = adapter;
        mExpectedAdapterCount = 0;

        if (mAdapter != null) {
            if (mObserver == null) {
                mObserver = new PagerObserver();
            }
            mAdapter.setViewPagerObserver(mObserver);
            mPopulatePending = false;
            //默认mFirstLayout为true
            final boolean wasFirstLayout = mFirstLayout;
            mFirstLayout = true;
            mExpectedAdapterCount = mAdapter.getCount();
            if (!wasFirstLayout) {
                populate();
            } else {
                //注释1处,调用requestLayout方法
                requestLayout();
            }
        }
        //...
    }

注释1处,当我们第一次调用ViewPager的setAdapter方法时,mFirstLayout为true,会调用requestLayout方法。调用requestLayout方法会导致View重新走measure,layout,draw等方法。

我们调用完ViewPager的setAdapter方法,又调用了ViewPager的setCurrentItem方法。

//在这个例子中,我们传入的方法参数是1073741823
public void setCurrentItem(int item) {
    mPopulatePending = false;
    setCurrentItemInternal(item, !mFirstLayout, false);
}

void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
    //smoothScroll为false
    setCurrentItemInternal(item, smoothScroll, always, 0);
}
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
    //...
   //adapter的getCount方法返回值会影响item
    if (item < 0) {
            item = 0;
    } else if (item >= mAdapter.getCount()) {
            item = mAdapter.getCount() - 1;
    }
    //mOffscreenPageLimit默认为1
    final int pageLimit = mOffscreenPageLimit;
    //条件满足
    if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
        //注释1处,此时mItems还是空的
        for (int i = 0; i < mItems.size(); i++) {
            mItems.get(i).scrolling = true;
        }
    }
    //条件满足
    final boolean dispatchSelected = mCurItem != item;

    if (mFirstLayout) {
        //将mCurItem赋值为我们要展示的item
        mCurItem = item;
        if (dispatchSelected) {//通知在下标为item的page被选中
            dispatchOnPageSelected(item);
        }
        //调用requestLayout
        requestLayout();
    } else {
        populate(item);
        scrollToItem(item, smoothScroll, velocity, dispatchSelected);
    }
}

综上所述

binding.viewPager.setAdapter(adapter);
int middle = Integer.MAX_VALUE >> 1;
//取divisiblePosition为可以整除multiImgs的size的值
int divisiblePosition = middle - (middle % multiImgs.size());
binding.viewPager.setCurrentItem(divisiblePosition);

我们调用完ViewPager的setAdapter方法,又调用了ViewPager的setCurrentItem方法,内部都会调用requestLayout方法。导致ViewPager重新走measure,layout,draw等方法。

ViewPager的onMeasure方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //...
    // Make sure we have created all fragments that we need to have shown.
    mInLayout = true;
    populate();
    mInLayout = false;
    //...
}

内部调用populate方法。

void populate() {
    //mCurItem默认是0
    populate(mCurItem);
}
    void populate(int newCurrentItem) {
        ItemInfo oldCurInfo = null;
        if (mCurItem != newCurrentItem) {
            oldCurInfo = infoForPosition(mCurItem);
            mCurItem = newCurrentItem;
        }

       //...
        //注释1处,
        mAdapter.startUpdate(this);
        //注释2处,pageLimit=1
        final int pageLimit = mOffscreenPageLimit;
        //startPos=1073741823-1=1073741822
        final int startPos = Math.max(0, mCurItem - pageLimit);
        //N=Integer.MAX_VALUE
        final int N = mAdapter.getCount();
        //endPos=1073741823+1=1073741824,
        final int endPos = Math.min(N - 1, mCurItem + pageLimit);

       
        int curIndex = -1;
        ItemInfo curItem = null;
        // 此时mItems仍然为空
        for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
            final ItemInfo ii = mItems.get(curIndex);
            if (ii.position >= mCurItem) {
                if (ii.position == mCurItem) curItem = ii;
                break;
            }
        }
        //注释3处,添加当前我们要展示的界面
        if (curItem == null && N > 0) {
            curItem = addNewItem(mCurItem, curIndex);
        }

        // 添加完当前要展示的界面以后,分别在左边和右边添加pageLimit个界面,pageLimit最小为1。
        if (curItem != null) {
            //左边额外的宽度
            float extraWidthLeft = 0.f;
            int itemIndex = curIndex - 1;
            ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
            final int clientWidth = getClientWidth();
            final float leftWidthNeeded = clientWidth <= 0 ? 0 :
                    2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
            //注释4处,for循环,当前界面左边的界面
            for (int pos = mCurItem - 1; pos >= 0; pos--) {
                if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
                    if (ii == null) {
                        break;
                    }
                    if (pos == ii.position && !ii.scrolling) {
                        mItems.remove(itemIndex);
                        mAdapter.destroyItem(this, pos, ii.object);
                        if (DEBUG) {
                            Log.i(TAG, "populate() - destroyItem() with pos: " + pos
                                    + " view: " + ((View) ii.object));
                        }
                        itemIndex--;
                        curIndex--;
                        ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                    }
                } else if (ii != null && pos == ii.position) {
                    extraWidthLeft += ii.widthFactor;
                    itemIndex--;
                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                } else {
                    ii = addNewItem(pos, itemIndex + 1);
                    extraWidthLeft += ii.widthFactor;
                    curIndex++;
                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                }
            }

            float extraWidthRight = curItem.widthFactor;
            itemIndex = curIndex + 1;
            if (extraWidthRight < 2.f) {
                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                final float rightWidthNeeded = clientWidth <= 0 ? 0 :
                        (float) getPaddingRight() / (float) clientWidth + 2.f;
                //注释5处,
                for (int pos = mCurItem + 1; pos < N; pos++) {
                    if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
                        if (ii == null) {
                            break;
                        }
                        if (pos == ii.position && !ii.scrolling) {
                            mItems.remove(itemIndex);
                            mAdapter.destroyItem(this, pos, ii.object);
                            if (DEBUG) {
                                Log.i(TAG, "populate() - destroyItem() with pos: " + pos
                                        + " view: " + ((View) ii.object));
                            }
                            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                        }
                    } else if (ii != null && pos == ii.position) {
                        extraWidthRight += ii.widthFactor;
                        itemIndex++;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    } else {
                        ii = addNewItem(pos, itemIndex);
                        itemIndex++;
                        extraWidthRight += ii.widthFactor;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    }
                }
            }

            calculatePageOffsets(curItem, curIndex, oldCurInfo);

            mAdapter.setPrimaryItem(this, mCurItem, curItem.object);
        }

        //注释6处
        mAdapter.finishUpdate(this);

        // Check width measurement of current pages and drawing sort order.
        // 注释7处,这时候我们总共添加了3个view,childCount=3
        final int childCount = getChildCount();
        //...
    }

注释2处,计算总共要添加的界面数量

//注释2处,pageLimit=1
final int pageLimit = mOffscreenPageLimit;
//startPos=1073741823-1=1073741822
final int startPos = Math.max(0, mCurItem - pageLimit);
//N=Integer.MAX_VALUE
final int N = mAdapter.getCount();
//endPos=1073741823+1=1073741824,
final int endPos = Math.min(N - 1, mCurItem + pageLimit);

我们根据pageLimit计算出我们总共要添加的页面的数量,pageLimit默认为1。我们只需要添加3个界面就行了。

//注释3处,添加当前我们要展示的界面

if (curItem == null && N > 0) {
     curItem = addNewItem(mCurItem, curIndex);
}

ViewPager的addNewItem方法

ItemInfo addNewItem(int position, int index) {
        ItemInfo ii = new ItemInfo();
        ii.position = position;
        //调用adapter的instantiateItem方法
        ii.object = mAdapter.instantiateItem(this, position);
        ii.widthFactor = mAdapter.getPageWidth(position);
        if (index < 0 || index >= mItems.size()) {
            mItems.add(ii);
        } else {
            mItems.add(index, ii);
        }
        return ii;
    }

adapter的instantiateItem方法

@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
    //...
    //这里的container就是ViewPager,调用ViewPager的addView方法,添加子View
    container.addView(imageView);
    return imageView;
}

注意:这里的container就是ViewPager,调用ViewPager的addView方法,添加子View。

我们继续回到ViewPager的populate方法

注释4处,for循环,当前界面左边的界面,添加一个界面就会跳出for循环。

注释5处,for循环,当前界面右边的界面,添加一个界面就会跳出for循环。

添加完毕以后,mItems中有3个元素。

private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();

mItems[0]:当前界面左边的界面,position=1073741822
mItems[1]:当前界面,position=1073741823
mItems[2]:当前界面右边的界面,position=1073741824

到此,ViewPager的addView方法执行完毕,回到onMeasure方法会做测量子View的操作我们忽略。继续往下看ViewPager的onLayout方法。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
     //...
     if (mFirstLayout) {
          //滚动到当前界面,不要动画
          scrollToItem(mCurItem, false, 0, false);
      }
    //将mFirstLayout置为false
    mFirstLayout = false;
}

内部会放置我们添加的view,然后滚动到当前界面,最后把mFirstLayout置为false。到现在一切还是很美好的。

binding.btnChangeCurrentItem.setOnClickListener(
      new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //注释1处,设置ViewPager当前item为0
                binding.viewPager.setCurrentItem(0);
            }
        }
);

然后当我们手动调用ViewPager的setCurrentItem方法,设置ViewPager当前item为0的时候就ANR了。继续往下看。

//此时我们传入的方法参数是0
public void setCurrentItem(int item) {
    mPopulatePending = false;
    //!mFirstLayout为true
    setCurrentItemInternal(item, !mFirstLayout, false);
}

void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
    //smoothScroll为true
    setCurrentItemInternal(item, smoothScroll, always, 0);
}

此时传入的方法参数是0,并且smoothScroll为true

void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
    //...
    //mOffscreenPageLimit默认为1
    final int pageLimit = mOffscreenPageLimit;
    ////注释1处,此时mItems的size是3条件满足
    if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
        for (int i = 0; i < mItems.size(); i++) {
            mItems.get(i).scrolling = true;
        }
    }
    //条件满足
    final boolean dispatchSelected = mCurItem != item;

    if (mFirstLayout) {
        //将mCurItem赋值为我们要展示的item
        mCurItem = item;
        if (dispatchSelected) {//通知在下标为item的page被选中
            dispatchOnPageSelected(item);
        }
        //调用requestLayout
        requestLayout();
    } else {
        //注释2处
        populate(item);
        scrollToItem(item, smoothScroll, velocity, dispatchSelected);
    }
}

注释1处, item < (mCurItem - pageLimit)条件成立,此时mItems的size是3。所以会将mItems中的每个元素的scrolling属性置为true。

这时候已经不是第一次layout了。mFirstLayout为false。所以会执行注释2处的代码,调用populate(item)方法。

    void populate(int newCurrentItem) {
        ItemInfo oldCurInfo = null;
        if (mCurItem != newCurrentItem) {
            //条件满足
            oldCurInfo = infoForPosition(mCurItem);
            mCurItem = newCurrentItem;
        }

       //...
        //注释1处,
        mAdapter.startUpdate(this);
        //注释2处,pageLimit=1
        final int pageLimit = mOffscreenPageLimit;
        //startPos=0
        final int startPos = Math.max(0, mCurItem - pageLimit);
        //N=Integer.MAX_VALUE
        final int N = mAdapter.getCount();
        //endPos=1
        final int endPos = Math.min(N - 1, mCurItem + pageLimit);

        int curIndex = -1;
        ItemInfo curItem = null;
        //注释3处,添加当前我们要展示的界面
        if (curItem == null && N > 0) {
            curItem = addNewItem(mCurItem, curIndex);
        }

        // 添加完当前要展示的界面以后,分别在左边和右边添加pageLimit个界面,pageLimit最小为1。
        if (curItem != null) {
            //左边额外的宽度
            float extraWidthLeft = 0.f;
            int itemIndex = curIndex - 1;
            ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
            final int clientWidth = getClientWidth();
            final float leftWidthNeeded = clientWidth <= 0 ? 0 :
                    2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
            //注释4处,for循环,当前界面左边的界面
            for (int pos = mCurItem - 1; pos >= 0; pos--) {
                //...
            }
            //extraWidthRight=1.0
            float extraWidthRight = curItem.widthFactor;
            //itemIndex=1
            itemIndex = curIndex + 1;
            if (extraWidthRight < 2.f) {
                //注释5处,
                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                //paddingright默认为0,rightWidthNeeded=2.0
                final float rightWidthNeeded = clientWidth <= 0 ? 0 :
                        (float) getPaddingRight() / (float) clientWidth + 2.f;
                //注释6处,添加当前界面右边的界面
                for (int pos = mCurItem + 1; pos < N; pos++) {
                    if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
                        if (ii == null) {
                            break;
                        }
                        if (pos == ii.position && !ii.scrolling) {
                            mItems.remove(itemIndex);
                            mAdapter.destroyItem(this, pos, ii.object);
                            if (DEBUG) {
                                Log.i(TAG, "populate() - destroyItem() with pos: " + pos
                                        + " view: " + ((View) ii.object));
                            }
                            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                        }
                    } else if (ii != null && pos == ii.position) {
                        extraWidthRight += ii.widthFactor;
                        itemIndex++;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    } else {
                        ii = addNewItem(pos, itemIndex);
                        itemIndex++;
                        extraWidthRight += ii.widthFactor;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    }
                }
            }

            calculatePageOffsets(curItem, curIndex, oldCurInfo);

            mAdapter.setPrimaryItem(this, mCurItem, curItem.object);
        }

        //注释6处
        mAdapter.finishUpdate(this);

        // Check width measurement of current pages and drawing sort order.
        // 注释7处,这时候我们总共添加了3个view,childCount=3
        final int childCount = getChildCount();
        //...
    }

注释2处,计算总共要添加的界面数量

//注释2处,pageLimit=1
final int pageLimit = mOffscreenPageLimit;
//startPos=0
final int startPos = Math.max(0, mCurItem - pageLimit);
//N=Integer.MAX_VALUE
final int N = mAdapter.getCount();
//endPos=1
final int endPos = Math.min(N - 1, mCurItem + pageLimit);

我们根据pageLimit计算出我们总共要添加的页面的数量,pageLimit默认为1。因为此时startPo为0,我们不需要添加左边的界面了,我们只需要添加2个界面就行了。

//注释3处,添加当前我们要展示的界面
if (curItem == null && N > 0) {
      curItem = addNewItem(mCurItem, curIndex);
}

注释3处,添加当前我们要展示的界面,此时mCurItem==0。添加完该界面后
mItems中一共有4个元素了。

mItems[0]:当前界面,position=0
mItems[1]:(老的当前界面)左边的界面,position=1073741822
mItems[2]:(老的当前界面),position=1073741823
mItems[3]:(老的当前界面)右边的界面,position=1073741824

注释4处的for循环

//注释4处,for循环,当前界面左边的界面
for (int pos = mCurItem - 1; pos >= 0; pos--) {
  //...
}

此时pos = mCurItem - 1=-1;所以for循环不会执行。

注释5处,注释6处
extraWidthRight=1.0
itemIndex=1
ii=mItems[1],position=1073741822
rightWidthNeeded=2.0
然后进入for循环开始添加当前界面右边的界面

for (int pos = mCurItem + 1; pos < N; pos++) {
       if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
             if (ii == null) {
               break;
             }
             if (pos == ii.position && !ii.scrolling) {
                   //...
              }
       } else if (ii != null && pos == ii.position) {
                extraWidthRight += ii.widthFactor;
                itemIndex++;
                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
        } else {
                ii = addNewItem(pos, itemIndex);
                itemIndex++;
                extraWidthRight += ii.widthFactor;
                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
       }
}

第一次循环会执行else条件

else {
     //新添加一个item
     ii = addNewItem(pos, itemIndex);
     itemIndex++;
     extraWidthRight += ii.widthFactor;
     ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
}

添加一个右边的界面,添加完毕后此时有5各元素
mItems[0]:当前界面,position=0
mItems[1]:当前界面右边的界面,position=1
mItems[2]:(老的当前界面)左边的界面,position=1073741822
mItems[3]:(老的当前界面),position=1073741823
mItems[4]:(老的当前界面)右边的界面,position=1073741824
执行完毕后itemIndex=2,extraWidthRight=2.0,获取的ii是position=1073741822的元素,不为null。
第二次,以及以后的每次循环都会执行if条件

if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
    //注释1处,条件不满足
    if (ii == null) {
         break;
    }
    //注释2处,条件不满足
   if (pos == ii.position && !ii.scrolling) {
        //...                  
    }
}

注释1处

if (ii == null) {
       break;
}

ii是position=1073741822的元素,即(老的当前界面)左边的界面,不为null无法break出去。

注释2处的条件也是无法满足的,因为我们在setCurrentItemInternal方法内部将mItems中老的元素的scrolling都置为true了。

if (pos == ii.position && !ii.scrolling) {
}

也就是说只有完整执行完循环我们才能继续向下执行,那得执行多少次啊?

for (int pos = mCurItem + 1; pos < N; pos++) {
    //...
}

在这个例子中,pos起始值是1,执行到Integer.MAX_VALUE(2147483647)结束。20多亿(20个小目标)啊,不ANR才怪。

我们分析了调用ViewPager的setCurrentItem(int item)方法,减小mCurItem值造成的ANR的原因。调用ViewPager的setCurrentItem(int item)方法,增加mCurItem值造成ANR的原因我们就不分析了,原理都是一样的就是for循环执行次数太多。

总结一下:

  1. 当我们调用ViewPager的setCurrentItem(int item)方法,改变当前显示的界面的时候,如果传入的item值和ViewPager的mCurItem值相差很大的话,在ViewPager的populate(int newCurrentItem)方法内部会导致多次循环,很容易引起ANR的。
  2. item的取值范围是[0,mAdapter.getCount() - 1],也就是说适配器的getCount方法返回值的大小决定了item值的取值范围。

参考链接:

  1. ViewPager anr,页面空白问题完全解析
  2. Android ViewPager 无限轮播Integer.MAX_VALUE 争议(看源码)
  3. 完整示例代码

相关文章

网友评论

      本文标题:ViewPager ANR的问题探索

      本文链接:https://www.haomeiwen.com/subject/igyphhtx.html