最全面的RecyclerView原始碼解析
相信很多人用RecyclerView已經很久了,但還是不得不感嘆 RecyclerView的強大,效能、擴充套件性等方面都很強大。網上看了很多原始碼方面對RecyclerView,覺得還不夠全面,而且自己不走一遍原始碼總感覺會很容易忘記。
開啟RecyclerView類,發現有11090行程式碼,千萬不要震驚,慢慢看。
整體看了下RecyclerView的架構,你會驚奇於這個優雅的設計,高度解耦,靈活性很強,給開發者一種插拔式的體驗,使用者只要通過設定不同的LayoutManager, ItemDecoration, ItemAnimator就可以實現各種各樣的效果了。
Recycler
開啟RecyclerView的結構,複雜,我們就從上往下說好了。首先來分析Recycler這個內部類,Recycler是整個RecyclerView的精髓所在,那麼Recycler到底是什麼呢:
Recycler的職責是管理那些已經廢棄了的或者從RecyclerView中分離的item view用於複用。
廢棄的View是指那些仍然依附於RecyclerView但是已經被標記為可以被移除或者複用的View。
Recycler典型的用法就是當LayoutManager去獲取Adapter中的某一項View的時候,如果這個View失效了,則需要重新繫結View,當複用的View是有效的話,View就會被直接被複用。有效的View如果不主動呼叫requestLayout,那麼該View不需要重新測量就可以被複用。
說了一大堆,還是邊看程式碼邊解釋更加清晰一點,首先來看Recycler幾個成員變數:
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
private ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mViewCacheMax = DEFAULT_CACHE_SIZE;
private RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
先來看RecycledViewPool和ViewCacheExtension這兩個類:
RecycledViewPool讓開發者可以在多個RecyclerView之間共享View。
如果你想要垮RecyclerView複用View,建立一個RecycledViewPool例項,然後呼叫setRecycledViewPool(RecycledViewPool)方法就可以了。
RecyclerView會自動建立一個RecycledViewPool的例項。
有了RecycledViewPool的存在,就能很大程度上減少View的建立,提高效能。
先看下面這兩個方法:
private SparseArray<ArrayList<ViewHolder>> mScrap =
new SparseArray<ArrayList<ViewHolder>>();
private SparseIntArray mMaxScrap = new SparseIntArray();
//此處省略部分程式碼
//……
public void setMaxRecycledViews(int viewType, int max) {
mMaxScrap.put(viewType, max);
final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
if (scrapHeap != null) {
while (scrapHeap.size() > max) {
scrapHeap.remove(scrapHeap.size() - 1);
}
}
}
public ViewHolder getRecycledView(int viewType) {
final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
if (scrapHeap != null && !scrapHeap.isEmpty()) {
final int index = scrapHeap.size() - 1;
final ViewHolder scrap = scrapHeap.get(index);
scrapHeap.remove(index);
return scrap;
}
return null;
}
可以看到,mScrap是一個《viewType, List>的對映,mMaxScrap是一個《viewType, maxNum>的對映,我們可以呼叫setMaxRecycledViews方法來設定每種viewType的view容量。從原始碼可以看出,如果viewType型別的list的size大於制定的最大數字的話,會先從列表的末尾開始丟棄超出的部分。呼叫getRecycledView(int viewType)方法呢,可以將scrapHeap中的最後一項移除並返回viewType對應的List的末尾項。這裡需要注意的是,因為是跨RecyclerView進行操作,所以要特別注意對於同一個RecycledViewPool,對ViewType的定義要統一,因為這裡是根據viewType來取ViewHolder的。
再來看ViewCacheExtension這個類:
public abstract static class ViewCacheExtension {
abstract public View getViewForPositionAndType(Recycler recycler, int position, int type);
}
ViewCacheExtension是一個幫助類,給開發者提供了一個可以由他們自己控制View快取的這麼一個類。當呼叫Recycler的getViewForPosition(int position)方法等時候,Recycler會先去檢查attached scrap和一級快取來這個View,如果都找不到匹配的View的話,Recycler會呼叫ViewCacheExtension的getViewForPositionAndType方法檢查,如果還是沒有,再去檢查RecycledViewPool。
需要注意的是:Recycler不會在這個類中做任何快取View的操作,是否需要快取View由開發者自己控制。
然後回到Recycler,Recycler最終目的還是要去獲取到來看這個方法:
View getViewForPosition(int position, boolean dryRun) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("Invalid item position " + position
+ "(" + position + "). Item count:" + mState.getItemCount());
}
boolean fromScrap = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
// 1) Find from scrap by position
if (holder == null) {
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle this scrap
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrap = true;
}
}
}
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());
}
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder");
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view.");
}
}
}
if (holder == null) { // fallback to recycler
// try recycler.
// Head to the shared pool.
if (DEBUG) {
Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
+ "pool");
}
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
}
// This is very ugly but the only place we can grab this information
// before the View is rebound and returned to the LayoutManager for post layout ops.
// We don't need this in pre-layout since the VH is not updated by the LM.
if (fromScrap && !mState.isPreLayout() && holder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (mState.mRunSimpleAnimations) {
int changeFlags = ItemAnimator
.buildAdapterChangeFlagsForAnimations(holder);
changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
holder, changeFlags, holder.getUnmodifiedPayloads());
recordAnimationInfoIfBouncedHiddenView(holder, info);
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder);
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
holder.mOwnerRecyclerView = RecyclerView.this;
mAdapter.bindViewHolder(holder, offsetPosition);
attachAccessibilityDelegate(holder.itemView);
bound = true;
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
}
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrap && bound;
return holder.itemView;
}
從程式碼中可以看出,要獲取到某個position下的view,有這麼一個過程:
首先Recycler會先去檢查mChangedScrap,如果匹配成功則返回相應的viewHolder。mChangedScrap就是所謂的detachedView,與RecyclerView分離的viewHolder列表。
然後是mAttachedScrap,mViewCacheExtension,mRecyclerPool,如果都沒有,則去建立,呼叫Adapter.createViewHolder()。
其實相當於RecyclerView也做到了二級快取的概念,mCachedViews是一層,預設大小是DEFAULT_CACHE_SIZE = 2, 還有一層就是RecycledViewPool。我們可以呼叫這個方法來控制第一層的快取數量:
public void setViewCacheSize(int viewCount) {
mViewCacheMax = viewCount;
// first, try the views that can be recycled
for (int i = mCachedViews.size() - 1; i >= 0 && mCachedViews.size() > viewCount; i--) {
recycleCachedViewAt(i);
}
}
這就是Recycler去根據position獲取相應的itemView的過程,既然有獲取,那麼就肯定有放入,來看這個方法:
void recycleViewHolderInternal(ViewHolder holder) {
//省略一部分程式碼。。。
if (forceRecycle || holder.isRecyclable()) {
if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE)) {
// Retire oldest cached view
final int cachedViewSize = mCachedViews.size();
if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
addViewHolderToRecycledViewPool(holder);
recycled = true;
}
} else if (DEBUG) {
Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ "re-visit here. We are still removing it from animation lists");
}
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
這個方法其實是先去判斷了下mCachedViews是否已經滿了,如果滿了,則移除一個到recycledViewPool中去,然後將新的itemView新增到mCachedViews,如果沒滿那最好了,直接新增。
LayoutManager
LayoutManager的主要職責就是去測量和擺放itemView和當itemView對使用者來說不再能看見的時候去複用它。LayoutManager牽扯到的東西其實也蠻多的,我特意去翻看了下LinearLayoutManager的原始碼,挺複雜的,就不再這篇講了,到時候另起一篇來講下這個東西。
可以看到,RecyclerView的onMeasure和onLayout方法都是交給了LayoutManager來處理。
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
//省略一段程式碼
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
mLayout.setMeasureSpecs(widthSpec, heightSpec);
//省略一段程式碼
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
//省略一段程式碼。。。。。。
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
eatRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
resumeRequestLayout(false);
mState.mInPreLayout = false;
}
}
滑動
RecyclerView的onTouchEvent方法比較長,這裡就不貼了。其實滑動的套路都差不多的,RecyclerView也一樣。那就講一下我看了原始碼後的理解吧,這裡以垂直方向的滑動為力。
在ACTION_MOVE中,首先計算出手指滑動的距離,先跟TouchSlop進行比較,如果大雨這個值,那麼就是確定為師滑動了,而不是點選,然後呼叫scrollByInternal()方法,scrollByInternal其實最後還是用了scrollBy方法,拖拽性質的滑動就解決了。當然我們也發現當我們拖拽RecyclerView後,當我們的手指離開螢幕以後,RecyclerView還會滑動一段時間,這個其實就是fling事件。RecyclerView在ACTION_UP事件的時候,VelocityTracker根據拖拽時候滑動的距離和時間計算出一個速度,然後呼叫fling方法就行了,其實fling裡面還是用Scrooler來實現的。這裡貼下fling方法的程式碼:
public void fling(int velocityX, int velocityY) {
setScrollState(SCROLL_STATE_SETTLING);
mLastFlingX = mLastFlingY = 0;
mScroller.fling(0, 0, velocityX, velocityY,
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
postOnAnimation();
}
原始碼這個東西真的是越看越多,尤其RecyclerView感覺更是。還是分篇寫吧打算,下一篇繼續分析下LayoutManager,ViewHolder,ItemDecoration這些。第一次分析原始碼,如果有錯誤的地方請及時指出
相關推薦
最全面的RecyclerView原始碼解析
相信很多人用RecyclerView已經很久了,但還是不得不感嘆 RecyclerView的強大,效能、擴充套件性等方面都很強大。網上看了很多原始碼方面對RecyclerView,覺得還不夠全面,而且自己不走一遍原始碼總感覺會很容易忘記。 開啟Recycler
【進階】RecyclerView原始碼解析(二)——快取機制
引言 接著上一篇部落格分析完RecyclerView的繪製流程,其實對RecyclerView已經有了一個大體的瞭解,尤其是RecyclerView和LayoutManager和ItemDecoration的關係。 本篇文章將對RecyclerVie
【進階】RecyclerView原始碼解析(三)——深度解析快取機制
上一篇部落格從原始碼角度分析了RecyclerView讀取快取的步驟,讓我們對於RecyclerView的快取有了一個初步的理解,但對於RecyclerView的快取的原理還是不能理解。本篇部落格將從實際專案角度來理解RecyclerView的快取原理。
RecyclerView原始碼解析
一.基本介紹 在平時的開發中我們或多或少地接觸過ListView,所以我們對ListView並不陌生。不過ListView存在某些缺點,所以谷歌又在ListView的基礎上推出了RecyclerView。記得RecyclerView剛推出時,網上許多文章都介紹
【進階】RecyclerView原始碼解析(一)——繪製流程
引言 自從Google出了RecyclerView後,基本上列表的場景已經完全替代了原來的ListView和GridView,現在不僅僅是列表,多樣式(俗稱蓋樓),複雜頁面等,只要我們願意,RecyclerView幾乎可以代替實現80%的佈局,Git
有史以來最小的編譯器原始碼解析
後續內容更新,請前往:個人部落格,歡迎一起交流。 前言 稍微接觸一點前端,我們都知道現在前端“ES6即正義”,然而瀏覽器的支援還處於進行階段,所以我們常常會用一個神奇的工具將 ES6 語法轉換為目前支援比較廣泛的 ES5 語法,這裡我們所說的神奇的工具就是編譯器。
[work*] 最全面解析 Ubuntu 16.04 安裝nvidia驅動
想在Lab的臺機上跑機器學習程式碼, 可以nvidia-smi, but一直報 AssertionError: Found no NVIDIA driver on your system. Please check that you have an NVIDIA GPU and installe
某課最全Java併發程式設計高階技術-高效能併發框架原始碼解析與實戰
第1章 課程介紹(Java併發程式設計進階課程) 什麼是Disruptor?它一個高效能的非同步處理框架,號稱“單執行緒每秒可處理600W個訂單”的神器,本課程目標:徹底精通一個如此優秀的開源框架,面試秒殺面試官。本章會帶領小夥伴們先了解課程大綱與重點,然後模擬千萬,億級
Tomcat最全面的解析和作用介紹
最近需要學Tomcat的相關知識,在網上搜尋了很多csdn的知識,但是介紹的都很不全面 在我看來: Tomcat的作用以及概念就是web容器的概念,web容器是一種服務程式,在伺服器一個埠就有一個提供相應服務的程式,而這個程式就是處理從客戶端發出的請求,如JAV
143行js頂部進度條最小外掛-nanobar.js原始碼解析
網頁頂部進度條外掛的有四五種,基本原理就是動態地建立一個元素,然後通過設定它的width來實現動畫效果,width增長到達指定位置時,將其去掉。 來看看nanobar.js作者jacoborus是怎麼做到的吧! /* http://nanobar.micronu
最新版azkaban-3.40.0原始碼解析
web上傳zip以及解析入庫 web服務上傳zip包 入口: azkaban.webapp.servlet.LoginAbstractAzkabanServlet.doPost(HttpServletRequest, HttpServletRes
Java併發程式設計高階技術-高效能併發框架原始碼解析與實戰(已完結)2018(最全)
1.背景 匿名內部類有以下問題: 語法過於冗餘 匿名類中的this和變數名容易使人產生誤解 型別載入和例項建立語義不夠靈活 無法捕獲非final的區域性變數 無法對控制流進行抽象 lambda表示式提供了輕量級的語法。 2.語法 lambda表示式的語法由引數列表、箭頭符號->和函式體
史上最全Universal-Image-Loader原始碼解析————核心程式碼篇
背景 接著上一篇的內容,我們接著看ImageLoader的核心程式碼篇,上一篇主要是看ImageLoader的記憶體優化,主要包括磁碟快取和記憶體快取,還有就是記憶體的快取策略,這一篇,我們重點來看一下ImageLoader是如何使用這些記憶體和進行圖片的
Android四大元件:Service史上最全面解析
前言 Service作為Android四大元件之一,應用非常廣泛 本文將介紹對Service進行全面介紹(基礎認識、生命週期、使用和應用場景) 目錄 1. 基礎知識 定義:服務,屬於Android中的計算型元件 作用
史上最全Universal-Image-Loader原始碼解析————快取篇
背景 在我寫下這篇部落格的時候,我還是一名二本學校計算機專業大四的應屆畢業生,自學Android開發快兩年了,這兩年的時間裡面,其實真的是感慨萬千,兩年的時間裡面,Android的很多事情也都見識過了,其實Android對於新手入門在就業方面是相當不友好的事情。都說第一個吃螃蟹的人最
JSON介紹及Android最全面解析方法(Gson、AS自帶org.son、Jackson解析)
前言 今天,我們來介紹一下現今主流的資料交換格式-JSON! 目錄 定義 JavaScript Object Notation,JavaScript的物件表示法,是一種輕量級的文字資料交換格式。 作用 用於資料的標記、儲存
【智慧製造】「人.機.料.法.環」最全面解析
人:指製造產品的人員;機:指製造產品所用的裝置;料:指製造產品所使用的原材料;法:指製造產品所使
外掛化系列開發之九--Android 全面外掛化 RePlugin 流程與原始碼解析
RePlugin,360開源的全面外掛化框架,按照官網說的,其目的是“儘可能多的讓模組變成外掛”,並在很穩定的前提下,儘可能像開發普通App那樣靈活。那麼下面就讓我們一起深入♂瞭解它吧。 (ps :閱讀本文請多參考原始碼圖片 ( ̄^ ̄)ゞ ) 一、介紹 RePlugi
Android開發:JSON簡介及最全面解析方法(Gson、AS自帶org.json、Jackson解析)
目錄 JSON簡介&解析方法介紹.png 定義 JavaScript Object Notation,JavaScript的物件表示法,是一種輕量級的文字資料交換格式。 作用 用於資料的標記、儲存和傳輸。 特點 輕量級的文字資料交換格式 獨立於語言和平臺 具有自我描述性 讀寫速度快,解析簡單 語法
RecyclerView 的常用方法;工作原理與ListView比較;原始碼解析
寫在前面 本文原創,轉載請以連結形式註明地址:http://kymjs.com/code/2016/07/10/01起深入淺出這名字的時候我是慎重又慎重的,生怕被人罵標題黨,寫的什麼破玩意還敢說深入淺出。所以還是請大家不要抱著太高的期望,因為沒有期望就沒有失望,就像陳潤說的