RecycleView角標越界問題分析

1、問題如下

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 19(offset:19).state:20 androidx.recyclerview.widget.RecyclerView{2a4d50e VFED..... .......D 0,0-1080,1979 #7f080160 app:id/rvNewsHome}, adapter:com.zj.architecture.mainscreen.TestNewsRvAdapter@fed2b2f, layout:androidx.recyclerview.widget.LinearLayoutManager@7de53c, context:com.zj.architecture.testrv.TestRvActivity@f4dbc62

2噪珊、模擬源碼如下

package com.zj.architecture.testrv

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.zj.architecture.R
import com.zj.architecture.mainscreen.TestNewsRvAdapter
import com.zj.architecture.repository.NewsItem
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*

class TestRvActivity : AppCompatActivity() {
    private var dataItem = mutableListOf<NewsItem>()
    private val newsRvAdapter by lazy {
        TestNewsRvAdapter(
            {

            }, dataItem
        )
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main_rv)
        fabStar.setOnClickListener {
            dataItem.removeAt(0)
            GlobalScope.launch {
                delay(3000)
                withContext(Dispatchers.Main) {

                    newsRvAdapter?.notifyItemRangeRemoved(0,dataItem.size)
                    newsRvAdapter?.notifyDataSetChanged()
                }
            }


        }
        newsRvAdapter.setHasStableIds(false)
        rvNewsHome.adapter = newsRvAdapter

        initData()
        srlNewsHome.setOnRefreshListener {
            initData()
            srlNewsHome.isRefreshing = false

        }
    }

    private fun initData() {
        dataItem.clear()
        for (i in 0 until 20) {
            var imageUrl = "https://t7.baidu.com/it/u=4162611394,4275913936&fm=193&f=GIF"
            if (i % 2 == 0) {
                imageUrl = "https://t7.baidu.com/it/u=1951548898,3927145&fm=193&f=GIF"
            }
            dataItem.add(NewsItem("title$i", "descriptioni$i", imageUrl))
        }
        rvNewsHome.adapter?.notifyDataSetChanged()
    }
}

3辨泳、問題拆解

  • 以上問題主要是由于內部數(shù)據(jù)源、與外部數(shù)據(jù)源長度不一致導致的要出。
  • 內部數(shù)據(jù)源如下:
class TestNewsRvAdapter(private val listener: (View) -> Unit, private var data: List<NewsItem>) :
    RecyclerView.Adapter<TestNewsRvAdapter.MyViewHolder>() {
    val TAG = "TestNewsRvAdapter"
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        Log.d(TAG, "onCreateViewHolder: ")
        return MyViewHolder(inflate(parent.context, R.layout.item_view, parent), listener)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        Log.d(TAG, "onBindViewHolder: ")
        holder.bind(data[position])
    }

    override fun getItemCount() = data.size

    override fun getItemId(position: Int): Long {
        return data[position].title.hashCode().toLong()
    }
    inner class MyViewHolder(override val containerView: View, listener: (View) -> Unit) :
        RecyclerView.ViewHolder(containerView),
        LayoutContainer {

        init {
            itemView.setOnClickListener(listener)
        }

        fun bind(newsItem: NewsItem) =
            with(itemView) {
                itemView.tag = newsItem
                tvTitle.text = newsItem.title
                tvDescription.text = newsItem.description
                ivThumbnail.load(newsItem.imageUrl) {
                    crossfade(true)
                    placeholder(R.mipmap.ic_launcher)
                }
            }
    }

}
  • 可以知道adapter內部設置的getItemCount,正是我們外部初始化傳入的dataItem
  • 在Activity里面,我們對dataItem做了刪除的操作出革。但是沒有立馬執(zhí)行notify等操作
  • 通過源碼RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline
