Android Fragment 懶加載
一、為什么要進行懶加載
一般我們在使用add+show+hide去顯示、隱藏fragment或者fragment嵌套使用、viewpager+fragment結合使用等場景下,如果不進行懶加載會導致多個fragment頁面的生命周期被調用,每個頁面都進行網絡請求這樣會產生很多無用的請求,因為實際顯示的只是用戶看到的那個頁面,其他頁面沒有必要在這個時候去加載數據。
二、fragment懶加載
懶加載即在頁面第一次可見時再進行數據請求以及加載的操作,因為版本的不同實現懶加載的方式也略有不同。
首先我們來看下舊版(support包)懶加載的實現方式,其實舊版主要是利用setUserVisibleHint和onHiddenChanged兩個函數來實現懶加載的。
- viewpager+fragment
viewpager+fragment結合的方式,一共有4個fragment分別是fragmentA、fragmentB、fragmentC、fragmentD。
ViewPager 預緩存 Fragment 的個數為1的情況下fragment的生命周期變化。
右划到fragmentB時的變化
返回fragmentA時的變化
由上面我們可以看出每次setUserVisibleHint函數都會比fragment的生命周期函數先回調,且僅當前顯示頁面的isVisibleToUser為true,其他頁面isVisibleToUser均為false。進入fragmentA頁面時同時加載了fragmentA和fragmentB且兩者都回調了onresume生命周期函數,當划向fragmentB之后加載了fragmentC但是fragmentB未回調任何生命周期函數僅回調了setUserVisibleHint。
因此我們可以利用setUserVisibleHint來進行fragment的懶加載。下面上代碼
public class LazyLoadFragment extends Fragment {
//判斷是否已進行過加載,避免重復加載
private boolean isLoad=false;
//判斷當前fragment是否可見
private boolean isVisibleToUser = false;
//判斷當前fragment是否回調了resume
private boolean isResume = false;
@Override
public void onResume() {
super.onResume();
isResume=true;
lazyLoad();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.i(TAG, “setUserVisibleHint: “+isVisibleToUser+” “+FragmentD.this);
this.isVisibleToUser=isVisibleToUser;
lazyLoad();
}
private void lazyLoad() {
if (!isLoad&&isVisibleToUser&&isResume){
//懶加載。。。
isLoad=true;
}
}
-
add+show+hide
Add fragmentA和fragmentB然后hide B show A,下面是兩者相關函數回調結果
然后 show B hide A
再show A hide B
右上邊可以看出一開始add A和B時兩者都回調了onresume,但是B還 回調了onHiddenChanged且為true,之后show B hide A 沒有觸發任何生命周期函數,兩者僅回調了onHiddenChanged 且A為true B為false。
由此我們可以利用onHiddenChanged 來完成懶加載機制,下面是代碼
public class FragmentD extends Fragment {
//判斷是否已進行過加載,避免重復加載
private boolean isLoad=false;
//判斷當前fragment是否可見
private boolean isHidden=true;
@Override
public void onResume() {
super.onResume();
lazyLoad();
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
Log.i(TAG, “onHiddenChanged: “+hidden+” “+FragmentD.this);
isHidden=hidden;
lazyLoad();
}
@Override
public void onDestroyView() {
super.onDestroyView();
isLoad=false; //注意當view銷毀時需要把isLoad置為false
}
private void lazyLoad() {
if (!isLoad&&!isHidden){
//懶加載。。。
isLoad=true;
}
}
}
- 復雜嵌套的fragment
當add+hide+show和viewpager+fragment 嵌套組合使用時上面的懶加載就需要做一些調整。
public class FragmentD extends Fragment {
//判斷是否已進行過加載,避免重復加載
private boolean isLoad=false;
//判斷當前fragment是否可見
private boolean isVisibleToUser = false;
//判斷當前fragment是否回調了resume
private boolean isResume = false;
private boolean isCallUserVisibleHint = false;
@Override
public void onResume() {
super.onResume();
isResume=true;
if (!isCallUserVisibleHint) isVisibleToUser=!isHidden();
lazyLoad();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.i(TAG, “setUserVisibleHint: “+isVisibleToUser+” “+FragmentD.this);
this.isVisibleToUser=isVisibleToUser;
isCallUserVisibleHint=true;
lazyLoad();
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
Log.i(TAG, "onHiddenChanged: "+hidden+" "+FragmentD.this);
isVisibleToUser=!hidden;
lazyLoad();
}
@Override
public void onDestroyView() {
super.onDestroyView();
isLoad=false;
isCallUserVisibleHint=false;
isVisibleToUser=false;
isResume=false;
}
private void lazyLoad() {
if (!isLoad&&isVisibleToUser&&isResume){
//懶加載。。。
isLoad=true;
}
}
看完舊版的我們來看下新版的(Androidx)fragment的懶加載應該如何實現。
Google 在 Androidx 在FragmentTransaction中增加了setMaxLifecycle方法來控制 Fragment 所能調用的最大的生命周期函數.該方法可以設置活躍狀態下 Fragment 最大的狀態,如果該 Fragment 超過了設置的最大狀態,那么會強制將 Fragment 降級到正確的狀態。
- ViewPager+Fragment
在 FragmentPagerAdapter 與 FragmentStatePagerAdapter 新增了含有behavior字段的構造函數
public FragmentPagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
public FragmentStatePagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
如果 behavior 的值為 BEHAVIOR_SET_USER_VISIBLE_HINT,那么當 Fragment 對用戶的可見狀態發生改變時,setUserVisibleHint 方法會被調用。
如果 behavior 的值為 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT ,那么當前選中的 Fragment 在 Lifecycle.State RESUMED 狀態 ,其他不可見的 Fragment 會被限制在 Lifecycle.State STARTED 狀態。
使用了 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 后,確實只有當前可見的 Fragment 調用了 onResume 方法。而導致產生這種改變的原因,是因為 FragmentPagerAdapter 在其 setPrimaryItem 方法中調用了 setMaxLifecycle 方法
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
//如果當前的fragment不是當前選中並可見的Fragment,那么就會調用
// setMaxLifecycle 設置其最大生命周期為 Lifecycle.State.STARTED
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,則設置其最大生命周期為
//Lifecycle.State.RESUMED
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);
}
mCurrentPrimaryItem = fragment;
}
}
所以在ViewPager+fragment 結構中實現懶加載可以這樣:
public class LazyLoadFragment extends Fragment {
//判斷是否已進行過加載,避免重復加載
private boolean isLoad=false;
@Override
public void onResume() {
super.onResume();
if (!isLoad){
lazyLoad();
isLoad=true;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
isLoad=false;
}
private void lazyLoad() {
//懶加載。。。
}
}
-
add+hide+show
參考viewpager做法,在add fragment時僅把要顯示的fragment通過setMaxLifecycle設置為resume,其他fragment均設置為start。
在show、hide切換顯示的fragment時僅把show的fragment通過setMaxLifecycle設置為resume,其他hide的fragment均設置為start -
復雜嵌套
當fragment嵌套fragment等復雜情況下,只要父fragment回調onresume生命周期函數那被嵌套的所有同級子fragment都會回調onresume,所以我們需要再加上fragment是否隱藏來判斷是否要進行懶加載。
public class LazyLoadFragment extends Fragment {
//判斷是否已進行過加載,避免重復加載
private boolean isLoad=false;
@Override
public void onResume() {
super.onResume();
if (!isLoad&& !isHidden()){
lazyLoad();
isLoad=true;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
isLoad=false;
}
private void lazyLoad() {
//懶加載。。。
}
}
viewpager2 本身已支持懶加載