1. <div id="f8mbs"></div>
        您好,歡迎來到源碼搜藏網!分享精神,快樂你我!
        [加入VIP] 設為首頁 | 收藏本站 | 網站地圖 | Sitemap | TAG標簽
      2. 首 頁
      3. 在線工具
      4. jquery手冊
      5. 當前位置:首頁 > 安卓源碼 > 技術博客 >

        Android Fragment 懶加載實踐

        時間:2019-02-14 13:54 來源:浜掕仈緗 作者:源碼搜藏 瀏覽:收藏 挑錯 推薦 打印

        開發中,Fragment 最常見的兩種使用方式就是 ViewPager 嵌套 Fragment ,以及直接通過FragmentManager 來管理 Fragment,對應的交互場景相信大家心里都有一個原型,沒有的話也沒關系,后邊會有例子的。但這和懶加載有什么關系呢?試想一下,如果每個 Fragmen

        開發中,Fragment 最常見的兩種使用方式就是 ViewPager 嵌套 Fragment ,以及直接通過FragmentManager 來管理 Fragment,對應的交互場景相信大家心里都有一個原型,沒有的話也沒關系,后邊會有例子的。但這和懶加載有什么關系呢?試想一下,如果每個 Fragment 都有默認的網絡請求操作(也可能是其它耗時操作,這里以網絡請求為例),那么多個在 Fragment創建過程中都會執行默認網絡請求,無論 Fragment 是否對用戶可見,顯然有些浪費流量、影響性 App 性能、用戶體驗不佳等缺點,這些自然不是我們想看到的,出于這些原因,讓 Fragment 進行數據懶加載就有必要了。

        Android Fragment 懶加載實踐

        先解釋下為什么會出現多個 Fragment中的默認網絡請求都會被執行,由于Fragment在創建的整個過程會走完從onAttach()onResume()的生命周期方法,然而一般情況我們無非在這里幾個生命周期方法(例如 onActivityCreated())里發起默認的網絡請求,所以問題的原因顯而易見,既然不能在 Fragment 生命周期方法直接請求數據,所以就要另謀它法。

        我們要做的事情就是讓 Fragment 按需加載數據,即對用戶可見時再請求數據,讓數據的請求時機可控,而不是在初始化創建過程中直接請求數據,同時不受嵌套層級的影響!

        接下來我們結合文章開頭提到的兩種 Fragment 使用方式來實現 Fragment 懶加載的功能。

        一、ViewPager 嵌套 Fragment

        Fragment 有一個非生命周期的setUserVisibleHint(boolean isVisibleToUser)回調方法,當 ViewPager 嵌套 Fragment 時會起作用,如果切換 ViewPager 則該方法也會被調用,參數isVisibleToUsertrue代表當前 Fragment 對用戶可見,否則不可見。

        目測可以在這個方法中來判斷是否請求數據,但在 Fragment 創建期間`setUserVisibleHint[BaseFragment](),代碼如下:

        public abstract class LazyLoadFragment extends BaseFragment {
            private boolean isViewCreated; // 界面是否已創建完成
            private boolean isVisibleToUser; // 是否對用戶可見
            private boolean isDataLoaded; // 數據是否已請求
        
            // 實現具體的數據請求邏輯
            protected abstract void loadData();
        
            @Override
            public void setUserVisibleHint(boolean isVisibleToUser) {
                super.setUserVisibleHint(isVisibleToUser);
                this.isVisibleToUser = isVisibleToUser;
                tryLoadData();
            }
        
            @Override
            public void onActivityCreated(@Nullable Bundle savedInstanceState) {
                super.onActivityCreated(savedInstanceState);
                isViewCreated = true;
                tryLoadData();
            }
        
            public void tryLoadData() {
                if (isViewCreated &amp;&amp; isVisibleToUser &amp;&amp; !isDataLoaded) {
                    loadData();
                    isDataLoaded = true;
                }
            }
        }

        ViewPager 中第一個可見的 Fragment 會走onActivityCreated()方法去請求數據,之后切換 Fragment 會走setUserVisibleHint()方法去嘗試請求數據。這樣我們的 Fragment 繼承 LazyLoadFragment,然后實現loadData()方法去完成數據的請求即可,寫一個簡單的 ViewPager 嵌套 Fragment 的界面,測試效果如下:

        Android Fragment 懶加載實踐

        一切正常,符合我們的預期,由于現在只是一層嵌套,我們再加兩層 ViewPager 嵌套 Fragment 試試,如下圖(具體的實現可參考源碼):

        Android Fragment 懶加載實踐

        1、這里我們約定用 tab 標簽上的編號指代對應的 Fragment,例如1-1代表最外層 ViewPager 的第一個 Fragment。
        2、ViewPager 都設置setOffscreenPageLimit()為其包含的 Fragment 個數

        再次運行,只有1-1對用戶可見,按照預期應該只有1-1請求了數據,但是2-13-1也請求了數據:

        Android Fragment 懶加載實踐

        所以問題來了,雖然2-13-1對用戶不可見,但在創建過程中它們的setUserVisibleHint()isVisibleToUser參數最終為true,從而在onActivityCreated()方法中請求了數據。注意此時2-13-1的父 Fragment 也是不可見的,所以要解決這個問題,可以在tryLoadData()方法中判斷當前要請求數據的 Fragment 的 父 Fragment 是否可見,不可見則不請求數據。

        但新的問題又來了,這個導致該 Fragment 失去了初次請求數據的機會,即便該 Fragment 初次對用戶可見時也不會主動去請求數據,需要來回再切換一次才會請求數據,要解決這個問題,可以讓該 Fragment 的父 Fragment 請求數據時通知子 Fragment 去請求數據,修改下代碼:

        public abstract class LazyLoadFragment extends BaseFragment {
            private boolean isViewCreated; // 界面是否已創建完成
            private boolean isVisibleToUser; // 是否對用戶可見
            private boolean isDataLoaded; // 數據是否已請求, isNeedReload()返回false的時起作用
        
            // 實現具體的數據請求邏輯
            protected abstract void loadData();
        
            @Override
            public void setUserVisibleHint(boolean isVisibleToUser) {
                super.setUserVisibleHint(isVisibleToUser);
                this.isVisibleToUser = isVisibleToUser;
                tryLoadData();
            }
        
            @Override
            public void onActivityCreated(@Nullable Bundle savedInstanceState) {
                super.onActivityCreated(savedInstanceState);
                isViewCreated = true;
                tryLoadData();
            }
        
            /**
             * ViewPager場景下,判斷父fragment是否可見
             *
             * @return
             */
            private boolean isParentVisible() {
                Fragment fragment = getParentFragment();
                return fragment == null || (fragment instanceof LazyLoadFragment &amp;&amp; ((LazyLoadFragment) fragment).isVisibleToUser);
            }
        
            /**
             * ViewPager場景下,當前fragment可見,如果其子fragment也可見,則嘗試讓子fragment請求數據
             */
            private void dispatchParentVisibleState() {
                FragmentManager fragmentManager = getChildFragmentManager();
                List<Fragment> fragments = fragmentManager.getFragments();
                if (fragments.isEmpty()) {
                    return;
                }
                for (Fragment child : fragments) {
                    if (child instanceof LazyLoadFragment &amp;&amp; ((LazyLoadFragment) child).isVisibleToUser) {
                        ((LazyLoadFragment) child).tryLoadData();
                    }
                }
            }
        
            public void tryLoadData() {
                if (isViewCreated &amp;&amp; isVisibleToUser &amp;&amp; isParentVisible() &amp;&amp;  !isDataLoaded) {
                    loadData();
                    isDataLoaded = true;
                    // 通知 子 Fragment 請求數據
                    dispatchParentVisibleState();
                }
            }
        }

        再次測試效果如下:

        Android Fragment 懶加載實踐

        效果符合預期,由于1-22-1同時可見,所以會幾乎同時請求數據,2-23-1也類似。

        至此 ViewPager 嵌套 Fragment 形式的懶加載就實現了。

        二、FragmentManager 管理 Fragment

        FragmentManager 管理 Fragment 時,和 ViewPager 嵌套 Fragment 中的問題類似,但此時setUserVisibleHint()方法并不會被調用,所以要尋找新的途徑了。

        當用 FragmentManager 來 add()hide()show() Fragment 時 Fragment 的onHiddenChanged(boolean hidden)方法會被調用,其中hidden參數為false時代表對應 Fragment 可見,否則不可見,注意三個操作里當執行 show()操作時hidden參數才為false,同時由于該方法在onActivityCreated()之后被調用。我們可以直接在onHiddenChanged()方法參數為false時發起數據請求即可。

        當存在多層嵌套的情況時,即 FragmentManager 管理的 Fragment 內部又使用 FragmentManager 管理新的 Fragment,這種情況和多層 ViewPager 嵌套 Fragment 時的處理方法類似,即判斷當前 Fragment 的父 Fragment 是否可見、以及 Fragment 可見時通知子 Fragment 去請求數據。

        主要的問題就這些,看下代碼實現:

        public abstract class LazyLoadFragment extends BaseFragment {
            private boolean isDataLoaded; // 數據是否已請求
            private boolean isHidden = true; // 記錄當前fragment的是否隱藏
        
            // 實現具體的數據請求邏輯
            protected abstract void loadData();
        
            /**
             * 使用show()、hide()控制fragment顯示、隱藏時回調該方法
             *
             * @param hidden
             */
            @Override
            public void onHiddenChanged(boolean hidden) {
                super.onHiddenChanged(hidden);
                isHidden = hidden;
                if (!hidden) {
                    tryLoadData1();
                }
            }
        
            /**
             * show()、hide()場景下,當前fragment沒隱藏,如果其子fragment也沒隱藏,則嘗試讓子fragment請求數據
             */
            private void dispatchParentHiddenState() {
                FragmentManager fragmentManager = getChildFragmentManager();
                List<Fragment> fragments = fragmentManager.getFragments();
                if (fragments.isEmpty()) {
                    return;
                }
                for (Fragment child : fragments) {
                    if (child instanceof LazyLoadFragment &amp;&amp; !((LazyLoadFragment) child).isHidden) {
                        ((LazyLoadFragment) child).tryLoadData1();
                    }
                }
            }
        
            /**
             * show()、hide()場景下,父fragment是否隱藏
             *
             * @return
             */
            private boolean isParentHidden() {
                Fragment fragment = getParentFragment();
                if (fragment == null) {
                    return false;
                } else if (fragment instanceof LazyLoadFragment &amp;&amp; !((LazyLoadFragment) fragment).isHidden) {
                    return false;
                }
                return true;
            }
        
            /**
             * show()、hide()場景下,嘗試請求數據
             */
            public void tryLoadData1() {
                if (!isParentHidden() &amp;&amp; !isDataLoaded) {
                    loadData();
                    isDataLoaded = true;
                    dispatchParentHiddenState();
                }
            }
        }

        實際的測試效果如下:

        Android Fragment 懶加載實踐

        上邊我們用isDataLoaded控制 Fragment 只請求一次數據,如果需要每次 Fragment 可見都請求數據,我們只需對LazyLoadFragment做如下修改:

        public abstract class LazyLoadFragment extends BaseFragment {
            /**
             * fragment再次可見時,是否重新請求數據,默認為flase則只請求一次數據
             *
             * @return
             */
            protected boolean isNeedReload() {
                return false;
            }
        
            /**
             * ViewPager場景下,嘗試請求數據
             */
            public void tryLoadData() {
                if (isViewCreated &amp;&amp; isVisibleToUser &amp;&amp; isParentVisible() &amp;&amp; (isNeedReload() || !isDataLoaded)) {
                    loadData();
                    isDataLoaded = true;
                    dispatchParentVisibleState();
                }
            }
        
            /**
             * show()、hide()場景下,嘗試請求數據
             */
            public void tryLoadData1() {
                if (!isParentHidden() &amp;&amp; (isNeedReload() || !isDataLoaded)) {
                    loadData();
                    isDataLoaded = true;
                    dispatchParentHiddenState();
                }
            }
        }

        添加了一個isNeedReload()方法,如果子類需要每次可見都請求數據,重寫該方法返回true即可。

        1、對于Activity,getSupportFragmentManager()得到的是FragmentActivity的FragmentManager對象
        2、對于Fragment,getFragmentManager()得到的是父Fragment的FragmentManager對象,如果沒有父Fragment,則是FragmentActivity的FragmentManager對象
        3、getChildFragmentManager()得到是Fragment自身的FragmentManager對象

        至此,LazyLoadFragment 基類就完成了,只要繼承它就可以輕松實現懶加載功能

        Android Fragment 懶加載實踐轉載http://www.rhcg.tw/appboke/39531.html
        標簽:網站源碼
        辽宁十一选五单双