現在很多app的架構,基本都是viewpager+fragment的,復雜一點的可能還含有嵌套,例如我們公司的app:
可以看到采用的是:底部按鈕+[viewpager+fragment[viewpager+fragment]]嵌套的模式
簡單來講如圖:
布局如下:
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".lazy.ViewPagerActivity">
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_one"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="第一頁"
android:textColor="#ffffff"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_two"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/colorPrimaryDark"
android:gravity="center"
android:text="第二頁"
android:textColor="#ffffff"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_three"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#ff0000"
android:gravity="center"
android:text="第三頁"
android:textColor="#ffffff"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_four"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#000000"
android:gravity="center"
android:text="第四頁"
android:textColor="#ffffff"
android:textSize="15sp" />
</LinearLayout>
</LinearLayout>
fragment_a.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/colorPrimary"
android:gravity="center"
android:padding="5dp"
android:text="A"
android:textColor="#ffffff"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/colorPrimary"
android:gravity="center"
android:padding="5dp"
android:text="B"
android:textColor="#ffffff"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_c"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/colorPrimary"
android:gravity="center"
android:padding="5dp"
android:text="C"
android:textColor="#ffffff"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_d"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/colorPrimary"
android:gravity="center"
android:padding="5dp"
android:text="D"
android:textColor="#ffffff"
android:textSize="12sp" />
</LinearLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
當我們深入viewpager加載需要一個adapter
android提供的有FragmentPagerAdapter/FragmentStatePagerAdapter
區別在于前者在沒有在destroyItem中調用實務的detach方法:
FragmentPagerAdapter:
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
mCurTransaction.detach((Fragment)object);
}
FragmentStatePagerAdapter:
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
//復制數組保存的fragment為null,不采用remove是因為在滑動的時候需要創建(instantiateItem:mFragments.set(position, fragment)),這里節省數組的插入位移數據時間來提升效率
mFragments.set(position, null);
//移除
mCurTransaction.remove(fragment);
}
這里我們主要結合FragmentStatePagerAdapter來優化viewpager下的數據加載
簡單分析FragmentStatePagerAdapter
入口viewpage.setAdapter(xxx)
public void setAdapter(@Nullable PagerAdapter adapter) {
if (mAdapter != null) {
//recycler adapter,將設置的adapter數據全清空
......
}
//將mAdpter復制
mAdapter = adapter;
if (mAdapter != null) {
//初始化adapter的一些必要參數
.....
if (mRestoredCurItem >= 0) {
//還原adapter數據狀態
...
} else if (!wasFirstLayout) {
//注意這里
populate();
} else {
requestLayout();
}
}
}
觀察populate函數,該函數是執行fragment生命周期的關鍵函數
void populate(int newCurrentItem) {
//FragmentStatePagerAdapter的startUpdate
mAdapter.startUpdate(this);
if (curItem == null && N > 0) {
curItem = addNewItem(mCurItem, curIndex);
}
//執行的是,返回一個fragment對象
mAdapter.instantiateItem(this, position)
//當前的fragment被選中
mAdapter.setPrimaryItem(this, mCurItem, curItem.object);
//完成
mAdapter.finishUpdate(this);
}
具體可知,viewpager在調用setAdapter(FragmentStatePagerAdapter fpg)的時候,
FragmentStatePagerAdapter 會依次執行
startUpdate--->instantiateItem()-->setPrimaryItem-->finishUpdate方法,那么再看下
FragmentStatePagerAdapter 的源碼
FragmentStatePagerAdapter
startUpdate是個空實現
instantiateItem
@Override
public Object instantiateItem(ViewGroup container, int position) {
//取出緩存的Fragment
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
//創建事務
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
//更新保存的狀態
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
fragment.setUserVisibleHint(false);
//更新該fragment,這里和后面set(position,null)相對應,節省性能
mFragments.set(position, fragment);
//執行事務的add方法,將fragment加入到事務中
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
setPrimaryItem
@Override
@SuppressWarnings("ReferenceEquality")
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
//當緩存的CurrentFragment不是選中的fragment執行
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
//調用setUserVisibleHint
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
//調用setUserVisibleHint
fragment.setUserVisibleHint(true);
}
//更新緩存當前fragment
mCurrentPrimaryItem = fragment;
}
}
destroyItem
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
//set null,對應上面的add position
mFragments.set(position, null);
//從事務中已移除
mCurTransaction.remove(fragment);
}
可以看到,這里完成了fragment的生命周期,但是由于fragment的生命周期是又FragmentManager的事務FragmentTransaction來管理的,所以,在沒有調用commit之前是不會進行生命周期的
這里finishUpdate函數調用的commit
finishUpdate
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
}
}
所以,在所有的生命周期中,setUserVisibleHint將會最先執行。
懶加載的本質是一種算法,因為viewpager中的預加載是無法避免的(內部緩存了mFragments來存儲fragment),在populate中兩個for循環中緩存了左右兩邊數據
for (int pos = mCurItem - 1; pos >= 0; pos--) {
//leftItem
ii(left) = mItems.get(itemIndex) ;
}
for (int pos = mCurItem - 1; pos >= 0; pos--) {
//leftItem
ii(right)= mItems.get(itemIndex) ;
}
所以,懶加載就是在數據上做優化,可見時加載數據,不可見時停止加載數據
LazyFragment
/**
* @author chenke
* @create 2021/2/25
* @Describe
*/
public abstract class LazyFragment extends Fragment {
private final String TAG = "lazy_fragment";
private View mRootView;
private boolean viewCreated = false;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if (mRootView == null) {
mRootView = inflater.inflate(getLayout(), container, false);
}
initView(mRootView);
viewCreated = true;
Log.i(TAG, getClass().getSimpleName() + "====>onCreateView");
if (getUserVisibleHint()) {
//由于onCreateView,在執行之前setUserVisibleHint已經執行,所以這里手動分發一次可見狀態為true
setUserVisibleHint(true);
}
return mRootView;
}
public abstract int getLayout();
public abstract void initView(View view);
/**
*setUserVisibleHint會優先于所有生命周期的執行,
*所以這里增加標志位viewCreated,視圖創建了才執行函數
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.i(TAG, getClass().getSimpleName() + "====>setUserVisibleHint");
if (viewCreated) {
if (isVisibleToUser) {
//選中的時候可見,分發
dispatchUserVisibleStatus(true);
} else if (!isVisibleToUser) {
//分發不可見
dispatchUserVisibleStatus(false);
}
}
}
public void dispatchUserVisibleStatus(boolean isUserVisibleStatus) {
if (isUserVisibleStatus) {
onStartLoad();
} else {
onStopLoad();
}
}
@Override
public void onResume() {
super.onResume();
Log.i(TAG, getClass().getSimpleName() + "===>onResume");
}
@Override
public void onPause() {
super.onPause();
Log.i(TAG, getClass().getSimpleName() + "===>onPause");
}
@Override
public void onDetach() {
super.onDetach();
Log.i(TAG, getClass().getSimpleName() + "===>onDetach");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.i(TAG, getClass().getSimpleName() + "===>onDestroyView");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, getClass().getSimpleName() + "===>onDestroy");
}
/**
* 子類重寫該方法來實現開始加載數據
*/
public void onStartLoad() {
Log.i(TAG, getClass().getSimpleName() + "====>開始加載數據onStartLoad");
}
/**
* 子類重寫該方法來實現暫停數據加載
*/
public void onStopLoad() {
Log.i(TAG, getClass().getSimpleName() + "====>停止加載數據onStopLoad");
}
}
如圖:
日志輸出情況:
可以看到符合預期,那么這里是不是結束了,顯然不是,因為在viewpage+fragment中如果存在嵌套的情況下,他在滑動到,嵌套的fragment,那么fragment的子類都會執行
所以需要加上在嵌套的時候,父類去控制子類的分發
public void dispatchUserVisibleStatus(boolean isUserVisibleStatus) {
if (isUserVisibleStatus) {
onStartLoad();
} else {
onStopLoad();
}
//在嵌套模式下,讓子類的fragment進行分發
FragmentManager fm = getChildFragmentManager();
List<Fragment> fragments = fm.getFragments();
if (fragments.size() > 0) {
for (Fragment fragment : fragments) {
if (fragment instanceof LazyFragment) {
//當前的fragment狀態為可見時才分發
if (fragment.getUserVisibleHint()) {
((LazyFragment) fragment).dispatchUserVisibleStatus(true);
}
}
}
}
}
但,此時的懶加載還不完整,因為,當如果我們從fragment跳轉到一個activity過后,由于viewpager沒有做改變,所以不會觸發setUserVisibleHint函數,我們需要在onResume和onPause來執行數據的暫停和加載
/**
*記錄當前fragment在執行數據加載/停止加載之前的狀態
*/
boolean currentVisibleStatus=false
@Override
public void onResume() {
super.onResume();
Log.i(TAG, getClass().getSimpleName() + "===>onResume");
if (getUserVisibleHint() && !currentVisibleStatus) {
//getUserVisibleHint() 不會執行,始終為true
//不可見-->可見,加載數據
dispatchUserVisibleStatus(true);
}
}
@Override
public void onPause() {
super.onPause();
Log.i(TAG, getClass().getSimpleName() + "===>onPause");
if (getUserVisibleHint() && currentVisibleStatus) {
//可見-->不可見,停止加載
dispatchUserVisibleStatus(false);
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.i(TAG, getClass().getSimpleName() + "====>setUserVisibleHint");
if (viewCreated) {
if (!currentVisibleStatus && isVisibleToUser) {
//之前狀態不可見-->當前狀態可見,加載數據
dispatchUserVisibleStatus(true);
} else if (currentVisibleStatus && !isVisibleToUser) {
//之前狀態可見-->當前狀態不可見,暫停數據加載
dispatchUserVisibleStatus(false);
}
}
}
完整LazyFragmet
/**
* @author chenke
* @create 2021/2/25
* @Describe
*/
public abstract class LazyFragment extends Fragment {
private final String TAG = "lazy_fragment";
private View mRootView;
private boolean viewCreated = false;
private boolean currentVisibleStatus = false;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if (mRootView == null) {
mRootView = inflater.inflate(getLayout(), container, false);
}
initView(mRootView);
viewCreated = true;
Log.i(TAG, getClass().getSimpleName() + "====>onCreateView");
if (getUserVisibleHint()) {
setUserVisibleHint(true);
}
return mRootView;
}
public abstract int getLayout();
public abstract void initView(View view);
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.i(TAG, getClass().getSimpleName() + "====>setUserVisibleHint");
if (viewCreated) {
if (!currentVisibleStatus && isVisibleToUser) {
dispatchUserVisibleStatus(true);
} else if (currentVisibleStatus && !isVisibleToUser) {
dispatchUserVisibleStatus(false);
}
}
}
public void dispatchUserVisibleStatus(boolean isUserVisibleStatus) {
currentVisibleStatus = isUserVisibleStatus;
if (isUserVisibleStatus) {
onStartLoad();
} else {
onStopLoad();
}
//在嵌套模式下,讓子類的fragment進行分發
FragmentManager fm = getChildFragmentManager();
List<Fragment> fragments = fm.getFragments();
if (fragments.size() > 0) {
for (Fragment fragment : fragments) {
if (fragment instanceof LazyFragment) {
if (fragment.getUserVisibleHint()) {
((LazyFragment) fragment).dispatchUserVisibleStatus(true);
}
}
}
}
}
@Override
public void onResume() {
super.onResume();
Log.i(TAG, getClass().getSimpleName() + "===>onResume");
if (getUserVisibleHint() && !currentVisibleStatus) {
dispatchUserVisibleStatus(true);
}
}
@Override
public void onPause() {
super.onPause();
Log.i(TAG, getClass().getSimpleName() + "===>onPause");
if (getUserVisibleHint() && currentVisibleStatus) {
dispatchUserVisibleStatus(false);
}
}
@Override
public void onDetach() {
super.onDetach();
Log.i(TAG, getClass().getSimpleName() + "===>onDetach");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.i(TAG, getClass().getSimpleName() + "===>onDestroyView");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, getClass().getSimpleName() + "===>onDestroy");
}
/**
* 子類重寫該方法來實現開始加載數據
*/
public void onStartLoad() {
Log.i(TAG, getClass().getSimpleName() + "====>開始加載數據onStartLoad");
}
/**
* 子類重寫該方法來實現暫停數據加載
*/
public void onStopLoad() {
Log.i(TAG, getClass().getSimpleName() + "====>停止加載數據onStopLoad");
}
}
總結:
- viewpager+fragment的結構下,子fragment集合會依次執行startUpdate--->instantiateItem()-->setPrimaryItem-->finishUpdate
- 由于fragment的由FragmentManager的事務FragmentTransaction來管理,而viewpager是在finishUpdate函數后才提交事務,所以setUserVisibleHinit會最先執行。
- FragmentStatePagerAdapter和FragmentPagerAdapter區別在于前者調用事務的remove函數,而后者這是detach,所有FragmentStatePagerAdapter需要幾率fragment的狀態,更消耗內存
- viewpager內部存儲的mFragments數組是通過置空來優化list的性能(add和remove會頻繁的移動list的指針)
以上是分析support包下的viewpager+fragment的懶加載,由于事務在是setUserVisibleHint之后執行,讓viewpager+fragment的這種結構的數據,在可見情況下無法做到有效的控制,所以AndroidX下的Fragment新增的maxLifecycle來控制最大生命周期,這也是setUserVisibleHint被廢棄的原因,下面我會再介紹在AndroidX下如何通過最大生命周期來優化懶加載。