Fragment的懒加载有2种情况,一种ViewPager+Fragment配合使用的懒加载。
一种是单纯只有Fragment的懒加载。
Fragment的生命周期
首先了解一下Fragment的生命周期。
只有Fragment的懒加载
一般首页都是像iOS那种底栏的设计UI。点击下方的按钮切换fragment,这时不需要使用ViewPager+Fragment来实现,只需要动态显示或者隐藏Fragment就行。
假设现在首页有A,B,C,D四个页面,点击按钮切换。 在Activity初始化先把A页面添加进去,点击其他页面的时候再Add。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
final Fragment[] mFragments = new Fragment[4];
private int mCurrentPosition;
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
findViewById(R.id.a_bt).setOnClickListener(this);
findViewById(R.id.b_bt).setOnClickListener(this);
findViewById(R.id.c_bt).setOnClickListener(this);
findViewById(R.id.d_bt).setOnClickListener(this);
mFragments[0] = FragmentA.newInstance();
getSupportFragmentManager().beginTransaction()
.add(R.id.container, mFragments[0], createTag(mFragments[0]))
.commit();
}
@Override public void onClick(View v) {
switch (v.getId()) {
case R.id.a_bt:
switchFragment(0);
break;
case R.id.b_bt:
switchFragment(1);
break;
case R.id.c_bt:
switchFragment(2);
break;
case R.id.d_bt:
switchFragment(3);
break;
}
}
/**
* 切换Fragment
*
* @param toPosition 点击的position
*/
private void switchFragment(int toPosition) {
if (mCurrentPosition == toPosition) return;
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (mFragments[toPosition] == null) {
mFragments[toPosition] = newFragmentInstance(toPosition);
}
if (!mFragments[toPosition].isAdded()) {
fragmentTransaction.add(R.id.container, mFragments[toPosition],
createTag(mFragments[toPosition]));
}
fragmentTransaction.hide(mFragments[mCurrentPosition]).show(mFragments[toPosition]).commit();
Log.d(TAG, mCurrentPosition + "");
mCurrentPosition = toPosition;
}
private Fragment newFragmentInstance(int position) {
switch (position) {
case 0:
return FragmentA.newInstance();
case 1:
return FragmentB.newInstance();
case 2:
return FragmentC.newInstance();
case 3:
return FragmentD.newInstance();
}
return null;
}
private String createTag(Fragment fragment) {
return fragment.getClass().getName();
}
}
复制代码
ViewPager + Fragment懒加载
一般是这样使用的:
mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override public Fragment getItem(int position) {
return mFragments[position];
}
@Override public int getCount() {
return mFragments.length;
}
});
复制代码
不难猜测fragment的处理都是在FragmentAdapter这个类里面,所以直接看这个类是如果处理的。
FragmentPagerAdapter源码走读
源码其实很简单,是一个单独的类,我把它copy出来方便打印日志和写注释:
public abstract class FragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = true;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;
public FragmentPagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
private static String makeFragmentName(int viewId, long id) {
String s = "android:switcher:" + viewId + ":" + id;
Log.d(TAG, s);
return s;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
@Override public void startUpdate(ViewGroup container) {
if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this + " requires a view id");
}
}
@Override public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
// 获得item的唯一id
final long itemId = getItemId(position);
// Do we already have this fragment?
// 由itemId和viewPager的id生成一组Tag标记Fragment
String name = makeFragmentName(container.getId(), itemId);
// 通过tag去找fragment
Fragment fragment = mFragmentManager.findFragmentByTag(name);
// 如果不为空的话说明这个fragment已经添加过了,只是被detach了,就重新attach这个fragment
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
// 如果为空,创建一个Fragment,添加进去。
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));
}
// 如果当期显示的fragment不是这里初始化的fragment,将这里的fragment设置为用户不可见
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
@Override 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());
}
mCurTransaction.detach((Fragment) object);
}
@Override public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
// 这里展现给用户可见的fragment是object,所以需要把mCurrentPrimaryItem设置为不可见,
// object设置为可见。
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
}
}
@Override public boolean isViewFromObject(View view, Object object) {
return ((Fragment) object).getView() == view;
}
@Override public Parcelable saveState() {
return null;
}
@Override public void restoreState(Parcelable state, ClassLoader loader) {
}
/**
* Return a unique identifier for the item at the given position.
*
* <p>The default implementation returns the given position.
* Subclasses should override this method if the positions of items can change.</p>
*
* @param position Position within this adapter
* @return Unique identifier for the item at position
*/
public long getItemId(int position) {
return position;
}
}
复制代码
重点关注instantiateItem
和setPrimaryItem
这两个方法。在instantiateItem
中通过TAG来找fragment,没有的话就初始化添加到frament事务中。有的话直接Attach。关键是这里设置了fragment的可见状态。setPrimaryItem
中也是对当前fragment可见状态做了标记。所以我们来仔细看看setUserVisibleHint
这个方法。
setUserVisibleHint 源码走读
这个方法用来设置ui是否对用户可见。 这个方法不会自动调用,是需要手动调用的,其实就是设置了一个boolean标记,没有做什么特殊处理。
viewpager里面左右切换fragment的时候对frgament设置了setUserVisibleHint,所以我们是不是可以通过这个可见性标记来处理懒加载?
/**
* 设置一个标记,标注用户是否可以看到Fragment的UI。默认是可见的,为true。
* 在fragment实例状态保存和恢复之间,这个标记是持久化存在的。
*
* 当fragment被滚动到屏幕之外,这时是不可见的。或者,不想直接对用户可见。
* 系统可以使用这一点来优先处理诸如片段生命周期更新或加载器排序行为的操作。
*
* 注意:此方法可能在片段生命周期之外调用。 因此在fragment生命周期方法调用中没有排序保证。
*
* @param isVisibleToUser 如果此fragment的UI当前对用户可见(默认),则为true,否则为false。
*/
public void setUserVisibleHint(boolean isVisibleToUser) {
// 如果原来对用户不可见 && 现在要设置为可见 && 状态在started之前
// && mFragmentManager不为空 && 已经被添加到Activity了
// 就执行performPendingDeferredStart()这个方法
if (!mUserVisibleHint
&& isVisibleToUser
&& mState < STARTED
&& mFragmentManager != null
&& isAdded()) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
mDeferStart = mState < STARTED && !isVisibleToUser;
}
复制代码
public void performPendingDeferredStart(Fragment f) {
if (f.mDeferStart) {
if (mExecutingActions) {
// Wait until we're done executing our pending transactions
mHavePendingDeferredStart = true;
return;
}
f.mDeferStart = false;
moveToState(f, mCurState, 0, 0, false);
}
}
复制代码
ViewPager
#/frameworks/base/core/java/com/android/internal/widget/ViewPager.java
//每次切换ViewPager的Tab时调用的方法
void populate(int newCurrentItem) {
mAdapter.startUpdate(this);
//......
addNewItem(mCurItem, curIndex);
// mCurItem 为当前可见Fragment
// 调用setUserVisibleHint(true)
mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
// 在这里调用了mCurTransaction.commitNowAllowingStateLoss();开始初始化 fragment,
// 所以setUserVisibleHint是在整个frament生命周期之前调用的
mAdapter.finishUpdate(this);
//.....
}
ItemInfo addNewItem(int position, int index) {
ItemInfo ii = new ItemInfo();
ii.position = position;
// 初始化fragment, 这里调用了setUserVisibleHint(false)
ii.object = mAdapter.instantiateItem(this, position);
ii.widthFactor = mAdapter.getPageWidth(position);
if (index < 0 || index >= mItems.size()) {
mItems.add(ii);
} else {
mItems.add(index, ii);
}
return ii;
}
复制代码
ViewPager在创建Fragment之前,初始化的时候先设置setUserVisibleHint(false), 在当前页面对用户可见的时候调用setUserVisibleHint(true)。所有的操作完成后最后将执行frgament.commit()。所以setUserVisibleHint都是先于fragment的生命周期调用的。
使用FragmentAdapter并不会销毁Fragment实例,只会销毁View。
onPause
onStop
onDestroyView
复制代码
因为setUserVisibleHint最先调用,所以避免空指针需要判断View是否都初始化完毕了。 根据这个思路我用2个map来保存每个fragment的view是否初始化了,和是否加载过数据。当fragment销毁view时重置标记。代码如下:fragment需要实现getPosition表明他的position。
/**
* author: Amaze
* date: 2017/4/20 11:44
* des: 适用于ViewPager懒加载fragment
*/
public abstract class LazyViewPagerFragment extends Fragment {
private static final String TAG = "LazyViewPagerFragment";
private final SparseBooleanArray mIsViewCreatedArray = new SparseBooleanArray();// 存储view是否加载完
private final SparseBooleanArray mIsDataLoadedArray = new SparseBooleanArray();// 存储数据是否加载过了
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mIsViewCreatedArray.put(getPosition(), true); // 初始化完了View
if (getUserVisibleHint()) {
loadData();
mIsDataLoadedArray.put(getPosition(), true);
}
}
@Override public void onDestroyView() {
super.onDestroyView();
// 销毁时重置
reset();
}
private void reset() {
mIsViewCreatedArray.put(getPosition(), false); // 默认没有初始化View
mIsDataLoadedArray.put(getPosition(), false); // 默认没有加载过数据
}
@Override public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
reset();
}
@Override public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
}
@Override public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
// 如果对用户可见且初始化完毕
if (isVisibleToUser && mIsViewCreatedArray.get(getPosition()) && !mIsDataLoadedArray.get(
getPosition())) {
loadData();
mIsDataLoadedArray.put(getPosition(), true);
}
}
protected abstract void loadData();
protected abstract int getPosition();
}
复制代码
FragmentAdapter和FragmentStatePagerAdapter的区别
观察生命周期,当ViewPager销毁frgament的时候:
// FragmentAdapter:
onPause
onStop
onDestroyView
// FragmentStatePagerAdapter
onPause
onStop
onDestroyView
onDestroy
onDetach
复制代码
所以之后重新创建的时候:
// FragmentAdapter:
onViewCreated
onStart
onResume
// FragmentStatePagerAdapter
onAttach
onCreate
onViewCreated
onStart
onResume
复制代码
FragmentStatePagerAdapter完全把fragment移除了,FragmentAdapter只是销毁了View。因此FragmentStatePagerAdapter里面用Bundle保存数据以便恢复。
FragmentStatePagerAdapter对内存开销更小,复杂页面应该使用这个。