概述
Android日常開發(fā)中除了四個組件之外会宪,還有一種使用頻率很高的組件——Fragment冬殃。在使用時我們通常需要在Fragment的各種生命周期方法中處理數(shù)據(jù)加載枣氧、頁面刷新和資源釋放等邏輯操作推溃。
但是當Fragment遇上了ViewPager,事情就變得有點不一樣了袱巨。Fragment的生命周期變得不再那么可控,當顯示Fragment A時碳抄,相鄰的Fragment B的一些生命周期方法也會觸發(fā)愉老。這是因為ViewPager為了優(yōu)化切換效果,使切換更流暢剖效、順滑嫉入。引入了預加載和緩存機制,通常會預加載前一個和后一個Fragment璧尸,讓前一個和后一個Fragment提前初始化咒林。
當頁面布局過于復雜或者數(shù)據(jù)量比較大,甚至當Fragment中有播放器時逗宁,預加載會耗費資源映九,造成頁面卡頓甚至頁面播放器出現(xiàn)異常報錯闪幽。
懶加載
使用懶加載的意義就在于只有當Fragment被顯示時绽诚,才會去加載耗費資源的素材和數(shù)據(jù)婉徘,可以節(jié)省資源孽锥、提升頁面流暢度,而且讓流程變得更可控引有。
實現(xiàn)思路
Fragment中提供了一對可見性相關的方法setUserVisibleHint(boolean isVisibleToUser)
和getUserVisibleHint()
可以通過重寫setUserVisibleHint()
來監(jiān)聽頁面可見性變化瓣颅,當頁面從不可見變?yōu)榭梢姇r觸發(fā)加載數(shù)據(jù)方法,反之也可以實現(xiàn)頁面從可見到不可見時部分資源的釋放操作譬正。
實現(xiàn)
先實現(xiàn)一個Fragment + ViewPager的結構(實現(xiàn)很簡單省略了)宫补,依次有三個Fragment為:AFragment、BFragment和CFragemtn曾我,三個Fragment分別繼承基類BaseLazyLoadFragment粉怕。
生命周期變化
在基類中添加生命周期方法的打印,如下圖:
從Fragment的生命周期變化可以看出抒巢,需要注意的有幾點:
-
setUserVisibleHint()
方法的調用在onCreateView()
方法之前贫贝。 - 進入Activity時第一個被顯示的Fragment,會調用兩次
setUserVisibleHint()
第一次值為false蛉谜,第二次值為true稚晚。 - ViewPager的預加載會讓還沒顯示的Fragment提前初始化。
- 當AFragment切換到BFragment時型诚,會先調用AFragment的
setUserVisibleHint(false)
方法客燕,后調用BFragment的setUserVisibleHint(true)
,我們可以在AFragment中做部分資源的釋放操作。 - 當BFragment切換到AFragment時狰贯,AFragment會執(zhí)行
onDestroyView()
方法釋放持有的布局資源也搓,但是AFragment中的數(shù)據(jù)資源并沒有釋放。 - 當從CFragment切換回BFragment時暮现,AFragment會重新初始化还绘。
代碼實現(xiàn)
基于以上幾點問題,我們通過來通過代碼實現(xiàn)BaseLazyLoadFragment栖袋。
public abstract class BaseLazyLoadFragment extends Fragment {
protected String TAG = BaseLazyLoadFragment.class.getSimpleName();
//Root View
protected View view;
//布局是否初始化完成
private boolean isLayoutInitialized = false;
//懶加載完成
private boolean isLazyLoadFinished = false;
//記錄頁面可見性
private boolean isVisibleToUser = false;
//不可見時釋放部分資源
private boolean isInVisibleRelease = false;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, getClass().getSimpleName() + " onCreate");
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.d(TAG, getClass().getSimpleName() + " onCreateView");
view = inflater.inflate(initLayout(),null);
initView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG, getClass().getSimpleName() + " onDestroyView");
//頁面釋放后拍顷,重置布局初始化狀態(tài)變量
isLayoutInitialized = false;
this.view = null;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(TAG, getClass().getSimpleName() + " onActivityCreated");
//此方法是在第一次初始化時onCreateView之后觸發(fā)的
//onCreateView和onActivityCreated中分別應該初始化哪些數(shù)據(jù)可以參考:
//https://stackoverflow.com/questions/8041206/android-fragment-oncreateview-vs-onactivitycreated
isLayoutInitialized = true;
//第一次初始化后需要處理一次可見性事件
//因為第一次初始化時setUserVisibleHint方法的觸發(fā)要先于onCreateView
dispatchVisibleEvent();
}
@Override
public void onStart() {
super.onStart();
Log.d(TAG, getClass().getSimpleName() + " onStart");
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, getClass().getSimpleName() + " onResume");
//頁面從其他Activity返回時,重新加載被釋放的資源
if(isLazyLoadFinished && isLayoutInitialized && isInVisibleRelease){
visibleReLoad();
isInVisibleRelease = false;
}
}
@Override
public void onPause() {
super.onPause();
Log.d(TAG, getClass().getSimpleName() + " onPause");
//當從Fragment切換到其他Activity釋放部分資源
if(isLazyLoadFinished && isVisibleToUser){
//頁面從可見切換到不可見時觸發(fā)塘幅,可以釋放部分資源昔案,配合reload方法再次進入頁面時加載
inVisibleRelease();
isInVisibleRelease = true;
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, getClass().getSimpleName() + " onDestroy");
//重置所有數(shù)據(jù)
this.view = null;
isLayoutInitialized = false;
isLazyLoadFinished = false;
isVisibleToUser = false;
isInVisibleRelease = false;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.d(TAG, getClass().getSimpleName() + " setUserVisibleHint isVisibleToUser = " + isVisibleToUser);
dispatchVisibleEvent();
}
/**
* 處理可見性事件
*/
private void dispatchVisibleEvent(){
Log.d(TAG, getClass().getSimpleName() + " dispatchVisibleEvent isVisibleToUser = " + getUserVisibleHint()
+ " --- isLayoutInitialized = " + isLayoutInitialized + " --- isLazyLoadFinished = " + isLazyLoadFinished);
if(getUserVisibleHint() && isLayoutInitialized){
//可見
if(!isLazyLoadFinished){
//第一次可見,懶加載
lazyLoad();
isLazyLoadFinished = true;
} else{
//非第一次可見电媳,刷新數(shù)據(jù)
visibleReLoad();
}
} else{
if(isLazyLoadFinished && isVisibleToUser){
//頁面從可見切換到不可見時觸發(fā)踏揣,可以釋放部分資源,配合reload方法再次進入頁面時加載
inVisibleRelease();
}
}
//處理完可見性事件之后修改isVisibleToUser狀態(tài)
this.isVisibleToUser = getUserVisibleHint();
}
/**
* 初始化View
*/
protected abstract void initView();
/**
* 綁定布局
* @return 布局ID
*/
protected abstract int initLayout();
/**
* 懶加載<br/>
* 只會在初始化后第一次可見時調用一次匾乓。
*/
protected abstract void lazyLoad();
/**
* 刷新數(shù)據(jù)加載<br/>
* 配合{@link #lazyLoad()}捞稿,在頁面非第一次可見時刷新數(shù)據(jù)
*/
protected abstract void visibleReLoad();
/**
* 當頁面從可見變?yōu)椴豢梢姇r,釋放部分數(shù)據(jù)和資源。<br/>
* 比如頁面播放器的釋放或者一些特別占資源的數(shù)據(jù)的釋放
*/
protected abstract void inVisibleRelease();
}
代碼注釋比較詳細了娱局,簡單說一下彰亥。BaseLazyLoadFragment中提供了
-
lazyLoad()
方法當頁面被顯示時做懶加載; -
visibleReLoad()
方法當頁面沒有被釋放且從不可見狀態(tài)切換到可見時刷新數(shù)據(jù)用衰齐; -
inVisibleRelease()
方法當頁面從可見狀態(tài)切換到不可見時任斋,做部分資源釋放(如播放器等)。 - 同樣支持當切換到其他Activity時耻涛,觸發(fā)
inVisibleRelease()
方法做資源釋放废酷,從Activity返回頁面時觸發(fā)visibleReLoad()
刷新加載數(shù)據(jù)。
小結
以上封裝的BaseLazyLoadFragment應該能夠滿足Fragment + ViewPager實現(xiàn)方式的大多數(shù)需求場景抹缕。
針對Fragment + ViewPager的懶加載實現(xiàn)澈蟆,還有一種實現(xiàn)方式:從ViewPager上入手,既然是因為ViewPager的預加載導致的Fragment的生命周期不可控歉嗓,那么關掉ViewPager的預加載就好了丰介。這種實現(xiàn)方式需要重寫ViewPager背蟆,需要閱讀ViewPager源碼針對預加載部分進行修改鉴分,而且在不同SDK版本的ViewPager的具體邏輯有差異,只能對某一版本的ViewPager進行修改带膀。
至于那種實現(xiàn)方式更合適志珍,那就需要按具體需求分析了。我個人比較推薦BaseLazyLoadFragment的實現(xiàn)方式垛叨,實現(xiàn)簡單伦糯、適配性更好也更加優(yōu)雅。