Android Fragment 資料動態更新的問題

來源:互聯網
上載者:User

標籤:

在一個 Android 應用中,我使用  FragmentPagerAdapter 來處理多 Fragment 頁面的橫向滑動。不過我碰到了一個問題,即當 Fragment 對應的資料集發生改變時,我希望能夠通過調用mAdapter.notifyDataSetChanged() 來觸發 Fragment 頁面使用新的資料調整或重建其內容,可是當我調用 notifyDataSetChanged() 後,發現什麼都沒發生。   搜尋之後發現不止我一個人碰到這個問題,大家給出的解決辦法五花八門,有些確實解決了問題,但是我總感覺問題沒搞清楚。於是我決定搞明白這個問題到底是怎麼回事,以及正確的用法到底如何。要搞明白這個問題,僅僅閱讀文檔並不足夠,還需要閱讀相關幾個類的相關方法的實現,搞懂其設計意圖。下面就是通過閱讀原始碼搞明白的內容。     【ViewPager】
ViewPager 如其名所述,是負責翻頁的一個 View。準確說是一個  ViewGroup,包含多個 View 頁,在手指橫向滑動螢幕時,其負責對 View 進行切換。為了產生這些 View 頁,需要提供一個  PagerAdapter 來進行和資料繫結以及產生最終的 View 頁。
  • setAdapter()
    • ViewPager 通過 setAdapter() 來建立與 PagerAdapter 的聯絡。這個聯絡是雙向的,一方面,ViewPager 會擁有 PagerAdapter 對象,從而可以在需要時調用 PagerAdapter 的方法;另一方面,ViewPager 會在 setAdapter() 中調用 PagerAdapter 的 registerDataSetObserver() 方法,註冊一個自己產生的 PagerObserver 對象,從而在 PagerAdapter 有所需要時(如 notifyDataSetChanged() 或 notifyDataSetInvalidated() 時),可以調用 Observer 的 onChanged() 或 onInvalidated() 方法,從而實現 PagerAdapter 向 ViewPager 方向發送資訊。
  • dataSetChanged()
    • 在 PagerObserver.onChanged(),以及 PagerObserver.onInvalide() 中被調用。因此當 PagerAdapter.notifyDataSetChanged() 被觸發時,ViewPager.dataSetChanged() 也可以被觸發。該函數將使用 getItemPosition() 的傳回值來進行判斷,如果為 POSITION_UNCHANGED,則什麼都不做;如果為 POSITION_NONE,則調用 PagerAdapter.destroyItem() 來去掉該對象,並設定為需要重新整理 (needPopulate = true) 以便觸發 PagerAdapter.instantiateItem() 來產生新的對象。
  【PagerAdapter】

