ViewPager页面过多导致OOM(内存溢出)

推荐

推荐使用 ViewPager2
可以更好的解决复用问题

前言

ViewPager是一个经常使用的组件,但是当页面越来越多时,由于各种原因(如内存泄露等等),会导致崩溃(内存溢出),好了,进入正题

本文出现的vp为ViewPager简写

vp页面的添加&删除

由于vp有预加载功能,会有两种加载情况(可能不止?)
1.先加载当前页,再从左到右加载or删除页面(左滑右滑)
2.跳转到某页的情况(如第1页跳转第10页),会优先加载跳转页&预加载页,再删除之前的旧页面(如加载10,9,11再删除1,2)

即真正需要用到的页面数其实是:预加载页 + 1个当前页+1缓存页

//把一侧预加载设置为2,即需要用到的页面为 2 * 2 + 1 + 1 = 6 个页面,vp预加载默认为1即缓存4个页面足以
vp.setOffscreenPageLimit(2);

那么我们可以用一个定长数组把这些页面缓存起来,然后通过复用这些页面达到资源合理利用,先贴代码
该适配器,弱引用防止Context泄漏,T为页面数据模型,H为页面Holder,View数组为缓存页面的数组
mUnRemoveTags这个int集合是个重点,用于标记vp调用destroyItem时是否删除页面,跟vp从左到右添加or删除有关
提供两个构造方法,一个为默认预加载为1,另一个可自行设置也加载数

public abstract class RecyclePagerAdapter<T, H extends RecyclePagerAdapter.PagerHolder> extends PagerAdapter {

    private final WeakReference<Context> mContext;

    private final List<T> mInfos;

    private final View[] mCacheView;

    //不删除记号,用于destroyItem时不清除view
    private final Set<Integer> mUnRemoveTags = new HashSet<>();

    public RecyclePagerAdapter(Context context, List<T> infos) {
        this(context, infos, 1);
    }

    /**
     * @param pagerLimit 预加载一侧页数(即预加载总数为pagerLimit * 2)
     */
    public RecyclePagerAdapter(Context context, List<T> infos, int pagerLimit) {
        if(infos == null)
            throw new NullPointerException();
        if(pagerLimit < 1)
            pagerLimit = 1;
        this.mContext = new WeakReference<>(context);
        this.mInfos = infos;
        //(pagerLimit * 2)(预加载数) + 1(当前页) + 1(缓存页)
        mCacheView = new View[(pagerLimit + 1) * 2];
    }
    ......
}

再添加两个获取页面数据&Context的方法

{
    ...
    public List<T> getPagerInfos(){
        return mInfos;
    }

    public Context getContext(){
        return mContext.get();
    }
    ...
}

现在再来看下PagerHolder,这个跟ListView的Holder差不多,就不多解释

public static class PagerHolder{
    public final View view;
    public PagerHolder(View view) {
        view.setTag(this);
        this.view = view;
    }
}

国际惯例getCount,isViewFromObject,都是原本的写法~
基本需要数据&方法都差不多了,现在来看看重点,添加&删除页面,这里我们先添加三个抽象方法供子类实现,与recycleview适配器类似,用于创建view,holder和绑定view数据,释放view

{
    ....
    /**
     * 创建一个Holder
     * @param position 页面下标
     * @return holder
     */
    protected abstract H onCreaterPagerHolder(Context context, int position);

    /**
     * 绑定页面数据
     * @param info 页面数据
     * @param position 页面下标
     */
    protected abstract void onBindPagerHolde(H holder, T info, int position);

    /**
     * 用于释放页面destroyItem调用且移除时调用
     */
    protected void onReleasePagerHolde(H holder, int position){}
    ....
}

接下来就到添加&删除页面了

1.添加view,通过position对缓存长度取模确定缓存下标,再判断是否需要新建页面,若页面已存在vp中,则记录下标,到destroyItem时不删除该页面,(复用)
2.删除view,通过已记录缓存下标判(mUnRemoveTags)断是否删除页面

