天天看點

android懶加載的原理,一道面試題:ViewPager中的Framgent如何實作懶加載?

問: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 的耦合, 職責更加單一。

android懶加載的原理,一道面試題:ViewPager中的Framgent如何實作懶加載?

看一下在 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;

}

複制代碼

整體流程圖如下

android懶加載的原理,一道面試題:ViewPager中的Framgent如何實作懶加載?

最後

除了使用預設的 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,我們甚至可以在自定義 Adapter 的instantiateItem 中為将 Fragment的 MaxLifecycle 設定為 CREATED, 這樣可以讓 Fragment 隻走到onCreate 進而延遲更多操作,比如在 onCreateView 中的 inflate 以及 onViewCreated 中的一些操作。 Fragment 1.3.0-rc01 已經支援設定最大生命周期為 INITIALIZED