当前位置: 代码网 > it编程>App开发>Android > Android中ViewPager你所不知道的优化技巧分享

Android中ViewPager你所不知道的优化技巧分享

2024年05月28日 Android 我要评论
写在前面提到viewpager想必各位同学一点都不陌生,它是android中最常用的组件之一,一般配合fragment一起使用。网上关于它的基本使用和常规优化方式也有很多,在这里我就不一一赘述,而是直

写在前面

提到viewpager想必各位同学一点都不陌生,它是android中最常用的组件之一,一般配合fragment一起使用。网上关于它的基本使用和常规优化方式也有很多,在这里我就不一一赘述,而是直接进入这篇文章的主题--viewpager一些新的优化方式

我获得这项技能的背景

最近组里做新的web容器的,一次承载多个h5页面,以实现左右切换,默认展示主会场页,并要达到提升打开率的目标。要达到这个目标,那势必要从加载优化入手,缩短页面的打开时间。 优化的点包括但不限于,activity初始化、viewpager和fragment的初始化、webview的初始化等等。我做的第一个优化点便是viewpager相关。

解决viewpager默认加载多个fragment的问题

viewpager会默认给我们缓存多个fragment,这样设计的目的是为了提升左右滑动的流畅度,代价就是会降低首次打开的启动时间。这让一个以打开率为kpi的我来说是不能容忍的!首先想到的解决方案便是,当fragment页面可见时,才从网络加载数据并显示出来。这样做还是不能解决其它fragment被缓存,以导致占用启动时间的问题,那怎么办?既然viewpager不给我们只加载一个fragment的机会,那我们强行创造行不行。我首次只往adapter塞一个fragment,等加载完成后再调用notifydatasetchanged方法更新其它页面行不行!

解决重复刷新的问题

fragmentpageradapter不会销毁已经初始化完毕的fragment

那为什么会有重复刷新的问题?且听我慢慢道来

我们的主会场在viewpager中的位置是由后端下发的。首次加载单个fragment,主会场在viewpager中的位置只能是0,后续更新时根据后端下发的position动态调整其所在的位置。

//调整主会场位置伪代码
marketinginfolist.add(new marketinginfo("www.juejin.com", "主会场"))
for (int i = 0; i <= 3; i++) {
    //将放在前两个主会场前面
    if (i < 2) {
        marketinginfolist.add(i, new marketinginfo("www.baidu.com", "模拟" + i));
    } else {
    //后两个往主会场后面添加
        marketinginfolist.add(new marketinginfo("www.baidu.com", "模拟" + i));
    }
}
mpageradapter.notifydatasetchanged();
//重新设置选中主会场
mviewpager.setcurrentitem(2);

可在实际开发的过程中却发现,主会场重复加载了两次,viewpager生成了一个新的fragment去承载主会场。我们的用户元气满满的点开我们的营销页,正准备下单呢,页面突然又重新白屏了一下。留下一句****,愤然离去。作为一名要给公司带来增长价值的开发这是不能接受的!那怎么办呢?分析源码!

viewpager源码解析

instantiateitem方法作用

viewpager会通过这个方法将构造fragment,fragmentmanager和transaction都在这个方法里出现

public object instantiateitem(viewgroup container, int position) {
    if (mcurtransaction == null) {
        mcurtransaction = mfragmentmanager.begintransaction();
    }
    
    final long itemid = getitemid(position);

    //跟据itemid生成fragment名字,通过名字去查找fragment是否加载过
    string name = makefragmentname(container.getid(), itemid);
    fragment fragment = mfragmentmanager.findfragmentbytag(name);
    //fragment加载过则直接attach,否则的话新生成一个fragment
    if (fragment != null) {
        if (debug) log.v(tag, "attaching item #" + itemid + ": f=" + fragment);
        mcurtransaction.attach(fragment);
    } else {
        fragment = getitem(position);
        if (debug) log.v(tag, "adding item #" + itemid + ": f=" + fragment);
        mcurtransaction.add(container.getid(), fragment,
        makefragmentname(container.getid(), itemid));
    }
    if (fragment != mcurrentprimaryitem) {
        fragment.setmenuvisibility(false);
        fragment.setuservisiblehint(false);
    }

    return fragment;
 }

instantiateitem会通过getitemid获取到itemid,再生成与fragment对应的唯一tag,通过tag查找fragment是否加载过。也就是说只要tag相同,无论你点击的是哪个tab都会加载到同一个fragment。我们再接着查看生成tag的方法makefragmentname。

private static string makefragmentname(int viewid, long id) {
    return "android:switcher:" + viewid + ":" + id;
}

