RecyclerView刨析:viewHolder的缓存策略

Recyclerview相较于listview更灵活,可以实现多种常用的页面。垂直的列表,横向的列表,瀑布流,grid页面等。甚至我们能自定义一个layoutmanager实现垂直横向都能scroll的grid。但是没有如listview一样的支持header的api,不过没有关系,我们自己可以使用viewtype实现。
listview中的getview方法中可以使用convertview来实现view的重用,或者用其它的优化策略---例如,使用settag,gettag来减少findviewbyid的调用从而提高效率,使得列表更顺滑。这里我们对listview不做更多的讨论,主要分析recyclerview中对viewholder的缓存策略。

我们查看recyclerview的源码,找到

getViewForPosition(int position, boolean dryRun) 

方法,顺着代码往下看,我们可以了解到很多recyclerview的一些内部机制。例如,viewhodler recycling,hidden views,predictive animations,stable ids。

当layoutmanager请求recyclerview根据position给它一个合适的view的时候,recyclerview会做以下几步工作:

  • 查找 changed scrap
ViewHolder holder = null;
 // 0) If there is a changed scrap, try to find from there
 if (mState.isPreLayout()) {
       holder = getChangedScrapViewForPosition(position);
       fromScrapOrHiddenOrCache = holder != null;
 }
  • 查找 attached scrap
// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
     final ViewHolder holder = mAttachedScrap.get(i);
     if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
            return holder;
     }
 }
  • 查找 没有被移除的 hidden views
View view = mChildHelper.findHiddenNonRemovedView(position);
  • 查找 view cache
  • 如果adapter有stable ids,根据提供的id查找attached scrap和view cache
  • 查找 ViewCacheExtension
  • 查找RecycledViewPool

代码就不一一列出了。如果上述步骤都不能得到一个view,那么就会调用adapter中的onCreateViewHolder方法,然后调用onBindViewHolder方法绑定数据,然后返回给layoutmanager。
看了源码,可能会有更多的疑惑,为什么除了一个pool之外,还需要其它各种cache?下面我们就一个一个来分析
RecycledViewPool
支持多种viewtype,每个viewtype默认的容量是5,但是我们可以自定义,这样就提供了一种灵活性。

recyclerView.getRecycledViewPool()
            .setMaxRecycledViews(SOME_VIEW_TYPE, POOL_CAPACITY);

getRecyclerView putRecycledView clear等方法都是public的,我们可以直接操作pool中的view,但是不提倡这样做,应该总是使用onCreateViewHolder方法。
另外一个特性就是多个recyclerview可以共用一个recycledViewPool。

pool的策略

  • recyclerview scrolling的时候,childview已经超出其边界(这里的边界跟view cache有关,并不是严格意义的边界,后面会解释)
  • 数据集变化,引起某些view不再展示,那么这些view在动画完成后就会被加入pool
  • view cache中的view更新或移除后会被加入pool
  • 在查找viewholder的过程中,我们根据position在scrap或者cache中找到一个viewHolder,但是最后因为viewtype或者id错误而发现不合适
  • layoutmanager在pre-layout时加入了一个view,但是在post-layout的时候并没有加入它

pre-layout, post-layout and predictive animations
思考下列情形,我们有三个item a,b,c。a和b正好显示并占满整个recyclerview的屏幕空间。当我们删除b的时候,c就会被带到recyclerview的屏幕空间(可见)。

0-ZxzTdRCpireBVs4t.png

我们就会看到c从底部平滑的滑动到屏幕上。但是这是怎么发生的呢?我们知道最终状态下c的位置,但是我们并不知道开始状态下c的位置。因为recyclerview的机制并不会每次都提前layout多余的item,这样就会浪费资源。

google提供的解决办法如下。当adapter notifychange的时候,recyclerview向layoutmanager请求俩次layout,而不是一次。第一次-pre-layout,它会根据adapter的初始状态layout items,在这个过程中就会layout额外的view。在上面的例子中,我们知道b会被删除,那么我们就会把c也进行layout操作,虽然c并没有在屏幕上,但是它即将展示到屏幕上。 第二次 -- post-layout,adapter执行完notifychange操作后的状态就是正常状态,只要根据adapter正常的layout view即可。

0-8dtqyWtaehYM-7zm-.png

现在我们比较c在pre-layout和post-layout中的位置,我们就能合适的执行对它的动画操作。

我们考虑另外一种情形---b不是被删除,而是数据有改变。


0-8dtqyWtaehYM-7zm-.png

layoutmanager照样会在执行pre-layout操作时layout c。因为b的改变可能会使得b的高度变小,从而使得c在最终状态中部分可见,谁知道会不会发生这种情况呢。所以最好的处理方式是在pre-layout layout它(额外量),在执行post-layout后,我们可能发现最后b的高度并没有变化,可能b中只是更改某个texview的text,那么c在这种情况下就不需要layout,那么c就会被放入到pool中,这种情形就是我们上面说的pool策略最后一点。

View Cache

RecyclerView.Recycler 中mCachedViews

  • 如果viewholder在所有的cache中还有pool中都没有查找到,那么就会调用adapter创建并绑定数据
  • 如果viewholder在pool中找到,那么它会重新绑定数据
  • 如果viewholder在cache中找到,那么什么额外的操作都不会有,直接展示在界面上。

Filling pool and cache

0-JKHlomcG7gdcpA7h-.png

上面的图只是使用默认的容量。填充的时序示意图

先填充cache,cache满了之后才会填充pool

Pool and Cache in Action

考虑scrolling的情形
0-loJwYG5qlTLTRiTX-.png

0-lcgR2438JvV5g3Sz-.png

如果该篇文章有任何问题,希望您能在百忙之中指出并联系我nanhuaqq@gmail.com

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 这篇文章分三个部分,简单跟大家讲一下 RecyclerView 的常用方法与奇葩用法;工作原理与ListView比...
    LucasAdam阅读 4,374评论 0 27
  • 简介: 提供一个让有限的窗口变成一个大数据集的灵活视图。 术语表: Adapter:RecyclerView的子类...
    酷泡泡阅读 5,137评论 0 16
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,431评论 25 707
  • 【四个收获】 解决职场方向问题的核心三问:我是谁?我有啥?我要去哪?人生困难重重是普遍现象,你不去克服就会走下坡路...
    华华素语阅读 115评论 0 0
  • “一,二,三……藏好了吗?”看到这个题目,人们的脑海里顿时会想起儿时一个古老的游戏,还有一群无忧无虑疯玩的熊孩子…...
    荒山雪阅读 477评论 0 1