回顾上一篇内容ViewPager(二) Adapter的爱恨情仇,我们了解到ViewPager对页面的加载需要PagerAdapter来辅助,而PagerAdapter中涉及开发者操作的核心四个方法分别是:
Int getCount() //返回显示的子View数量
Boolean isViewFromObject(View view, Object object) //加载前确认加载类型是否一致
Object instantiateItem(ViewGroup container, int position) //返回具体加载的子元素
Void destroyItem(ViewGroup container, int position, Object object) //提供销毁子view策略
在我们给ViewPager设置适配器,绑定之后,ViewPager在适当的时候会调用Adapter的以上四个方法准确无误的加载需要显示的子View,并且这四个方法都必须提供实现。
在ViewPager系列第一篇我们也提到,直接继承Viewpager需要实现以上四个方法,并且子View是Fragment的这种情景又比较常见,而Fragment的管理是个麻烦事,意味着Adapter中更多的代码量,针对这种情况,谷歌推荐开发者直接继承,PagerAdapter的两个直接子类FragmentPagerAdapter和FragmentStatePagerAdapter,这样开发者不用关注Fragment的管理,而且只需要提供两个方法,就行了。
按照谷歌给的提示,这两个子类主要是Fragment内存管理状态的不同,为了验证,我们在Fragment的生命周期中添加Log,贴出其中一个Fragment的代码如下:
public class MessageFragment extends Fragment { private final String TAG = getClass().getSimpleName(); private static final String message = "message"; private String messageTag; public MessageFragment() { } /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param Parameter . * @return A new instance of fragment MessageFragment. */ public static MessageFragment newInstance(String param) { MessageFragment fragment = new MessageFragment(); Bundle args = new Bundle(); args.putString(message, param); fragment.setArguments(args); return fragment; } @Override public void onAttach(Context context) { super.onAttach(context); Log.e(TAG,"---onAttach----"); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e(TAG,"---onCreate----"); if (getArguments() != null) { messageTag = getArguments().getString(message); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.e(TAG,"---onCreateView---"); View view = inflater.inflate(R.layout.fragment_common_layout, container, false); TextView textView = view.findViewById(R.id.text_view); textView.setText(messageTag); return view; } @Override public void onDestroyView() { super.onDestroyView(); Log.e(TAG,"----onDestroyView---"); } @Override public void onDestroy() { super.onDestroy(); Log.e(TAG,"----onDestroy---"); } @Override public void onDetach() { super.onDetach(); Log.e(TAG,"----onDetach---"); } }
四个子View都是Fragment,其他的三个写法相同,简单介绍下Fragment主要干了些什么,newInstance(String param)方法是谷歌的创建Fragment实例的方法,Fragment构造方法私有,Activity通过这个方法传参数进来,Fragment的布局很简单,就是一个位于布局中心的TextView,显示Activity传进来的字符串参数。然后就是各个生命周期方法的Log打印。
由于这里要根据Fragment的生命周期来分析,所以这里贴一张图,帮助大家回顾一下Fragment的生命周期
上图中不仅列出了Fragment生命周期,也同时列出了Activity的生命周期,因为Fragment的依赖性,所以他们之间的生命周期会产生联系。(注:上图参考谷歌源码)
在代码中我们分别关注了Fragment的onAttach方法,onCreate方法,onCreateView方法,onDestroyView方法,onDestroy方法,onDetach方法。
两个PagerAdapter实现也很简单,分别继承FragmentPagerAdapter和 FragmentStatePagerAdapter,并实现他们的抽象方法getItem()返回具体子View和getCount()返回要显示子View的数量。由于前边系列有贴代码,这里就不贴Adapter的代码了。然后在Activity中分别使用上述两个Adapter的实例设置给ViewPager,然后向右滑动ViewPager,我们看到了不一样的Log日志
(Fragment的顺序:MessageFragment-> FriendFragment -> CircleFragment -> AccountFragment)
采用了FragmentPagerAdapter 的Log记录
//滑到第一页 11-20 20:40:32.480 6878-6878/com.hzx.viewpagerdirector E/MessageFragment: ---onAttach---- 11-20 20:40:32.480 6878-6878/com.hzx.viewpagerdirector E/MessageFragment: ---onCreate---- 11-20 20:40:32.481 6878-6878/com.hzx.viewpagerdirector E/FriendFragment: ---onAttach---- 11-20 20:40:32.481 6878-6878/com.hzx.viewpagerdirector E/FriendFragment: ---onCreate---- 11-20 20:40:32.481 6878-6878/com.hzx.viewpagerdirector E/MessageFragment: ---onCreateView--- 11-20 20:40:32.483 6878-6878/com.hzx.viewpagerdirector E/FriendFragment: ---onCreateView--- //滑到第二页 11-20 20:40:37.370 6878-6878/com.hzx.viewpagerdirector E/CircleFragment: ---onAttach---- 11-20 20:40:37.370 6878-6878/com.hzx.viewpagerdirector E/CircleFragment: ---onCreate---- 11-20 20:40:37.371 6878-6878/com.hzx.viewpagerdirector E/CircleFragment: ---onCreateView--- //滑到第三页 11-20 20:40:39.316 6878-6878/com.hzx.viewpagerdirector E/AccountFragment: ---onAttach---- 11-20 20:40:39.316 6878-6878/com.hzx.viewpagerdirector E/AccountFragment: ---onCreate---- 11-20 20:40:39.317 6878-6878/com.hzx.viewpagerdirector E/MessageFragment: ----onDestroyView--- 11-20 20:40:39.317 6878-6878/com.hzx.viewpagerdirector E/AccountFragment: ---onCreateView--- //滑到第四页 11-20 20:40:41.812 6878-6878/com.hzx.viewpagerdirector E/FriendFragment: ----onDestroyView---
采用了FragmentStatePagerAdapter的Log记录
//滑到第一页 11-20 17:39:13.562 32391-32391/com.hzx.viewpagerdirector E/MessageFragment: ---onAttach---- 11-20 17:39:13.562 32391-32391/com.hzx.viewpagerdirector E/MessageFragment: ---onCreate---- 11-20 17:39:13.562 32391-32391/com.hzx.viewpagerdirector E/FriendFragment: ---onAttach---- 11-20 17:39:13.562 32391-32391/com.hzx.viewpagerdirector E/FriendFragment: ---onCreate---- 11-20 17:39:13.562 32391-32391/com.hzx.viewpagerdirector E/MessageFragment: ---onCreateView--- 11-20 17:39:13.565 32391-32391/com.hzx.viewpagerdirector E/FriendFragment: ---onCreateView--- //滑到第二页 11-20 17:40:54.209 32391-32391/com.hzx.viewpagerdirector E/CircleFragment: ---onAttach---- 11-20 17:40:54.209 32391-32391/com.hzx.viewpagerdirector E/CircleFragment: ---onCreate---- 11-20 17:40:54.209 32391-32391/com.hzx.viewpagerdirector E/CircleFragment: ---onCreateView--- //滑到第三页 11-20 17:41:03.759 32391-32391/com.hzx.viewpagerdirector E/AccountFragment: ---onAttach---- 11-20 17:41:03.759 32391-32391/com.hzx.viewpagerdirector E/AccountFragment: ---onCreate---- 11-20 17:41:03.760 32391-32391/com.hzx.viewpagerdirector E/MessageFragment: ----onDestroyView--- 11-20 17:41:03.760 32391-32391/com.hzx.viewpagerdirector E/MessageFragment: ----onDestroy--- (笔者标记:不同的地方) 11-20 17:41:03.760 32391-32391/com.hzx.viewpagerdirector E/MessageFragment: ----onDetach---(笔者标记:不同的地方) 11-20 17:41:03.760 32391-32391/com.hzx.viewpagerdirector E/AccountFragment: ---onCreateView--- //滑到第四页 11-20 17:43:03.554 32391-32391/com.hzx.viewpagerdirector E/FriendFragment: ----onDestroyView--- 11-20 17:43:03.556 32391-32391/com.hzx.viewpagerdirector E/FriendFragment: ----onDestroy---(笔者标记:不同的地方) 11-20 17:43:03.557 32391-32391/com.hzx.viewpagerdirector E/FriendFragment: ----onDetach---(笔者标记:不同的地方)
log分析
我们没更改ViewPager的预加载状态,然后在翻到第一页(MessageFragment)的时候和翻到第二页(FriendFragment)的时候,两个完全Adapter相同,这是因为这个时候根据ViewPager的缓存策略,他会缓存3个子View,提高加载速度和显示流畅性。看日志可以证明证明:翻到第二页的时候第三个Fragment(CircleFragment)已经完成了加载操作。而且这个时候还没有回调Fragment卸载的相关方法。
翻到第三页(CircleFragment)的时候和第四页(AccountFragment)的时候,出现了不同:
当翻到第三页的时候,因为他要提前加载第四页(AccountFragment),又由于缓存的数量是3,所以第一页(MessageFragment)开始回调卸载方法,
使用FragmentPagerAdapter,回调了 onDestroyView卸载方法
使用FragmentStatePagerAdapter,回调了onDestroyView,onDestroy,onDetach卸载方法
当翻到第四页(AccountFragment)的时候,又由于缓存的数量是3,而且是任一方向不保存1张以上,所以第二页(FriendFragment)开始回调卸载方法,
使用FragmentPagerAdapter,回调了 onDestroyView卸载方法
使用FragmentStatePagerAdapter,回调了onDestroyView,onDestroy,onDetach卸载方法
所以很清楚了,采用FragmentStatePagerAdapter的ViewPager由于在意Fragment的State,为了节省内存,所以他回调了更彻底的onDestroy和onDetach方法,所以当需要重新使用完全卸载掉的Fragment的时候就需要通过getItem方法重新获取实例。而采用FragmentPagerAdapter的ViewPager,只会回调onDestroyView方法。当需要显示或者提前加载这个Fragment的时候重新走onCreateView迅速创建显示,同时也就会一直驻留在内存里(在一般情况下)。
为什么会出现呢?这种问题的指向性就很明确了,也就是从源码中找答案:
不过,看官先别急。。。
我们先猜测一下,熟悉Fragment的程序员知道,为了保证Fragment加载的安全性和管理的便捷性,他是通过FragmentManager(Activity调用getSupportFragmentManager()获得)统一管理,然后开启FragmentTransaction事务(了解DataBase的童鞋,都知道事务是可以做到操作的一致性,这样加载和卸载能保证协调一致,如果失败还可以回滚)来加载Fragment。
然而对于事务在对Fragment的加载和卸载有两套方法,
一套是:attach(),add()方法 , detach()方法
另一套是:add()方法 ,remove()方法(replace()方法内部是先执行remove()方法,然后执行add()方法)
注意:由于Fragment是用的时候是加载,并不能提前知道加载哪一个,所以add()方法和remove()方法必须分开,所以源码中没有用到replace()方法,但是在我们自己管理的时候,可以考虑使用replace()方法
恰恰当执行detach方法的时候,Fragment会回调onDestroyView(),而执行remove()方法的时候,对应的Fragment会回调onDestroyView,onDestroy,onDetach。所以我们有理由相信,两个PagerAdapter的实现类中源码应该是这样的逻辑,为了验证这个,我们来看源码。。。
因为他们都继承自PagerAdapter,所以在上一篇主要叙述的规则,他们一定是遵守的,加载View是调用
instantiateItem()方法,而卸载是调用destroyItem()方法
所以我们先看FragmentPagerAdapter的源码中的instantiateItem()方法
public Object instantiateItem(ViewGroup container, int position) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } final long itemId = getItemId(position); // Do we already have this fragment? String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); //存在的时候,调用attach()方法,快速加载 mCurTransaction.attach(fragment); } else { //调用FragmentPagerAdapter的抽象方法getItem fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); //第一次创建,不存在的时候,调用add()方法加载 mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } //采用懒加载策略 if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; }
我们再来看FragmentPagerAdapter的destroyItem()方法
public void destroyItem(ViewGroup container, int position, Object object) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object + " v=" + ((Fragment)object).getView()); //采用detach()方法卸载 mCurTransaction.detach((Fragment)object); }
接下来是FragmentStatePagerAdapter的instantiateItem()方法
public Object instantiateItem(ViewGroup container, int position) { // If we already have this item instantiated, there is nothing // to do. This can happen when we are restoring the entire pager // from its saved state, where the fragment manager has already // taken care of restoring the fragments we previously had instantiated. if (mFragments.size() > position) { Fragment f = mFragments.get(position); if (f != null) { return f; } } if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } //调用FragmentStatePagerAdapter的抽象方法getItem Fragment fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); if (mSavedState.size() > position) { Fragment.SavedState fss = mSavedState.get(position); if (fss != null) { fragment.setInitialSavedState(fss); } } while (mFragments.size() <= position) { mFragments.add(null); } //懒加载策略 fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); mFragments.set(position, fragment); //添加Fragment,由于其卸载使用的是remove()方法,所以不能使用attach()方法进行加载,只能用add()方法 mCurTransaction.add(container.getId(), fragment); return fragment; }
再然后是FragmentStatePagerAdapter的destroyItem()方法
public void destroyItem(ViewGroup container, int position, Object object) { Fragment fragment = (Fragment) object; if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + " v=" + ((Fragment)object).getView()); while (mSavedState.size() <= position) { mSavedState.add(null); } mSavedState.set(position, fragment.isAdded() ? mFragmentManager.saveFragmentInstanceState(fragment) : null); mFragments.set(position, null); //采用remove()方法卸载 mCurTransaction.remove(fragment); }
根据我再源码中添加的注释,很显然和我们的猜测是一致的,另外在FragmentStatePagerAdapter源码中还创建了ArrayList<Fragment.SavedState> mSavedState来保存对应位置的Fragment的状态,在instantiateItem()方法中获取状态,在destroyItem()中设置更新状态,这都是为更好的加载服务服务的。
另外我们还发现,本来PagerAdapter需要我们实现的四个方法,经过这两个亲儿子的对instantiateItem和destroyItem方法的实现,所以,我们无论实现哪一个FragmentPagerAdapter都不需要再实现这两个方法了,而且内部也对isViewFromObject进行了实现
@Override public boolean isViewFromObject(View view, Object object) { return ((Fragment)object).getView() == view; }
所以我们都不用实现了,同时为了让用户设置加载的Fragment的,又提供了getItem抽象方法供子类继承
/** * Return the Fragment associated with a specified position. */ public abstract Fragment getItem(int position);
而且这个抽象方法是在instantiateItem方法(在最上边比较两个Adapter区别的源码分析中能够看到)中获取子View的时候调用。
因此我们在实现Adapter的时候只需要实现两个方法一个是getCount(),另一个是getItem()。
在我们使用ViewPager的,选择PagerAdapter的时候应遵循这样的原则:
当我们子View是普通的View,而非Fragment的时候,继承基类PagerAdapter实现必须实现的四个方法;
当子View是Fragment的时候,并且当子View中保存的内容比较少,轻量级,占用内存较小,为了提高加载流畅性,使用FragmentPagerAdapter;
当子View是Fragment的时候,并且其中有些子View保存的内存较多,占用内存较大时,如果经大量测试发现不会出现out of memory,那为了保证流畅性,还是建议是用FragmentPagerAdapter,但是如果发现很容易oom,或者频率很大,那就一定要抛弃FragmentPagerAdapter,而建议采用FragmentStatePagerAdapter来辅助ViewPager加载子View。这样做出的牺牲就是数据每次都要重新加载,页面也需要重新加载初始化。
Adapter的小尾巴,到此我们终于解决了,因为他们加载卸载的机制的不同,导致FragmentPagerAdapter和FragmentStatePagerAdapter这对亲兄弟,天生具有不同的使用场景,并且各有利弊,如果读者发现自己有更好的思路来管理Fragment也可以实现自己项目的FragmentPagerAdapter基类。
在下一篇我们将来列举一些小技巧和在使用过程中的一些坑,便于读者查漏补缺,定位bug
请看 ViewPager (四)让ViewPager用起来更顺滑——换页监听及换页方法
————————————————
版权声明:本文为CSDN博主「郝振兴」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_39095733/article/details/84309051