回顾上一篇内容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
前边两篇遗留下来的一个问题。
两个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
的时候应遵循这样的原则:
1 当我们子View是普通的View,而非Fragment的时候,继承基类PagerAdapter实现必须实现的四个方法;
2 当子View是Fragment的时候,并且当子View中保存的内容比较少,轻量级,占用内存较小,为了提高加载流畅性,使用FragmentPagerAdapter;
3 当子View是Fragment的时候,并且其中有些子View保存的内存较多,占用内存较大时,如果经大量测试发现不会出现out of memory,那为了保证流畅性,还是建议是用FragmentPagerAdapter,但是如果发现很容易oom,或者频率很大,那就一定要抛弃FragmentPagerAdapter,而建议采用FragmentStatePagerAdapter来辅助ViewPager加载子View。这样做出的牺牲就是数据每次都要重新加载,页面也需要重新加载初始化。
Adapter
的小尾巴,到此我们终于解决了,因为他们加载卸载的机制的不同,导致FragmentPagerAdapter
和FragmentStatePagerAdapter
这对亲兄弟,天生具有不同的使用场景,并且各有利弊,如果读者发现自己有更好的思路来管理Fragment
也可以实现自己项目的FragmentPagerAdapter
基类。
在下一篇我们将来列举一些小技巧和在使用过程中的一些坑,便于读者查漏补缺,定位bug