PageAdapter 是 ViewPager 的支援者,ViewPager 將調用它來取得所需顯示的頁,而 PageAdapter 也會在資料變化時,通知 ViewPager。這個類也是FragmentPagerAdapter 以及 FragmentStatePagerAdapter 的基類。如果繼承自該類,至少需要實現 instantiateItem(), destroyItem(), getCount() 以及 isViewFromObject()。

  • getItemPosition()
    • 該函數用以返回給定對象的位置,給定對象是由 instantiateItem() 的傳回值。
    • 在 ViewPager.dataSetChanged() 中將對該函數的傳回值進行判斷,以決定是否最終觸發 PagerAdapter.instantiateItem() 函數。
    • 在 PagerAdapter 中的實現是直接傳回 POSITION_UNCHANGED。如果該函數不被重載,則會一直返回 POSITION_UNCHANGED,從而導致 ViewPager.dataSetChanged() 被調用時,認為不必觸發 PagerAdapter.instantiateItem()。很多人因為沒有重載該函數,而導致調用
       PagerAdapter.notifyDataSetChanged() 後,什麼都沒有發生。
  • instantiateItem()
    • 在每次 ViewPager 需要一個用以顯示的 Object 的時候,該函數都會被 ViewPager.addNewItem() 調用。
  • notifyDataSetChanged()
    • 在資料集發生變化的時候,一般 Activity 會調用 PagerAdapter.notifyDataSetChanged(),以通知 PagerAdapter,而 PagerAdapter 則會通知在自己這裡註冊過的所有 DataSetObserver。其中之一就是在 ViewPager.setAdapter() 中註冊過的 PageObserver。PageObserver 則進而調用 ViewPager.dataSetChanged(),從而導致 ViewPager 開始觸發更新其內含 View 的操作。
  【FragmentPagerAdapter】   FragmentPagerAdapter 繼承自 PagerAdapter。相比通用的 PagerAdapter,該類更專註於每一頁均為 Fragment 的情況。如文檔所述, 該類內的每一個產生的 Fragment 都將儲存在記憶體之中,因此適用於那些相對靜態頁,數量也比較少的那種;如果需要處理有很多頁,並且資料動態性較大、佔用記憶體較多的情況,應該使用 FragmentStatePagerAdapter。FragmentPagerAdapter 重載實現了幾個必須的函數,因此來自 PagerAdapter 的函數,我們只需要實現 getCount(),即可。且,由於 FragmentPagerAdapter.instantiateItem() 的實現中,調用了一個新增的虛函數 getItem(),因此,我們還至少需要實現一個 getItem()。因此,總體上來說,相對於繼承自 PagerAdapter,更方便一些。
  • getItem()
    • 該類中新增的一個虛函數。函數的目的為產生新的 Fragment 對象。重載該函數時需要注意這一點。在需要時,該函數將被 instantiateItem() 所調用。
    • 如果需要向 Fragment 對象傳遞相對靜態資料時,我們一般通過 Fragment.setArguments() 來進行,這部分代碼應當放到 getItem()。它們只會在新產生 Fragment 對象時執行一遍。
    • 如果需要在產生 Fragment 對象後,將資料集裡面一些動態資料傳遞給該 Fragment,那麼,這部分代碼不適合放到 getItem() 中。因為當資料集發生變化時,往往對應的 Fragment 已經產生,如果傳遞資料部分代碼放到了 getItem() 中,這部分代碼將不會被調用。這也是為什麼很多人發現調用 PagerAdapter.notifyDataSetChanged() 後,getItem() 沒有被調用的一個原因。
  • instantiateItem()
    • 函數中判斷一下要產生的 Fragment 是否已經產生過了,如果產生過了,就使用舊的,舊的將被 Fragment.attach();如果沒有,就調用 getItem() 產生一個新的,新的對象將被 FragmentTransation.add()。
    • FragmentPagerAdapter 會將所有產生的 Fragment 對象通過 FragmentManager 儲存起來備用,以後需要該 Fragment 時,都會從 FragmentManager 讀取,而不會再次調用 getItem() 方法
    • 如果需要在產生 Fragment 對象後,將資料集中的一些資料傳遞給該 Fragment,這部分代碼應該放到這個函數的重載裡。在我們繼承的子類中,重載該函數,並調用 FragmentPagerAdapter.instantiateItem() 取得該函數返回 Fragment 對象,然後,我們該 Fragment 對象中對應的方法,將資料傳遞過去,然後返回該對象。
    • 否則,如果將這部分傳遞資料的代碼放到 getItem()中,在 PagerAdapter.notifyDataSetChanged() 後,這部分資料設定代碼將不會被調用。
  • destroyItem()
    • 該函數被調用後,會對 Fragment 進行 FragmentTransaction.detach()。這裡不是 remove(),只是 detach(),因此 Fragment 還在 FragmentManager 管理中,Fragment 所佔用的資源不會被釋放。
  【FragmentStatePagerAdapter】   FragmentStatePagerAdapter 和前面的 FragmentPagerAdapter 一樣,是繼承子 PagerAdapter。但是,和 FragmentPagerAdapter 不一樣的是,正如其類名中的 ‘State‘ 所表明的含義一樣,該 PagerAdapter 的實現將只保留當前頁面,當頁面離開視線後,就會被消除,釋放其資源;而在頁面需要顯示時,產生新的頁面(就像 ListView 的實現一樣)。這麼實現的好處就是當擁有大量的頁面時,不必在記憶體中佔用大量的記憶體。  
  • getItem()
    • 一個該類中新增的虛函數。
    • 函數的目的為產生新的 Fragment 對象。
    • Fragment.setArguments() 這種只會在建立 Fragment 時執行一次的參數傳遞代碼,可以放在這裡。
    • 由於 FragmentStatePagerAdapter.instantiateItem() 在大多數情況下,都將調用 getItem() 來產生新的對象,因此如果在該函數中放置與資料集相關的 setter 代碼,基本上都可以在 instantiateItem() 被調用時執行,但這和設計意圖不符。畢竟還有部分可能是不會調用 getItem() 的。因此這部分代碼應該放到 instantiateItem() 中。
  • instantiateItem()
    • 除非碰到 FragmentManager 剛好從 SavedState 中恢複了對應的 Fragment 的情況外,該函數將會調用 getItem() 函數,產生新的 Fragment 對象。新的對象將被 FragmentTransaction.add()。
    • FragmentStatePagerAdapter 就是通過這種方式,每次都建立一個新的 Fragment,而在不用後就立刻釋放其資源,來達到節省記憶體佔用的目的的。
  • destroyItem()
    • 將 Fragment 移除,即調用 FragmentTransaction.remove(),並釋放其資源。
  討論   之前看到一些解決辦法,有的認為這是一個  bug,應該被修複;有的 建議不用 FragmentPagerAdapter,而改用 FragmentStatePagerAdapter,並且重載 getItemPosition() 並返回 POSITION_NONE,以觸發銷毀對象以及重建對象。從上面的分析中看,後者給出的建議確實可以達到調用 notifyDataSetChanged() 後,Fragment 被以新的參數重建立立的效果。   但是問題在於,如果我們只能這麼解決這個問題,豈不是 FragmentPagerAdapter 就用不上了?最關鍵的是,二者對應的情況不同。對於頁面相對較少的情況,我仍舊希望能夠將產生的 Fragment 儲存在記憶體中,在需要顯示的時候直接調用,而不要產生產生、銷毀對象的額外的開銷,這樣效率更高。這種情況下,選擇 FragmentPagerAdapter 是更適合,不加考慮的選擇 FragmentStatePagerAdapter 是不合適的。我們不能夠因噎廢食。   因此,對於 FragmentPagerAdapter 的解決方案就是,分別重載 getItem() 以及 instantiateItem() 對象。getItem() 只用於產生新的與資料無關的 Fragment;而 instantiateItem() 函數則先調用父類中的 instantiateItem() 取得所對應的 Fragment 對象,然後,根據對應的資料,調用該對象對應的方法進行資料設定。   當然,不要忘記重載 getItemPosition() 函數,返回 POSITION_NONE,這個兩個類的解決方案都需要的。二者不同之處在於,FragmentStatePagerAdapter 在會在因 POSITION_NONE 觸發調用的 destroyItem() 中真正的釋放資源,重建立立一個新的 Fragment;而 FragmentPagerAdapter 僅僅會在 destroyItem() 中 detach 這個 Fragment,在 instantiateItem() 時會使用舊的 Fragment,並觸發 attach,因此沒有釋放資源及重建的過程。   這樣,當 notifyDataSetChanged() 被調用後,會最終觸發 instantiateItem(),而不管 getItem() 是否被調用,我們都在重載的 instantiateItem() 函數中已經將所需要的資料傳遞給了相應的 Fragment。在 Fragment 接下來的 onCreateView(), onStart() 以及 onResume() 的事件中,它可以正確的讀取新的資料,Fragment 被成功複用了。   這裡需要注意一個問題,在 Fragment 沒有被添加到 FragmentManager 之前,我們可以通過 Fragment.setArguments() 來設定參數,並在 Fragment 中,使用 getArguments() 來取得參數。這是常用的參數傳遞方式。但是這種方式對於我們說的情況不適用。因為這種資料傳遞方式只可能用一次,在 Fragment 被添加到 FragmentManager 後,一旦被使用,我們再次調用 setArguments() 將會導致  java.lang.IllegalStateException: Fragment already active 異常。因此,我們這裡的參數傳遞方式選擇是,在繼承的 Fragment 子類中,新增幾個 setter,然後通過這些 setter 將資料傳遞過去。反向也是類似。相關資訊可以參考 [5]。哦,這些 setter 中要注意不要操作那些 View,這些 View 只有在 onCreateView() 事件後才可以操作。   針對 FragmentPagerAdapter 的解決辦法如下列代碼所示:
@Overridepublic Fragment getItem(int position) {    MyFragment f = new MyFragment();    return f;}@Overridepublic Object instantiateItem(ViewGroup container, int position) {    MyFragment f = (MyFragment) super.instantiateItem(container, position);    String title = mList.get(position);    f.setTitle(title);    return f;}@Overridepublic int getItemPosition(Object object) {    return PagerAdapter.POSITION_NONE;}



    參考   Android 文檔: [1]  http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html [2]  http://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter.html [3]  http://developer.android.com/reference/android/support/v4/app/FragmentStatePagerAdapter.html [4]  http://developer.android.com/reference/android/support/v4/view/ViewPager.html [5]  http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity   Android 原始碼: [6]  http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.1.1_r1/android/support/v4/view/PagerAdapter.java#PagerAdapter [7]  http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.1.1_r1/android/support/v4/app/FragmentPagerAdapter.java#FragmentPagerAdapter [8]  http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.1.1_r1/android/support/v4/app/FragmentStatePagerAdapter.java#FragmentStatePagerAdapter [9]  http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.1.1_r1/android/support/v4/view/ViewPager.java#ViewPager   Android Issue List: [10]  http://code.google.com/p/android/issues/detail?id=19001

Android Fragment 資料動態更新的問題

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.