如何高效的使用ViewPager,以及FragmentPagerAdapter与FragmentStatePagerAdapter的区别
xiaobai888
8年前
<h2><strong>ViewPager介绍</strong></h2> <p>Layout manager that allows the user to flip left and right through pages of data. You supply an implementation of a PagerAdapter to generate the pages that the view shows.</p> <p>ViewPager is most often used in conjunction with Fragment , which is a convenient way to supply and manage the lifecycle of each page. There are standard adapters implemented for using fragments with the ViewPager, which cover the most common use cases. These are FragmentPagerAdapter and FragmentStatePagerAdapter ; each of these classes have simple code showing how to build a full user interface with them.</p> <p>通过官网的介绍,我们可以看出,Viewpager可以通过PagerAdapter展示数据,同时ViewPager更常用于和Fragment结合使用,这样可以更好的管理页面的生命周期。有两种标准的适配器(FragmentPagerAdapter和FragmentStatePagerAdapter)来使fragments和ViewPager更好的结合,并且它们也满足大多数的情况了。</p> <h2><strong>PagerAdapter</strong></h2> <p>Base class providing the adapter to populate pages inside of a ViewPager .</p> <p>You will most likely want to use a more specific implementation of this, such as FragmentPagerAdapter or FragmentStatePagerAdapter</p> <p>PagerAdapter是ViewPager适配器的基类。也许你更想要一个具体的实现,比如FragmentPagerAdapter和FragmentStatePagerAdapter</p> <p>一般我们在只用简单View来适配ViewPager的时候会选择使用PagerAdapter,比如轮播图。</p> <p>When you implement a PagerAdapter, you must override the following methods at minimum:</p> <ul> <li>instantiateItem(ViewGroup, int)</li> <li>destroyItem(ViewGroup, int, Object);</li> <li>getCount();</li> <li>isViewFromObject(View, Object);</li> </ul> <h2><strong>FragmentPagerAdapter 和 FragmentStatePagerAdapter的区别</strong></h2> <p>在使用Fragment来适配ViewPager的时候,我们可以选择FragmentPagerAdapter 或者FragmentStatePagerAdapter作为适配器。但是这两者又有什么区别呢? 我们可以通过官网对这两者的介绍来做一个大体的了解,然后通过具体的实践来进行更深入的使用。</p> <h2><strong>FragmentPagerAdapter</strong></h2> <p>Implementation of PagerAdapter that represents each page as a Fragment that is persistently kept in the fragment manager as long as the user can return to the page.</p> <p>This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs. The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible. This can result in using a significant amount of memory since fragment instances can hold on to an arbitrary amount of state. For larger sets of pages, consider FragmentStatePagerAdapter</p> <p>FragmentPagerAdapter继承自 PagerAdapter。相比通用的 PagerAdapter,该类更专注于每一页均为 Fragment 的情况。如文档所述, <strong>该类内的每一个生成的 Fragment 都将保存在内存之中</strong> ,因此适用于那些相对静态的页,数量也比较少的那种;如果需要处理有很多页,并且数据动态性较大、占用内存较多的情况,应该使用FragmentStatePagerAdapter</p> <h2><strong>FragmentStatePagerAdapter</strong></h2> <p>Implementation of PagerAdapter that uses a Fragment to manage each page. This class also handles saving and restoring of fragment's state.</p> <p>This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.</p> <p>FragmentStatePagerAdapter和前面的 FragmentPagerAdapter 一样,是继承子 PagerAdapter。但是,和 FragmentPagerAdapter 不一样的是,正如其类名中的 <strong>'State'</strong> 所表明的含义一样,该 PagerAdapter 的实现将只保留当前页面,当页面离开视线后,就会被 <strong>消除</strong> ,释放其资源;而在页面需要显示时,生成新的页面(就像 ListView 的实现一样)。这么实现的好处就是当拥有大量的页面时,不必在内存中占用大量的内存。</p> <h2><strong>通过观察Fragment的生命周期来看其中的不同</strong></h2> <p>在观察ViewPager中Fragment的生命周期之前,我们先来看一个ViewPager的方法:</p> <pre> <code class="language-java">/** * Set the number of pages that should be retained to either side of the * current page in the view hierarchy in an idle state. Pages beyond this * limit will be recreated from the adapter when needed. */ viewPager.setOffscreenPageLimit(int limit);</code></pre> <p>该方法是设置左右被保留的page的数量的,也就是说当前被保留的page数量为 2*limit + 1。至于那些没有被保留的,又处于什么状态了呢?这就需要观察Fragment的生命周期了。</p> <h3>FragmentPagerAdapter</h3> <p>此处我们设置viewPager.setOffscreenPageLimit(1);</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/7f22e217ed6fbaa9caa380cbed5e4cf3.png"></p> <p style="text-align:center">FragmentPagerAdapter初始化.png</p> <p>通过Log打印我们可以看出,在 <strong>刚进入</strong> ViewPager的时候,默认的应该是第0个page,</p> <p>此时第0个page的生命周期是: onAttach -> onCreate -> onCreateView -> onViewCreated -> onActivityCreated -> onStart -> onResume 。同时 setUserVisibleHint:true<br> 此时第1个page的生命周期是: onAttach -> onCreate -> onCreateView -> onViewCreated -> onActivityCreated -> onStart -> onResume 同时 setUserVisibleHint:false</p> <p>可以看出,在初始化的过程,两个Fragment的生命周期是一致,而且第1个page此时虽然不可以见,但是依然调用了onStart,onResume。我们都知道在Activity的生命周期中,只有当Acitivty是可见的时候才会调用onStart,只有在可以交互的时候才会调用onResume。</p> <p>此时我们向右滑动,让 <strong>第1个</strong> page显示在界面上。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/f2f9be59910fdd4698bc3fb5d8433369.png"></p> <p style="text-align:center">currentPage==1.png</p> <p>此时第0个page的生命周期是没有发生变化的,因为我们设置了setOffscreenPageLimit(1);只是 javasetUserVisibleHint:false<br> 第1个page有了一点变化: setUserVisibleHint:true<br> 此时第2个page的生命周期有了变化: onAttach -> onCreate -> onCreateView -> onViewCreated -> onActivityCreated -> onStart -> onResume</p> <p>此时我们向右滑动,让 <strong>第2个</strong> page显示在界面上。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/312390bec419fdffec731f8619411025.png"></p> <p style="text-align:center">currentPage==2</p> <p>此时第0个page的生命周期变化为: onPause -> onStop -> onDestroyView<br> 第1个page生命周期没有变化,只是 setUserVisibleHint:false<br> 第2个page生命周期没有发生变化,只是 setUserVisibleHint:true<br> 第三个生命周期 onAttach -> onCreate -> onCreateView -> onViewCreated -> onActivityCreated -> onStart -> onResume 同时 javasetUserVisibleHint:false</p> <p>可以看出,此时第0个page已经执行了 onDestroyView 了,但是它却没有走 onDestroy() 和 onDetach() 说明该page只是将View进行了回收,而真正的fragment是还没有被回收的</p> <p>此时我们向左滑动,让 <strong>第1个</strong> page显示在界面上。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/5ece4db2061da074338e684260cb320b.png"></p> <p style="text-align:center">currentPage == 1</p> <p>可以看出,page0只是走了 onCreateView -> onViewCreated ... 再次证明fragment没有被真正的回收</p> <h3><strong>FragmentStatePagerAdapter</strong></h3> <p>同样我们也设置viewPager.setOffscreenPageLimit(1);</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/5dadbb63c060267013b0c1011a42853a.png"></p> <p style="text-align:center">FragmentStatePagerAdapter -- fragment初始化.png</p> <p>此时我们向右滑动,让 <strong>第1个</strong> page显示在界面上。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/d92e338eeebcd43cee34d357edebab1c.png"></p> <p style="text-align:center">currentPage==1.png</p> <p>此时我们向右滑动,让 <strong>第2个</strong> page显示在界面上</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/b91b68b4de946fd32dd9273134dc9134.png"></p> <p style="text-align:center">currentPage==2.png</p> <p>在这个时候,发现了与FragmentPagerAdapter不同的生命周期了。第0个page它执行了 onDestroy -> onDetach 。说明fragment是被真正的回收掉了</p> <p>此时我们向右滑动,让 <strong>第1个</strong> page显示在界面上</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/7a97e5a48d6dee748ea555a84c719747.png"></p> <p style="text-align:center">currentPage==1</p> <h3><strong>为什么两个适配器回收方式不同</strong></h3> <p>通过之前对两个适配器的了解,我们也知道了FragmentPagerAdapter <strong>每一个生成的 Fragment 都将保存在内存之中</strong> ,因此适用于那些相对静态的页,数量也比较少的那种;FragmentStatePagerAdapter将会对limit外的page进行回收。造成这两者区别的原因就是因为</p> <ul> <li>instantiateItem(ViewGroup, int)</li> <li>destroyItem(ViewGroup, int, Object);<br> 通过观察两个适配器的源码,可以发现: <p>在FragmentPagerAdapter中</p> </li> </ul> <p style="text-align:center"><img src="https://simg.open-open.com/show/f93ee7b06e1f7ec8e27162dfee8f811c.png"></p> <p style="text-align:center">FragmentPagerAdapter</p> <p>函数中判断了一下要生成的framgment是否已经生成过了,如果生成过了,就使用旧的,旧的将被 Fragment.attach();如果没有,就调用 getItem() 生成一个新的,新的对象将被 FragmentTransation.add()。</p> <p>FragmentPagerAdapter 会将所有生成的 Fragment 对象通过 FragmentManager 保存起来备用,以后需要该 Fragment 时,都会从 FragmentManager 读取, <strong>而不会再次调用 getItem() 方法</strong> 。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/2aab6ff758c3ff1deee0f054ff31a059.png"></p> <p style="text-align:center">destroyItem</p> <p>该函数被调用后,会对 Fragment 进行 FragmentTransaction.detach()。这里不是 remove(),只是 detach(),因此 Fragment 还在 FragmentManager 管理中,Fragment 所占用的资源不会被释放。</p> <p>在FragmentStatePagerAdapter中,</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/30494df8ec88735ad4df88a63b30bf1a.png"></p> <p style="text-align:center">instantiateItem</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/b05232a78e078ec23c1bea3908d1e317.png"></p> <p style="text-align:center">destroyItem</p> <p>FragmentStatePagerAdapter 就是通过这种方式,每次都创建一个新的 Fragment,而在不用后就立刻释放其资源,来达到节省内存占用的目的的。</p> <h2><strong>总结</strong></h2> <p>FragmentPagerAdapter <strong>每一个生成的 Fragment 都将保存在内存之中</strong> ,因此适用于那些相对静态的页,数量也比较少的那种;FragmentStatePagerAdapter将会对limit外的page进行回收。</p> <p>我们可以通过观察framgment的生命周期来进行判断。</p> <p>造成这样的原因是因为: instantiateItem(ViewGroup, int) 和 destroyItem(ViewGroup, int, Object); 的实现方式不一样</p> <p> </p> <p>来自:http://www.jianshu.com/p/25a02f5a15b3</p> <p> </p>