RecyclerView中出現item重複問題的一次記錄

問題背景

前不久我們項目中由用戶反饋說遇到筆記重複的問題,而且不只一次遇到類似的反饋。

這種重複筆記總是出現的feed流的中間位置,如下示意圖所示:

這個圖畫的有點醜,湊合看,意思大概就是這樣的。

接下來,我就得追蹤下這個問題了,開始時我幾乎就一口咬定是接口返回的有問題,由於前幾次後端沒有日誌,好像之前的反饋就那麼過去了,直到後面又出現一次重複筆記的問題,這次是公司內部員工出現的,於是後端也通過這個抓到了相應的日誌,發現返回的筆記的確沒有重複的,這下跑不掉了,就是前端的問題。

問題排查

於是,我又重新梳理了下代碼流程,發現有一處比較有嫌疑:

        ...
        if (...) {
            mItems[0] = noteItem
        } else {
            mItems.add(0, noteItem)
        }
        mAdapter.items = mItems
        mAdapter.notifyItemChanged(0)
        ...

鑑於是公司項目,我就省略掉業務邏輯了,這裏的代碼按照開發者的意圖是當RecyclerView第一個item如果已經是noteItem這種類型的時候,我們就將這個位置item替換成最新的,如果這個位置的item不是noteItem這個數據的話,我們需要手動把它添加到第一個位置去,到這裏實際上都沒有什麼問題。

但是,當我看到mAdapter.notifyItemChanged(0)這個方法,直覺告訴我這裏好像有點問題,當上面的邏輯走到else這裏的時候,會往list裏add一個新的item,但是這時候調用的刷新方法卻是notifyItemChanged(0)。

這個notifyItemChanged明顯是刷新某個item的方法,即當這個item裏的數據有變化時,調用這個方法去刷新這個item區域的UI,但是如果我們在adapter中add了一個新的item,再調用這個方法明顯是不行的,這裏是導致重複的原因嘛,我其實也不太確定。

問題復現

於是我寫了一個Demo試了下。

class RecyclerViewActivity : AppCompatActivity() {

    private val mDataList = ArrayList<Any>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_recycler_view)

        val layoutManager = LinearLayoutManager(this)
        layoutManager.orientation = RecyclerView.VERTICAL
        recyclerView.layoutManager = layoutManager

        val customAdapter =  CustomAdapter(mDataList)

        recyclerView.adapter = customAdapter

        for (i in 0..5) {
            mDataList.add("text: $i")
        }

        recyclerView.adapter?.notifyDataSetChanged()
    }

    //刷新方法
    fun refresh(view: View) {
        mDataList.add(0, "add item")
        recyclerView.adapter?.notifyItemChanged(0)
    }

}

構造一個普通的feed列表,每次點擊刷新按鈕,就會調用刷新方法,調用刷新方法的時候往index爲0的位置再add一個item,然後再調用notifyItemChanged(0)方法,上下滑動後,發現數據是重複了。

下面放個gif圖展示下效果。

從gif圖中可以看到,點擊刷新按鈕,添加了”add item“,往下滑動後,出現了兩個”text:5“的item,這個就是重複的item。

問題原因

我們看到notifyItemChanged的文檔說明:

notifyItemChanged(int position)
This is an item change event, not a structural change event.

RecyclerView中有兩種不同數據改變事件,一種叫(item changes)項目改變,另一種叫(structual changes)結構改變。項目改變指的是某個單個item的數據發生變化,這個時候沒有位置的改變。而structual changes則是有位置的變化發生,主要是數據源的變化會導致RecyclerView item位置發生變化。

我們這個問題是我們往數據源前面加了一個item,這個時候應該需要調用具有structual changes 的方法來刷新,而不是採用notifyItemChanged來刷新,因爲notifyItemChanged是一個item changes 的方法。

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