if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                            + "position " + position + "(offset:" + offsetPosition + ")."
                            + "state:" + mState.getItemCount() + exceptionLabel());
                }
  • mAdapter.getItemCount()我們可以知道,內部數(shù)據(jù)源初始化的長度是20渡讼、由于我在Activity里面操作了刪除骂束,外部數(shù)據(jù)源dataItem長度變成了19.等待3秒后耳璧,執(zhí)行notify操作。這個時候展箱。我們執(zhí)行滑動操作旨枯。基于RecycleView的復用原理混驰∨矢簦可以知道,會執(zhí)行到以上源碼處栖榨,進行offsetPosition判斷竞慢。由于offsetPosition獲取的位置是根據(jù)外部數(shù)據(jù)源決定的。所以導致了治泥,內部跟外部數(shù)據(jù)源長度不一致筹煮。外部數(shù)據(jù)源長度19,內部數(shù)據(jù)源認為還是20.這個時候就出現(xiàn)了角標越界問題居夹。

解決方案:

1败潦、使用DiffUtils替代notify等操作,原理后續(xù)分析
2准脂、對外部數(shù)據(jù)源操作后劫扒,要及時執(zhí)行notify等操作。切勿類似demo中有延遲狸膏。很多業(yè)務其實都會忽略這一點沟饥,進行了很多耗時操作后,再執(zhí)行notify操作湾戳。這個會導致內部部數(shù)據(jù)源不一致的角標越界崩潰
3贤旷、網上很多說法,自定義LinerLayoutManager砾脑,個人認為這個無效幼驶。可能因為執(zhí)行順序的原因吧韧衣。

@Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
        }
    }

4盅藻、網上還有很多說法,去除動畫,其實畅铭,個人認為也沒什么必要氏淑。角標越界,從源碼分析來看硕噩,基本都是內外部數(shù)據(jù)源長度不一致導致的假残。動畫的執(zhí)行,耗時很短榴徐,類似我上面做的延遲守问。其實在這一點上匀归,如果不是很花里胡哨的寫了一堆動畫坑资,正常不用去掉

rvNewsHome.animation = null

5耗帕、以上4點我認為還有優(yōu)化空間,基于舊業(yè)務袱贮,不太可能大改仿便。所以,還在思考怎么處理更加合適攒巍。暫且先記錄

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末嗽仪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子柒莉,更是在濱河造成了極大的恐慌闻坚,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兢孝,死亡現(xiàn)場離奇詭異窿凤,居然都是意外死亡,警方通過查閱死者的電腦和手機跨蟹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門雳殊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窗轩,你說我怎么就攤上這事夯秃。” “怎么了痢艺?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵仓洼,是天一觀的道長。 經常有香客問我堤舒,道長衬潦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任植酥,我火速辦了婚禮镀岛,結果婚禮上,老公的妹妹穿的比我還像新娘友驮。我一直安慰自己漂羊,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布卸留。 她就那樣靜靜地躺著走越,像睡著了一般。 火紅的嫁衣襯著肌膚如雪耻瑟。 梳的紋絲不亂的頭發(fā)上旨指,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天蝶桶,我揣著相機與錄音,去河邊找鬼慎皱。 笑死铡买,一個胖子當著我的面吹牛,可吹牛的內容都是我干的搬素。 我是一名探鬼主播呵晨,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼熬尺!你這毒婦竟也來了摸屠?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤粱哼,失蹤者是張志新(化名)和其女友劉穎季二,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體揭措,經...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡胯舷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蜂筹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片需纳。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖艺挪,靈堂內的尸體忽然破棺而出不翩,到底是詐尸還是另有隱情,我是刑警寧澤麻裳,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布口蝠,位于F島的核電站,受9級特大地震影響津坑,放射性物質發(fā)生泄漏妙蔗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一疆瑰、第九天 我趴在偏房一處隱蔽的房頂上張望眉反。 院中可真熱鬧,春花似錦穆役、人聲如沸寸五。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梳杏。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間十性,已是汗流浹背叛溢。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留劲适,地道東北人楷掉。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像减响,于是被迫代替她去往敵國和親靖诗。 傳聞我的和親對象是個殘疾皇子郭怪,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容