問:ViewPager中的Fragment如何實作懶加載?
當被問到上述問題時,很多人可能首先會想到借助setUserVisiblity實作
如下,當Fragment可見時調用 onVisible 進而實作異步加載
@Override
public void setUserVisibleHint(boolean isVisibleToUser){
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
isVisible = true;
onVisible();
} else {
isVisible = false;
onInVisible();
}
}
複制代碼
放在兩年前,這個答案是OK的,但是2021年的今天還這麼回答可能就不過關了。
AndroidX 自 1.1.0-alpha07 起, 為 FragmentTransaction 增加了新的方法 setMaxLifeCycle, 官方建議開發者以此取代setUserVisibleHint,這将帶來如下好處:
基于 Lifecycle 的懶加載更加科學,可以配合 Livedata 等元件在MVVM架構中使用
setMaxLifeCycle 無需額外定義 Fragment 基類,使用起來更加無侵使用 setMaxLifecycle 進行懶加載
FragmentPagerAdapter 的構造方法新增了一個 behavior 參數,
當被設定為FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT時,會通過setMaxLifecycle 來限制 Fragment 的生命周期:隻有當 Fragment 顯示在螢幕中時才執行onResume()。
這樣就可以把加載資料等處理放在 onResume() 中進而實作懶加載了。
代碼如下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_main)
val viewPager: ViewPager = findViewById(R.id.viewpager)
val fragmentList: MutableList = ArrayList()
fragmentList.add(Fragment1())
fragmentList.add(Fragment2())
fragmentList.add(Fragment3())
// 為MyPagerAdapter擴充卡設定FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 參數
val myPagerAdapter: MyPagerAdapter = MyPagerAdapter(
getSupportFragmentManager(),
FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, fragmentList
)
viewPager.setAdapter(myPagerAdapter)
// 設定預加載為3頁,來測試懶加載是否成功
viewPager.offscreenPageLimit = 3
}
class MyPagerAdapter(
fm: FragmentManager,
behavior: Int,
val fragmentList: List
) :
FragmentPagerAdapter(fm, behavior) {
override fun getCount() = fragmentList.size
override fun getItem(position: Int) = fragmentList[position]
}
}
複制代碼
FragmentPagerAdapter 在建立 Fragment後,根據 behavior 調用了setMaxLifecycle。
//FragmentPagerAdapter.java
public FragmentPagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior){
mFragmentManager = fm;
mBehavior = behavior;
}
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position){
...
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
// mBehaviour為1的時候走新邏輯
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
// 初始化item時将其生命周期限制為STARTED
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
} else {
// 相容舊版邏輯
fragment.setUserVisibleHint(false);
}
}
return fragment;
}
@Override
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) {
...
// 滑走的會變成非主item, 設定其Lifecycle為STARTED
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
} else {
mCurrentPrimaryItem.setUserVisibleHint(false);
}
}
fragment.setMenuVisibility(true);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
...
// 設定新滑到的主item的Lifecycle為RESUMED
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
複制代碼
不借助 behavior,在自定義Adapter中建構 Framgent時直接調用setMaxLifecycle 也是等價的。
setMaxLifecycle 實作原理
setMaxLifecycle 使用方法很簡單,接下來通過梳理源碼了解一下實作原理(基于1.3.0-rc01),即使面試官追問其原理你也能沉着應對。
OP_SET_MAX_LIFECYCLE
我們知道 FramgentTransition 對 Fragment 的所有操作都将轉換為一個Op,針對setMaxLifecycle也同樣增加了一個新的Op -- OP_SET_MAX_LIFECYCLE, 專門用來設定生命周期的上限。
@NonNull
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
@NonNull Lifecycle.State state){
addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
return this;
}
複制代碼
當 FramgentTransition 對 Frament 添加了 OP_SET_MAX_LIFECYCLE 後,在實作類 BackStackRecord 中, FragmentManager 會周遊 Transaction 的 Op 清單
void executeOps(){
final int numOps = mOps.size();
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = mOps.get(opNum);
final Fragment f = op.mFragment;
//...
switch (op.mCmd) {
//...
// 新引入的這個Op類型, 在這裡會給這個Fragment設定允許的生命周期上限
case OP_SET_MAX_LIFECYCLE:
mManager.setMaxLifecycle(f, op.mCurrentMaxState);
break;
//...
}
}
複制代碼
當遇到 OP_SET_MAX_LIFECYCLE 時,通過調用 FragmentManager 的 setMaxLifeCycle 方法設定 fragment 的 mMaxState,以标記其生命周期上限
void setMaxLifecycle(@NonNull Fragment f, @NonNull Lifecycle.State state){
//...
f.mMaxState = state;
}
複制代碼
FragmentStateManager
FragmentManager 通過 FragmentStateManager 推進 Fragment 的生命周期。 推進過程中根據 mMaxState 對生命周期
值得一提的是,FragmentStateManager 是 1.3.0-alpha08 之後新增的類,将原來和 State 相關的邏輯從FragmentManager 抽離了出來, 降低了與 Fragment 的耦合, 職責更加單一。
看一下在 FragmentStateManager 中具體是如何推進 Fragment 生命周期的:
void moveToExpectedState(){
try {
...
// 循環計算聲明周期是否可以推進
while ((newState = computeExpectedState()) != mFragment.mState) {
if (newState > mFragment.mState) {
// 生命周期向前推進
int nextStep = mFragment.mState + 1;
//...
switch (nextStep) {
//...
case Fragment.ACTIVITY_CREATED:
//...
case Fragment.STARTED:
start();
break;
//...
case Fragment.RESUMED:
resume();
break;
}
} else {
// 如果應有的生命周期小于目前, 後退
int nextStep = mFragment.mState - 1;
//...
switch (nextStep) {
// 與上面的switch類似
//...
}
}
}
...
}
...
}
複制代碼
int computeExpectedState(){
// 其他計算expected state的邏輯, 算出maxState
//...
// mMaxState 對生命周期做出限制
switch (mFragment.mMaxState) {
case RESUMED:
break;
case STARTED:
maxState = Math.min(maxState, Fragment.STARTED);
break;
case CREATED:
maxState = Math.min(maxState, Fragment.CREATED);
break;
default:
maxState = Math.min(maxState, Fragment.INITIALIZING);
}
// 其他計算expected state的邏輯, 算出 maxState
// ...
return maxState;
}
複制代碼
整體流程圖如下
最後
除了使用預設的 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,我們甚至可以在自定義 Adapter 的instantiateItem 中為将 Fragment的 MaxLifecycle 設定為 CREATED, 這樣可以讓 Fragment 隻走到onCreate 進而延遲更多操作,比如在 onCreateView 中的 inflate 以及 onViewCreated 中的一些操作。 Fragment 1.3.0-rc01 已經支援設定最大生命周期為 INITIALIZED