1. 程式人生 > >再也不用擔心問RecycleView了——面試真題詳解

再也不用擔心問RecycleView了——面試真題詳解

關於RecycleView,之前我寫過一篇比較基礎的文章,主要說的是快取和優化等問題。但是有讀者反映問題不夠實際和深入。於是,我又去淘了一些關於RecycleView的面試真題,大家一起看看吧,這次的問題如果都弄懂了,下次面試再遇到RecycleView應該就沒啥可擔心的了。 * 講一下`RecyclerView`的快取機制,滑動10個,再滑回去,會有幾個執行`onBindView`。快取的是什麼?`cachedView`會執行onBindView嗎? * `RecyclerView`預取機制 * 如何實現`RecyclerView`的區域性更新,用過`payload`嗎,notifyItemChange方法中的引數? * `RecyclerView`巢狀`RecyclerView`滑動衝突,NestScrollView巢狀RecyclerView。 ## 講一下RecyclerView的快取機制,滑動10個,再滑回去,會有幾個執行onBindView。快取的是什麼?cachedView會執行onBindView嗎? ## RecyclerView預取機制 這兩個問題都是關於快取的,我就一起說了。 1)首先說下RecycleView的快取結構: Recycleview有四級快取,分別是`mAttachedScrap(螢幕內),mCacheViews(螢幕外),mViewCacheExtension(自定義快取),mRecyclerPool(快取池)` * `mAttachedScrap(螢幕內)`,用於螢幕內itemview快速重用,不需要重新createView和bindView * `mCacheViews(螢幕外)`,儲存最近移出螢幕的ViewHolder,包含資料和position資訊,複用時必須是相同位置的ViewHolder才能複用,應用場景在那些需要來回滑動的列表中,當往回滑動時,能直接複用ViewHolder資料,不需要重新bindView。 * `mViewCacheExtension(自定義快取)`,不直接使用,需要使用者自定義實現,預設不實現。 * `mRecyclerPool(快取池)`,當cacheView滿了後或者adapter被更換,將cacheView中移出的ViewHolder放到Pool中,放之前會把ViewHolder資料清除掉,所以複用時需要重新bindView。 2)四級快取按照順序需要依次讀取。所以**完整快取流程**是: 1) 儲存快取流程: * 插入或是刪除`itemView`時,先把螢幕內的ViewHolder儲存至`AttachedScrap`中 * 滑動螢幕的時候,先消失的itemview會儲存到`CacheView`,CacheView大小預設是2,超過數量的話按照先入先出原則,移出頭部的itemview儲存到`RecyclerPool快取池`(如果有自定義快取就會儲存到自定義快取裡),RecyclerPool快取池會按照itemview的`itemtype`進行儲存,每個itemType快取個數為5個,超過就會被回收。 2) 獲取快取流程: * AttachedScrap中獲取,通過pos匹配holder——>獲取失敗,從`CacheView`中獲取,也是通過pos獲取holder快取 ——>獲取失敗,從`自定義快取`中獲取快取——>獲取失敗,從`mRecyclerPool`中獲取 ——>獲取失敗,重新建立`viewholder`——createViewHolder並bindview。 3)瞭解了快取結構和快取流程,我們再來看看具體的問題 滑動10個,再滑回去,會有幾個執行onBindView? * 由之前的快取結構可知,需要重新執行`onBindView`的只有一種快取區,就是快取池`mRecyclerPool`。 所以我們假設從載入`RecyclView`開始盤的話(頁面假設可以容納7條資料): * 首先,7條資料會依次呼叫`onCreateViewHolder`和`onBindViewHolder`。 * 往下滑一條(position=7),那麼會把position=0的資料放到`mCacheViews`中。此時`mCacheViews`快取區數量為1,`mRecyclerPool`數量為0。然後新出現的position=7的資料通過postion在`mCacheViews`中找不到對應的`ViewHolder`,通過`itemtype`也在`mRecyclerPool`中找不到對應的資料,所以會呼叫`onCreateViewHolder`和`onBindViewHolder`方法。 * 再往下滑一條資料(position=8),如上。 * 再往下滑一條資料(position=9),position=2的資料會放到`mCacheViews`中,但是由於`mCacheViews`快取區預設容量為2,所以position=0的資料會被清空資料然後放到`mRecyclerPool`快取池中。而新出現的position=9資料由於在`mRecyclerPool`中還是找不到相應type的ViewHolder,所以還是會走`onCreateViewHolder`和`onBindViewHolder`方法。所以此時`mCacheViews`快取區數量為2,`mRecyclerPool`數量為1。 * 再往下滑一條資料(position=10),這時候由於可以在`mRecyclerPool`中找到相同viewtype的ViewHolder了。所以就直接複用了,並呼叫`onBindViewHolder`方法繫結資料。 * 後面依次類推,剛消失的兩條資料會被放到`mCacheViews`中,再出現的時候是不會呼叫onBindViewHolder方法,而複用的第三條資料是從`mRecyclerPool`中取得,就會呼叫`onBindViewHolder`方法了。 4)所以這個問題就得出結論了(假設`mCacheViews`容量為預設值2): * 如果一開始滑動的是新資料,那麼滑動10個,就會走10個`bindview`方法。然後滑回去,會走10-2個`bindview`方法。一共18次呼叫。 * 如果一開始滑動的是老資料,那麼滑動10-2個,就會走8個`bindview`方法。然後滑回去,會走10-2個`bindview`方法。一共16次呼叫。 **但是但是**,實際情況又有點不一樣。因為`Recycleview`在v25版本引入了一個新的機制,`預取機制`。 `預取機制`,就是在滑動過程中,會把將要展示的一個元素提前快取到`mCachedViews`中,所以滑動10個元素的時候,第11個元素也會被建立,也就多走了一次`bindview`方法。但是滑回去的時候不影響,因為就算提前取了一個快取資料,只是把`bindview`方法提前了,並不影響總的繫結item數量。 所以滑動的是新資料的情況下就會多一次呼叫`bindview`方法。 5)總結,問題怎麼答呢? * 四級快取和流程說一下。 * 滑動10個,再滑回去,`bindview`可以是19次呼叫,可以是16次呼叫。 * 快取的其實就是快取item的view,在Recycleview中就是`viewholder`。 * `cachedView`就是`mCacheViews`快取區中的view,是不需要重新繫結資料的。 ## 如何實現RecyclerView的區域性更新,用過payload嗎,notifyItemChange方法中的引數? 關於RecycleView的資料更新,主要有以下幾個方法: * `notifyDataSetChanged()`,重新整理全部可見的item。 *`notifyItemChanged(int)`,重新整理指定item。 * `notifyItemRangeChanged(int,int)`,從指定位置開始重新整理指定個item。 * `notifyItemInserted(int)、notifyItemMoved(int)、notifyItemRemoved(int)`。插入、移動一個並自動重新整理。 * `notifyItemChanged(int, Object)`,區域性重新整理。 可以看到,關於view的區域性重新整理就是notifyItemChanged(int, Object)方法,下面具體說說: `notifyItemChange`有兩個構造方法: * notifyItemChanged(int position, @Nullable Object payload) * notifyItemChanged(int position) 其中`payload`引數可以認為是你要重新整理的一個標示,比如我有時候只想重新整理`itemView`中的`textview`,有時候只想重新整理`imageview`?又或者我只想某一個view的文字顏色進行高亮設定?那麼我就可以通過`payload`引數來標示這個特殊的需求了。 具體怎麼做呢?比如我呼叫了`notifyItemChanged(14,"changeColor")`,那麼在`onBindViewHolder`回撥方法中做下判斷即可: ```java @Override public void onBindViewHolder(ViewHolderholder, int positi