{
    ......
    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        //获取缓存下标
        int index = position % mCacheView.length;

        T info = mInfos.get(position);
        H holder;

        if(mCacheView[index] == null){
            holder = onCreaterPagerHolder(mContext.get(), position);
            mCacheView[index] = holder.view;
        }else{
            holder = (H) mCacheView[index].getTag();
        }

        //获取容器是否存在该View
        int i = container.indexOfChild(holder.view);
        if(i != -1)
            //存在则记录缓存下标
            mUnRemoveTags.add(index);
        else
            container.addView(holder.view);

        onBindPagerHolde(holder, info, position);

        return holder.view;
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        int index = position % mCacheView.length;
        //检查是否在不删除标记内,是则不移除view,否则移除view
        if(!mUnRemoveTags.contains(index)) {
            View view = mCacheView[index];
            onReleasePagerHolde((H) view.getTag(), position);
            container.removeView(view);
        }else {
            mUnRemoveTags.remove(index);
        }
    }
    ......
}

好了,到这里,vp复用就完了,目前,缺陷是只能用于同一种布局,用于多布局的话可以配合recycleview Or ListView等等使用

完整适配器代码

/**
 * Created by xiaobaicz
 */
public abstract class RecyclePagerAdapter<T, H extends RecyclePagerAdapter.PagerHolder> extends PagerAdapter {

    private final WeakReference<Context> mContext;

    private final List<T> mInfos;

    private final View[] mCacheView;

    //不删除记号,用于destroyItem时不清除view
    private final Set<Integer> mUnRemoveTags = new HashSet<>();

    public RecyclePagerAdapter(Context context, List<T> infos) {
        this(context, infos, 1);
    }

    /**
     * @param pagerLimit 预加载一侧页数(即预加载总数为pagerLimit * 2)
     */
    public RecyclePagerAdapter(Context context, List<T> infos, int pagerLimit) {
        if(infos == null)
            throw new NullPointerException();
        if(pagerLimit < 1)
            pagerLimit = 1;
        this.mContext = new WeakReference<>(context);
        this.mInfos = infos;
        //(pagerLimit * 2)(预加载数) + 1(当前页) + 1(缓存页)
        mCacheView = new View[(pagerLimit + 1) * 2];
    }

    @Override
    public int getCount() {
        return mInfos.size();
    }

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

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        //获取缓存下标
        int index = position % mCacheView.length;

        T info = mInfos.get(position);
        H holder;

        if(mCacheView[index] == null){
            holder = onCreaterPagerHolder(mContext.get(), position);
            mCacheView[index] = holder.view;
        }else{
            holder = (H) mCacheView[index].getTag();
        }

        //获取容器是否存在该View
        int i = container.indexOfChild(holder.view);
        if(i != -1)
            //存在则记录缓存下标
            mUnRemoveTags.add(index);
        else
            container.addView(holder.view);

        onBindPagerHolde(holder, info, position);

        return holder.view;
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        int index = position % mCacheView.length;
        //检查是否在不删除标记内,是则不移除view,否则移除view
        if(!mUnRemoveTags.contains(index)) {
            View view = mCacheView[index];
            onReleasePagerHolde((H) view.getTag(), position);
            container.removeView(view);
        }else {
            mUnRemoveTags.remove(index);
        }
    }

    /**
     * 创建一个Holder
     * @param position 页面下标
     * @return holder
     */
    protected abstract H onCreaterPagerHolder(Context context, int position);

    /**
     * 绑定页面数据
     * @param info 页面数据
     * @param position 页面下标
     */
    protected abstract void onBindPagerHolde(H holder, T info, int position);

    /**
     * 用于释放页面destroyItem调用且移除时调用
     */
    protected void onReleasePagerHolde(H holder, int position){}

    public List<T> getPagerInfos(){
        return mInfos;
    }

    public Context getContext(){
        return mContext.get();
    }

    public static class PagerHolder{
        public final View view;
        public PagerHolder(View view) {
            view.setTag(this);
            this.view = view;
        }
    }

}

简单用法

/**
 * Created by xiaobaicz
 */
public class MyAdapter extends RecyclePagerAdapter<Integer, MyAdapter.Holder> {

    public MyAdapter(Context context, List<Integer> infos) {
        super(context, infos);
    }

    public MyAdapter(Context context, List<Integer> infos, int pagerLimit) {
        super(context, infos, pagerLimit);
    }

    @Override
    protected Holder onCreaterPagerHolder(Context context, int position) {
        return new Holder(View.inflate(context, R.layout.pager, null));
    }

    @Override
    protected void onBindPagerHolde(Holder holder, Integer info, int position) {
        holder.view.setBackgroundColor(info);
    }

    static class Holder extends RecyclePagerAdapter.PagerHolder {
        Holder(View view) {
            super(view);
        }
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容