回顾上一篇内容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,并且这四个方法都必须提供实现。
两个PagerAdapter到底有什么不同呢?
在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