網(wǎng)上的懶加載分析文章已經(jīng)很多,這里也給出我自己的分析思路。
1 為什么要實現(xiàn)懶加載?原因是默認(rèn)情況下ViewPager會去預(yù)加載前后各一頁的內(nèi)容。預(yù)加載會依次調(diào)用Fragment的生命周期方法 onAttach(),onCreate(),onCreateView(),onViewCreated(),onActivityCreated(),onStart(),onResume(),此時已經(jīng)完成網(wǎng)絡(luò)的請求了(一般而言我們會把請求網(wǎng)絡(luò)的接口放在onViewCreated中),結(jié)果就是浪費用戶的流量。
2 何謂懶加載?懶加載即是當(dāng)Fragment可見時才去加載數(shù)據(jù)。對于普通的fragment,參考activity,當(dāng)onStart被調(diào)用時其界面應(yīng)該就可見了。但是當(dāng)Viewpager+Fragment配合使用時,根據(jù)上面的內(nèi)容,此時Viewpager相鄰的Fragment是不可見的,但是onStart已經(jīng)調(diào)用了。顯然,在Viewpager+Fragment配合使用時,無法通過onStart是否調(diào)用判斷Fragment是否可見。針對這種情況,谷歌提供了一個方法 public void setUserVisibleHint(boolean isVisibleToUser) 用于判斷Viewpager+Fragment配合使用時,fragment是否可見。參數(shù)isVisibleToUser為true時可見,為false時不可見。
3 懶加載的難點其實主要是各種標(biāo)志位的意義
private void lazyLoad() {
if(getUserVisibleHint() && isViewPrepared && !hasLoadData){
hasLoadData = true;
initdata();
}
}
標(biāo)志位有3個:
getUserVisibleHint : 表示界面是否可見
isViewPrepared ?。骸”硎窘缑媸欠褚呀?jīng)準(zhǔn)備好
hasLoadData?。骸”硎臼欠褚呀?jīng)加載過數(shù)據(jù)。
只有當(dāng)界面可見,界面ui已經(jīng)準(zhǔn)備好,且沒有加載過數(shù)據(jù)的情況下,才調(diào)用initData()加載數(shù)據(jù)。
3.1 isViewPrepared
首先我們會想到,既然setUserVisiableHint() 能夠判斷界面是否可見,那直接在setUserVisiableHint() 進行判斷,當(dāng)界面可見時,即isVisibleToUser為true去加載數(shù)據(jù)不就行了嗎?答案是不行。理由是setUserVisiableHint() 會在生命周期方法調(diào)用前調(diào)用。以下是打印的log:
05-24 10:42:12.819 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab1 isVisibleToUser :false
05-24 10:42:12.819 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab2 isVisibleToUser :false
05-24 10:42:12.820 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab1 isVisibleToUser :true
05-24 10:42:12.821 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab1 onAttach
05-24 10:42:12.821 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab1 onCreate
05-24 10:42:12.821 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab2 onAttach
05-24 10:42:12.821 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab2 onCreate
05-24 10:42:12.822 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab1 onCreateView
05-24 10:42:12.825 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab1 onViewCreated
05-24 10:42:12.826 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab1 onActivityCreated
05-24 10:42:12.826 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab1 onStart
05-24 10:42:12.826 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab1 onResume
05-24 10:42:12.826 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab2 onCreateView
05-24 10:42:12.829 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab2 onViewCreated
05-24 10:42:12.829 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab2 onActivityCreated
05-24 10:42:12.829 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab2 onStart
05-24 10:42:12.829 15245-15245/com.lyd.lazyload I/FRAGMENT: Tab2 onResume
那么為什么setUserVisiableHint() 在生命周期前調(diào)用就不能在里面去加載數(shù)據(jù)呢?聯(lián)系我們具體項目,一般而言拉取數(shù)據(jù)后會去更新ui控件,那ui控件的實例從哪里獲得?
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.i(TAG , mParam1 + "onCreateView");
View view = inflater.inflate(R.layout.fragment_my, container, false);
tv = (TextView) view.findViewById(R.id.tv);
return view;
}
如上所示,在onCreateView中通過View.findViewById獲取ui控件的實例,之后才能更新ui,所以在setUserVisiableHint()里面去拉取數(shù)據(jù)可以,但是不能更新ui,否則會報空指針異常。
那么這時候我們就可以在增加標(biāo)志位isViewPrepared,表示界面是否已經(jīng)加載完成,在onCreateView() 中View view = inflater.inflate(R.layout.fragment_my, container, false)后設(shè)置為true即可,這里我把它放在onViewCreated()這個方法中,由方法名可以知道,此時View已經(jīng)創(chuàng)建完成。
3.2 getUserVisibleHint
界面可見時返回true,不可見時返回false
3.3 hasLoadData
其實這里主要有3種情景需要考慮:
假設(shè)ViewPager共有3個頁面,分別對應(yīng)Fragment1,fragment2,fragment3.
1一開始進來,fragment1可見,Viewpager會去預(yù)加載fragment2,此時fragment2要不要去加載數(shù)據(jù)?當(dāng)然是不去加載,只有Fragment可見時才去加載數(shù)據(jù)。
2由fragment1滑到fragment2,然后再回到fragment1,此時fragment1需不需要再次去加載數(shù)據(jù)?需不需要加載數(shù)據(jù)主要是看滑動Fragment2時,fragment1有沒有被銷毀?通過log我們可以知道,當(dāng)滑到Fragment時,并沒有觸發(fā)Fragment1的任何生命周期方法,所以此時Fragment1頁面沒有銷毀,不需要去加載數(shù)據(jù)。此時hasLoadData為true,所以initdata()不執(zhí)行。
3由fragment滑到fragment2,在滑到Fragment3,然后再回到Fragment1.此時Fragment1需不需要去加載數(shù)據(jù)?
當(dāng)滑到Fragment3時,log如下:
05-24 15:15:32.139 12422-12422/com.lyd.lazyload I/FRAGMENT: Tab1 onPause
05-24 15:15:32.139 12422-12422/com.lyd.lazyload I/FRAGMENT: Tab1 onStop
05-24 15:15:32.140 12422-12422/com.lyd.lazyload I/FRAGMENT: Tab1 onDestroyView
此時Fragment1界面被銷毀,回到Fragment1時需要重新加載數(shù)據(jù)。需要特別注意的是,當(dāng)滑到Fragment3時,fragment1并沒有調(diào)用onDestory方法,也即是沒有銷毀Fragment1的實例,只是銷毀它的界面。所以需要onDestroyView做出判斷:
@Override
public void onDestroyView() {
super.onDestroyView();
Log.i(TAG , mParam1 + "onDestroyView");
hasLoadData = false;
isViewPrepared = false;
}
4 完整代碼:
public class MyFragment extends Fragment {
public static final String TAG = "FRAGMENT";
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;
private boolean hasLoadData = false;//是否已經(jīng)加載過數(shù)據(jù)
private boolean isViewPrepared = false;//控件是否已經(jīng)準(zhǔn)備好
private TextView tv;
public MyFragment() {
// Required empty public constructor
}
public static MyFragment newInstance(String param1, String param2) {
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG , mParam1 + "onCreate");
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.i(TAG , mParam1 + "onCreateView");
View view = inflater.inflate(R.layout.fragment_my, container, false);
tv = (TextView) view.findViewById(R.id.tv);
return view;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.i(TAG , mParam1 + "isVisibleToUser :" + isVisibleToUser);
if(isVisibleToUser){
lazyLoad();
}
}
private void lazyLoad() {
if(getUserVisibleHint() && isViewPrepared && !hasLoadData){
hasLoadData = true;
initView();
}
}
private void initView() {
Log.i(TAG , mParam1 + "initdata");
tv.setText(mParam1);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.i(TAG , mParam1 + "onViewCreated");
isViewPrepared = true;
lazyLoad();
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.i(TAG , mParam1 + "onDestroyView");
hasLoadData = false;
isViewPrepared = false;
}
}