原来tag就是由instantiateitem传入的viewid和itemid两个值组成,那么我们再看看itemid的生成方式

public long getitemid(int position) {
    return position;
}

我惊了!更加的简单!也就是说fragment的唯一tag是又position决定的。这下刚刚的问题有答案了吧。

重复刷新的真相与解决

viewpager在初始化fragment时,会根据tag寻找fragment,有则直接加载,无则重新生成。主会场首次加载的position是0,后续调整位置后变成了2,导致两次的tag不一至,所以就出现了重复加载的问题。知道了问题产生的原因,再来想解决办法就好办了。我们可以重写getitem方法,重新定义itemid的生成方式。

 public long getitemid(int position) {
     //可以直接使用后端给页面id
     return pageid;
     //后端不给也没事,我们自己生成一个
     return data.get(position).gettitle().hashcode();
 }

延伸: #getitemposition方法

如果不重写getitemid这方法,将页面位置调整后再跳切回旧的位置,还会面临就位置的页面不刷新的问题。举个栗子:

掘金的position是0,我将它的position改为2,第0个position这个时候设置为百度,会发现首个页面依然是掘金。

网上给出的答案是重写getitemposition方法,虽然可以解决问题,但是没有一个能讲明白这个方法的作用,在这里我来补充一下

public int getitemposition(object object) {
        return position_unchanged;
}

getitemposition默认返回position_unchanged,表示页面无变化。还有另外一个默认值position_none,表示页面不存在。

???

页面指的是哪个页面?调用时机又是什么?还能再返回其它值吗? 各位看官先别急且看我慢慢写来,写帖一段源码:

void datasetchanged() {
        // this method only gets called if our observer is attached, so madapter is non-null.

        final int adaptercount = madapter.getcount();
        mexpectedadaptercount = adaptercount;
        boolean needpopulate = mitems.size() < moffscreenpagelimit * 2 + 1
                && mitems.size() < adaptercount;
        int newcurritem = mcuritem;

        boolean isupdating = false;
            //mitems为旧数据的容器
        for (int i = 0; i < mitems.size(); i++) {
            final iteminfo ii = mitems.get(i);
          //返回刷新之前tab项所处的位置
            final int newpos = madapter.getitemposition(ii.object);
            //返回的位置等于position_unchanged(-1)表示当前页未有变更,不做任何操作
            if (newpos == pageradapter.position_unchanged) {
                continue;
            }
            //如果返回的位置等于position_none(-2)表示当前页tab项刷新后不存在,需要销毁并重新加载新的页面
            if (newpos == pageradapter.position_none) {
                mitems.remove(i);
                i--;

                if (!isupdating) {
                    madapter.startupdate(this);
                    isupdating = true;
                }

                madapter.destroyitem(this, ii.position, ii.object);
                needpopulate = true;

                if (mcuritem == ii.position) {
                    // keep the current item in the valid range
                    newcurritem = math.max(0, math.min(mcuritem, adaptercount - 1));
                    needpopulate = true;
                }
                continue;
            }
            //如果当前页的新的位置和和旧位置不等,则说明调整了顺序
            if (ii.position != newpos) {
            //这段代码是将页面定位到刷新之前的打开页,据数据的position和mcuritem相等的话,则表示这个item是之前打开的,赋予它新位置的值
                if (ii.position == mcuritem) {
                    // our current item changed position. follow it.
                    newcurritem = newpos;
                }

                ii.position = newpos;
                needpopulate = true;
            }
        }

        if (isupdating) {
            madapter.finishupdate(this);
        }

        collections.sort(mitems, comparator);

        if (needpopulate) {
            // reset our known page widths; populate will recompute them.
            final int childcount = getchildcount();
            for (int i = 0; i < childcount; i++) {
                final view child = getchildat(i);
                final layoutparams lp = (layoutparams) child.getlayoutparams();
                if (!lp.isdecor) {
                    lp.widthfactor = 0.f;
                }
            }

            setcurrentiteminternal(newcurritem, false, true);
            requestlayout();
        }
    }

notifydatasetchanged方法之后会调用datasetchanged方法,getitemposition又是在datasetchanged方法被调用的。

调用notifydatasetchanged的后,会遍历旧的页面,通过getitemposition方法返回的位置去决定当前遍历到的页面是否需要更新。position_unchanged:表示页面无变化;position_none:表示页面不存在,需要销毁,重新加载新的页面。如果返回值返回的是页面具体的位置,则更新当前页在刷新数据后的位置,将tab栏选中的对应的tab项选中。

以上就是android中viewpager你所不知道的优化技巧分享的详细内容,更多关于android viewpager优化的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com