Android ViewPager + fragment 实现懒加载及原理解析
1 代码实现
fragment 实现
public abstract class LazyFragment extends LogFragment {
private View viewRoot;
private boolean isShow;
private boolean isLoaded;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
viewRoot = inflater.inflate(getLayoutId(), container, false);
initView(viewRoot);
if (!isShow && getUserVisibleHint()) {
dispatchVisibleHint(true);
}
return viewRoot;
}
protected abstract int getLayoutId();
protected abstract void initView(View viewRoot);
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (viewRoot != null) {
if (!isShow && isVisibleToUser) {
//当前从不可见变为可见
dispatchVisibleHint(true);
} else if (isShow && !isVisibleToUser) {
//当前从可见变为不可见
dispatchVisibleHint(false);
}
}
}
private void dispatchVisibleHint(boolean isVisible) {
isShow = isVisible;
if (isVisible) {
if (!isLoaded) {
Log.e(TAG, "开始加载" + getClass().getName());
isLoaded = true;
loadData();
}
} else {
Log.e(TAG, "停止加载" + getClass().getName());
stopLoadData();
}
}
protected void stopLoadData() {
}
protected void loadData() {
}
@Override
public void onDestroyView() {
super.onDestroyView();
viewRoot = null;
isShow = false;
isLoaded = false;
}
}
使用
public class SecondActivity extends AppCompatActivity {
private ViewPager viewPager;
private TabLayout tabLayout;
private ArrayList<LazyFragment> fragments;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
initView();
}
private void initView() {
fragments = new ArrayList<>();
viewPager = findViewById(R.id.activity_second_viewPager);
tabLayout = findViewById(R.id.activity_tabLayout);
// for (int i = 0; i < 10; i++) {
// fragments.add(fragment1);
// }
fragments.add(new Fragment1());
fragments.add(new Fragment2());
fragments.add(new Fragment3());
fragments.add(new Fragment4());
fragments.add(new Fragment6());
//他会真正销毁我们的fragment,只保存状态
// FragmentStatePagerAdapter
//mActive 这个数组 会造成oom
//他不会真正销毁
FragmentPagerAdapter fragmentPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
@NonNull
@Override
public Fragment getItem(int position) {
// Fragment1 fragment1 = new Fragment1();
// Bundle bundle = new Bundle();
// bundle.putString("fragment", "" + position);
// fragment1.setArguments(bundle);
// fragments.add(fragment1);
// return fragment1;
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
};
viewPager.setAdapter(fragmentPagerAdapter);
tabLayout.setupWithViewPager(viewPager);
for (int i = 0; i < tabLayout.getTabCount(); i++) {
tabLayout.getTabAt(i).setText("Tab " + i);
}
viewPager.setOffscreenPageLimit(fragments.size());
}
}
2 原理解析
先来看 ViewPager 的源码 onMeasure 方法
2.1 ViewPager.onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
final int measuredWidth = getMeasuredWidth();
final int maxGutterSize = measuredWidth / 10;
mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
// Children are just made to fill our space.
int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
int size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp != null && lp.isDecor) {
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
int widthMode = MeasureSpec.AT_MOST;
int heightMode = MeasureSpec.AT_MOST;
boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
//... 测量代码 省略代码
child.measure(widthSpec, heightSpec);
if (consumeVertical) {
childHeightSize -= child.getMeasuredHeight();
} else if (consumeHorizontal) {
childWidthSize -= child.getMeasuredWidth();
}
}
}
}
mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
// Make sure we have created all fragments that we need to have shown.
mInLayout = true;
populate(); // 重点方法,负责创建我们需要显示的 fragments
mInLayout = false;
// Page views next.
size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
if (DEBUG) {
Log.v(TAG, "Measuring #" + i + " " + child + ": " + mChildWidthMeasureSpec);
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp == null || !lp.isDecor) {
final int widthSpec = MeasureSpec.makeMeasureSpec(
(int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
child.measure(widthSpec, mChildHeightMeasureSpec);
}
}
}
}
2.2 ViewPager.populate()
void populate(int newCurrentItem) {
ItemInfo oldCurInfo = null;
if (mCurItem != newCurrentItem) {
oldCurInfo = infoForPosition(mCurItem);
mCurItem = newCurrentItem;
}
// 省略代码。。。
// 1.当显示的页面将开始进行更改时调用
mAdapter.startUpdate(this);
// 预加载相关
final int pageLimit = mOffscreenPageLimit;
final int startPos = Math.max(0, mCurItem - pageLimit);
final int N = mAdapter.getCount();
final int endPos = Math.min(N - 1, mCurItem + pageLimit);
// 省略代码。。。
// Locate the currently focused item or add it if needed.
int curIndex = -1;
ItemInfo curItem = null;
for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
final ItemInfo ii = mItems.get(curIndex);
if (ii.position >= mCurItem) {
if (ii.position == mCurItem) curItem = ii;
break;
}
}
if (curItem == null && N > 0) {
// 2.实例化 fragment 的代码入口
// instantiteItem -> getItem -> setUserVisableHint(false)
curItem = addNewItem(mCurItem, curIndex);
}
// Fill 3x the available width or up to the number of offscreen
// pages requested to either side, whichever is larger.
// If we have no current item we have no work to do.
if (curItem != null) {
float extraWidthLeft = 0.f;
int itemIndex = curIndex - 1;
ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
final int clientWidth = getClientWidth();
final float leftWidthNeeded = clientWidth <= 0 ? 0 :
2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
for (int pos = mCurItem - 1; pos >= 0; pos--) {
if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
if (ii == null) {
break;
}
if (pos == ii.position && !ii.scrolling) {
mItems.remove(itemIndex);
// 3.销毁不需要显示的 fragment
mAdapter.destroyItem(this, pos, ii.object);
if (DEBUG) {
Log.i(TAG, "populate() - destroyItem() with pos: " + pos
+ " view: " + ((View) ii.object));
}
itemIndex--;
curIndex--;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
}
} else if (ii != null && pos == ii.position) {
extraWidthLeft += ii.widthFactor;
itemIndex--;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
} else {
ii = addNewItem(pos, itemIndex + 1);
extraWidthLeft += ii.widthFactor;
curIndex++;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
}
}
float extraWidthRight = curItem.widthFactor;
itemIndex = curIndex + 1;
if (extraWidthRight < 2.f) {
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
final float rightWidthNeeded = clientWidth <= 0 ? 0 :
(float) getPaddingRight() / (float) clientWidth + 2.f;
for (int pos = mCurItem + 1; pos < N; pos++) {
if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
if (ii == null) {
break;
}
if (pos == ii.position && !ii.scrolling) {
mItems.remove(itemIndex);
// 销毁不需要显示的 fragment
mAdapter.destroyItem(this, pos, ii.object);
if (DEBUG) {
Log.i(TAG, "populate() - destroyItem() with pos: " + pos
+ " view: " + ((View) ii.object));
}
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
}
} else if (ii != null && pos == ii.position) {
extraWidthRight += ii.widthFactor;
itemIndex++;
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
} else {
ii = addNewItem(pos, itemIndex);
itemIndex++;
extraWidthRight += ii.widthFactor;
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
}
}
}
calculatePageOffsets(curItem, curIndex, oldCurInfo);
// 4.设置当前显示的 item, 会调用 setUserVisableHint(true)
mAdapter.setPrimaryItem(this, mCurItem, curItem.object);
}
// 5.真正调用 commit, 走 fragment 的生命周期
mAdapter.finishUpdate(this);
// 省略代码。。。
}
FragmentPagerAdapter 和 FragmentStateAdapter 的区别在于,FragmentPagerAdapter 不会对不在显示的fragment 进行销毁,只进行 detach 操作,存在内存泄漏的风险。而 FragmentStateAdapter 则会重新销毁(remove)、创建,具体调用实现可以看两个类对以下方法的实现。
我们来看基于 FragmentPagerAdapter 的流程:
2.3 mAdapter.startUpdate( )
2.4 mAdapter.addNewItem(mCurItem, curIndex)
ItemInfo addNewItem(int position, int index) {
ItemInfo ii = new ItemInfo();
ii.position = position;
// 提供实例化 fragment 的回调方法
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;
}
// androidx/fragment/app/FragmentPagerAdapter.java
public Object instantiateItem(@NonNull 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) {
// 调用 mCurTransaction的attach ,后面会走生命周期
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));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
} else {
// 懒加载重点
fragment.setUserVisibleHint(false);
}
}
return fragment;
}
2.5 mAdapter.destroyItem()
// androidx/fragment/app/FragmentPagerAdapter.java
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
// 调用 detach,此时还未 commit
mCurTransaction.detach(fragment);
if (fragment == mCurrentPrimaryItem) {
mCurrentPrimaryItem = null;
}
}
2.6 mAdapter.setPrimaryItem()
// androidx/fragment/app/FragmentPagerAdapter.java
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
} else {
mCurrentPrimaryItem.setUserVisibleHint(false);
}
}
fragment.setMenuVisibility(true);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
// 懒加载重点
fragment.setUserVisibleHint(true);
}
// 设置当前主 item
mCurrentPrimaryItem = fragment;
}
}
2.7 mAdapter.finshStartUpdate()
// androidx/fragment/app/FragmentPagerAdapter.java
public void finishUpdate(@NonNull ViewGroup container) {
if (mCurTransaction != null) {
// 与 {@link commitNow} 类似,但允许在保存活动状态后执行提交.
// 此时立即执行生命周期回调,此前会先调用 setUserVisableHint(false/true)
mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
}
}
Android ViewPager与Fragment懒加载深度解析
本文详细探讨了如何在Android中使用ViewPager与Fragment实现懒加载,通过分析ViewPager的关键方法如onMeasure、populate等,揭示了FragmentPagerAdapter与FragmentStateAdapter在内存管理上的差异,并阐述了基于FragmentPagerAdapter的流程。
646

被折叠的 条评论
为什么被折叠?



