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);
        }
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容