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,這將帶來如下好處:

  1. 基於 Lifecycle 的懶加載更加科學,可以配合 Livedata 等組件在MVVM架構中使用
  2. 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<Fragment> = 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<Fragment>
    ) :
        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

面試前複習路線參考

接下來分享的系統學習資源以詳解各大互聯網公司的 Android 常見面試題爲主線,從面試的角度帶你介紹必備知識點,以及該知識點在項目中的實際應用

幫你在現在的基礎上,重新梳理和建立 Android 開發的知識體系。無論是你短期內想提升 Android 內功實力,突破自己工作中的能力瓶頸,還是準備參加 Android 面試,都會在這份資料中有所一些收穫。

從架構基礎開始,分了8個模塊來逐步從基礎進階到架構師的環節:

多餘的話就不講了,接下來將分享面試的一個複習路線,如果你也在準備面試但是不知道怎麼高效複習,可以參考一下我的複習路線,有任何問題也歡迎一起互相交流,加油吧!

首先是超級詳細得不能再詳細的Android開發學習思維導圖,因爲圖片實在是太大了,所以我就只把二級目錄的內容放出來,更加詳細的你們可以私信【資料】查看免費領取方式。

接下來就需要梳理知識,提升儲備了!(Android移動架構師七大專題學習資源)

  • 架構師築基必備技能:深入Java泛型+註解深入淺出+併發編程+數據傳輸與序列化+Java虛擬機原理+反射與類加載+動態代理+高效IO
  • Android高級UI與FrameWork源碼:高級UI晉升+Framework內核解析+Android組件內核+數據持久化
  • 360°全方面性能調優:設計思想與代碼質量優化+程序性能優化+開發效率優化
  • 解讀開源框架設計思想:熱修復設計+插件化框架解讀+組件化框架設計+圖片加載框架+網絡訪問框架設計+RXJava響應式編程框架設計+IOC架構設計+Android架構組件Jetpack
  • NDK模塊開發:NDK基礎知識體系+底層圖片處理+音視頻開發
  • 微信小程序:小程序介紹+UI開發+API操作+微信對接
  • Hybrid 開發與Flutter:Html5項目實戰+Flutter進階

知識梳理完之後,就需要進行查漏補缺,所以針對這些知識點,我手頭上也準備了不少的電子書和筆記,這些筆記將各個知識點進行了完美的總結。

然後再是通過源碼來系統性地學習

只要是程序員,不管是Java還是Android,如果不去閱讀源碼,只看API文檔,那就只是停留於皮毛,這對我們知識體系的建立和完備以及實戰技術的提升都是不利的。

真正最能鍛鍊能力的便是直接去閱讀源碼,不僅限於閱讀各大系統源碼,還包括各種優秀的開源庫。

刷大廠面試題備戰,增加大廠通過率

歷時半年,整理了這份市面上最全面的安卓面試題解析大全。

1.可以通過目錄索引直接翻看需要的知識點,查漏補缺。
2.五角星數表示面試問到的頻率,代表重要推薦指數

以上這些內容均免費分享給大家,需要完整版的朋友,簡信我回復【資料】免費領取!!!(或者在【主頁簡介】查看獲取方式)

最後還有耗時一年多整理的一系列Android學習資源:Android源碼解析、Android第三方庫源碼筆記、Android進階架構師七大專題學習、歷年BAT面試題解析包、Android大佬學習筆記等等,這些內容均免費分享給大家。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章