Android:Fragment懶載入的實現以及自己的封裝思路

Lesincs發表於2018-02-26

1.什麼是fragment懶載入以及為什麼要使用fragment懶載入。

先看下Demo實現的效果吧。大家對這種效果一定不陌生,知乎,掘金等app都用到了這種效果。

Android:Fragment懶載入的實現以及自己的封裝思路
這裡探索的懶載入是當viewpager 結合tablayout時,多個fragment在進行資料載入的時候,當且僅當fragment對使用者可見的時候,才進行資料載入,這裡的資料載入可以是網路請求,資料庫請求,是需要消耗資源的,如果不使用懶載入,對於那些使用者未開啟的頁面,由於viewpager的載入機制,即使使用者未開啟的fragment也有可能進行資料載入,造成資源白白的浪費,因此為了良好的使用者體驗,懶載入是有必要的。

2.fragment懶載入的實現方式以及封裝。

fragment懶載入實現的關鍵在於其的setUserVisibleHint(isVisibleToUser: Boolean)方法,該方法在fragment對使用者由可見變為不可見以及由不可見變為可見時都會回撥。我們建立抽象AbstractLazyInitFrag,對其進行封裝。首先我們引入isVisibleToUser變數,負責儲存當前fragment對使用者的可見狀態。同時還有幾個值得注意的地方:

  1. setUserVisibleHint(isVisibleToUser: Boolean)方法的回撥時機並沒有與fragment的生命週期有確切的關聯,比如說,回撥時機有可能在onCreateView方法之後,也可能在onCreateView方法之前。因此,必須引入一個標誌位isPrepareView判斷view是否建立完成,不然,很容易會造成空指標異常。我們初始化該變數為false,在onViewCreated中,也就是view建立完成後,將其賦值為true。程式碼中是這樣:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        isPrepareView = true
    }
複製程式碼
  1. 資料初始化只應該載入一次,因此,引入第二個標誌位,isInitData,初始為false,在資料載入完成之後,將其賦值為true。至此,我們的懶載入方法考慮了所有條件。也就是當isVisibleToUsertrueisInitDatafalseisPrepareViewtrue時,進行資料載入,並且載入後為了防止重複呼叫,將isInitData賦值為true。程式碼如下:
private fun lazyInitData() {
        if (!isInitData && isVisibleToUser && isPrepareView) {
            isInitData = true
            initData()
        }
    }
複製程式碼

其中initData()為抽象方法,由子類實現,在這裡運算元據載入的邏輯。

  1. 該方法的呼叫時機。首先是 setUserVisibleHint(isVisibleToUser: Boolean)方法中是必須呼叫的。程式碼如下:
/*當fragment由可見變為不可見和不可見變為可見時回撥*/
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        this.isVisibleToUser = isVisibleToUser //標誌位儲存fragment對使用者的可見狀態
        lazyInitData()
    }
複製程式碼

其次,很容易忽略的一點。對於上圖中第一個fragment,如果setUserVisibleHint(isVisibleToUser: Boolean)方法在onCreateView之前呼叫的話,如果懶載入方法只在setUserVisibleHint(isVisibleToUser: Boolean)中呼叫,那麼該fragment將只能在被主動切換一次之後才能載入資料,這肯定是不可能的,因此,我們需要在view建立完成之後,也進行一次呼叫。思來想去,在onActivityCreated方法中是最合適的。我們在繼承的時候,在onViewCreated方法中進行一些初始化就行了,這樣不會引起衝突。程式碼如下:

/*fragment生命週期中onViewCreated之後的方法 在這裡呼叫一次懶載入 避免第一次可見不載入資料*/
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        lazyInitData()
    }
複製程式碼

貼上完整的封裝好的抽象基類fragment:

abstract class AbstractLazyInitFrag : Fragment() {

    private var isInitData = false /*標誌位 判斷資料是否初始化*/
    private var isVisibleToUser = false /*標誌位 判斷fragment是否可見*/
    private var isPrepareView = false /*標誌位 判斷view已經載入完成 避免空指標操作*/

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
       return inflater.inflate(getLayoutId(), container, false)
    }

    abstract fun getLayoutId(): Int

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        isPrepareView = true
    }

    /*載入資料的方法,由子類實現*/
    abstract fun initData()

    /*懶載入方法*/
    private fun lazyInitData() {
        if (!isInitData && isVisibleToUser && isPrepareView) {
            isInitData = true
            initData()
        }
    }

    /*當fragment由可見變為不可見和不可見變為可見時回撥*/
    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        this.isVisibleToUser = isVisibleToUser
        lazyInitData()
    }

    /*fragment生命週期中onViewCreated之後的方法 在這裡呼叫一次懶載入 避免第一次可見不載入資料*/
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        lazyInitData()
    }
}
複製程式碼

順便貼下Demo中測試的繼承的子類:

class FragLazyInitTest : AbstractLazyInitFrag() {

    private val dataList = ArrayList<String>()
    private val adapter = ListAdapter(dataList)

    /*初始化一般在這裡實現*/
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        recyclerView.layoutManager = LinearLayoutManager(context)
        recyclerView.adapter = adapter
    }

    override fun getLayoutId(): Int {
        return R.layout.frag_lazy_init_test
    }

    override fun initData() {
        /*模擬的載入資料過程,實際場景一般是網路請求或者資料庫等耗時操作*/
        swipeRefreshLayoutFLIT.isRefreshing = true
        swipeRefreshLayoutFLIT.postDelayed({
            swipeRefreshLayoutFLIT.isRefreshing = false
            dataList.add("data1")
            dataList.add("data2")
            dataList.add("data3")
            dataList.add("data4")
            dataList.add("data5")
            dataList.add("data6")
            dataList.add("data7")
            dataList.add("data8")
            adapter.notifyDataSetChanged()
        }, 2000)
    }
}
複製程式碼

3.Demo地址

github.com/Lesincs/Laz…

4.最後

這是第一次在掘金上寫文章,也幾乎是第一次寫部落格吧。感覺描述的很凌亂,大家如果有疑問可以去看看Demo,然後多多交流.

相關文章