忽然,發現,網上的公開資料都是教你怎麼繼承一個baseadapter,然後重寫那幾個方法,再調用相關view的 setAdpater()方法, 接着,你的item 就顯示在手機螢幕上了。很少有人關注android adpater模式機制的實作原理,比較深入的也不過是說說adapter getview()中的回收情況。今天把相關的源碼看了一遍,把自己的了解記錄下來。
<a href="https://developer.android.com/reference/android/widget/AdapterView.html">AdpaterView</a>
api手冊的說明:An AdapterView is a view whose children are determined by an Adapter.
實際上android裡面ListView, GridView, Spinner , Gallery等view都是基于設計模式上的設配器模式實作的,隻要熟悉設配器模式的相關知識,就知道如何從源碼裡面找到相關的實作線索。
要了解listview等的實作,其父類是不得不看。源碼有1200多行。閱讀完AdapterView,能搞明白以下問題
響應資料的更改。
(793 - 842)
知道點選view的時候,獲得對應的位置.
(593 - 615)
在我剛開始用adapterview 的時候,最讓我費勁的就是,為什麼我調用adpater 的 notifyDataSetChanged() 就能更新view 的狀态了呢,然後跟調用notifyDataSetInvalidated() 兩者之間又有什麼差別呢?以前,找了一下資料,沒找到很詳細的說明,現在從源碼裡面找答案的話,就很清晰了。
我相信你,應該能明白觀察者模式是個什麼樣的實作了。。。
AdapterView 之是以能對Adapter 的資料更新進行響應,就是因為其在Adapter上注冊了一個資料觀察者(AdapterDataSetObserver(793 - 842 ))的内部類,是以,我們隻要對adpater 狀态的改變發送一個通知,就能讓AdapterView調用相應的方法了。
現在我們就能解決我們一開始的疑問notifyDataSetChanged() 與notifyDataSetInvalidated() 具體回到AdapterView 産生什麼影響?
我們對比一下<code>onChange()</code> 與 <code>onInvalidated()</code> 方法,就能對比得出,前者會對目前位置的狀态進行同步,而後者會重置所有位置的狀态。從代碼的注釋裡面還可以擷取得到更多的資訊。
這樣,我們以後調用notifyDataSetChanged()和notifyDataSetInvalidated() 就更加明白會發生什麼情況了。
源碼相關:(2130-2197) (2196 - 2279)
假設你已經搞懂委托模式的概念,首先我們來看源碼(2130 - 2197)。
從<code>obtainView()</code> 方法名中我們可以知道,這是一個用于生成itemView的方法。把這塊代碼看完,以後,會不會有個疑問呢(先不用管回收那塊)? position 到哪裡了?我們可以看到這個方法實際上并沒有對我們的itemview 設定了任何的監聽器,那為什麼最後能對我們的itemview的動作進行反應呢?
接下來我們看:源碼(2196 - 2279)
從代碼裡面我們可以看出這是一個委托類,對item 的動作進行初始化,以及響應對應的操作,從源碼裡面我們可以獲知得到,一個item view 為什麼能對click,longclick,select 動作進行響應,然後,通過調用<code>performItemClick()</code> 最終把事件調用到AdapterView(292-303)的<code>performItemClick()</code> 裡面的監聽器方法.
如果,你對委托模式不熟的話,要明白這裡的話,需要花點時間。
長期以來,都有這麼一個說法,listview 會自動把不可見的view進行回收,但是長期以來,我都沒看到有人對其回收機制進行分析說明
<code>obtainView()(2130-2197)</code>
你會看到一個
<code>mRecycler</code> 的變量。
接下來,通過搜尋我們可以得知這個變量是在(308)進行初始化,這是一個内部類的
RecycleBin的執行個體(6139 - 6507)
看到這類,我們大緻可以知道,這個類是這個absListView 回收機制的實作者。
請 跳轉到(6139)
現在,我們來看一下這個類的注釋,大體的意思這個類是用來幫助複用view的,用2個不同級别的方式進行存儲(The RecycleBin has two levels of storage)(個人感覺描述得挺變扭的,還是看原文好了。。)
ActiveViews : 一開始顯示在螢幕的view
ScrapViews: 潛在的一些可以讓adpater 使用的old views。
然後,注釋裡面已經說了,ActiveViews 怎麼變成 ScrapViews。就注釋提供的資訊這裡我們有兩個疑問。
什麼時候産生 ActiveViews。
什麼時候産生 ScrapViews。
這要把這兩點搞清楚了,整個回收體系也就清楚了。
從RecycleBin類的注釋裡面我們獲知,回收機制的第一步就是螢幕的view 放在ActiveViews,然後通過對ActiveViews進行降級變成ScrapViews,然後通過scrapViews 進行view 的複用
當我們看到(1550)行的時候,就會發現了這個回收類的指派。接下來我們看下listview是如何利用回收機制:
當資料發生改變的時候,把目前的view放到scrapviews裡面,否則标記為activeViews(1557 - 1562)
<code>recycleBin.removeSkippedScrap();</code> 移除所有old views
<code>recycleBin.scrapActiveViews();</code> 重新整理緩存,将目前的ActiveVies 移動到 ScrapViews。
這裡幹了些事情呢?我們回到(1557 - 1562) 我們可以看到一個變量dataChanged,從單詞的意思我們就可以,這裡的優化規則就是基于資料是否有變化,我們通過搜尋成員變量<code>mDataChanged</code>在 (1693) 的時候變成了false 接着我們在<code>makeAndAddView</code>(1751 - 1775)發現了這個變量的使用。
閱讀(1756 - 1766) 我們可以看到回收機制的第一次使用,如果資料沒有發生改變,通過判斷ActiveViews(這些些view來自(1557 - 1562)) 清單裡面有沒有目前 活動view,有的話直接複用已經存在的view。這樣的好處就是直接複用目前已經存在的view,不需要通過adapter.getview()裡面擷取子view。
在 (2134) 中我們看到一個很神秘的方法<code>scrapView = mRecycler.getTransientStateView(position);</code> 從單詞的意思裡面我們可以得知這是擷取一個瞬間狀态的view,這裡就有個疑問什麼是瞬間狀态的view?通過對源碼的層層分析終于在View 類的 hasTransientState()方法裡面找到描述。從描述中我們得知這個方法是用來标記這個view的瞬時狀态,用來告訴app無需關心其儲存和恢複。從注釋中,官方告訴我這種具有瞬時狀态的view,用于在view動畫播放等情況中。
那麼,我們就可以明白這句話優化的是absListView 的清單動畫.
接着閱讀到一下代碼的時候,我就困惑了
<code>scrapView = mRecycler.getScrapView(position);</code>
從這行代碼裡面我們可知,複用的review是跟位置有關的,我們回去在看看(ListView 1557-1563)
我們可以發現,實際上這裡放進回收類裡面的隻有目前的顯示的view,并沒有産生目前螢幕沒有的view,但是,實際使用中,當我們進行滾屏的時候,顯示下個view的時候,就已經能發現getView 第二個參數已經不為null了,那實際實作在哪裡了,我們通過搜尋用到RecycleBin 的方法,找到
<code>layoutChildren()</code> <code>scrollListItemsBy()</code> <code>onMeasure()</code> <code>measureHeightOfChildren()</code>
通過檢視
我們就能夠明白,當我們進行滾屏的時候,在listview 移除item view 的時候,把移除的item view放進了
<code>recycleBin.addScrapView(last, mFirstPosition+lastIndex);</code>
于是生成下一個view的時候就能夠複用之前的view了,搞清楚這個機制以後我們回到
接下來代碼, 解答了我們一個經典的adapter 優化方法的由來
實際上所謂的優化,就是通過利用已有産生的View進行複用,減少在Adapter.getView()進行類的執行個體化操作優化性能。
從某年google io的文檔中我們得知這個回收機制的效率能夠提供listview 300%的效率。
接着我們還明白了
getView(int position, View convertView, ViewGroup parent) 這個三個參數的由來了。
通過,對回收機制的分析,我們可以檢視
listview scrollListItemsBy()
的時候應該注意到,實際上不可見的 item 是會被自動移除,那樣為什麼當滾動過多的item的時候會發生oom的情況了?
在我們閱讀完整個回收機制的時候,我們會發現回收機制實際上是通過在記憶體裡面緩存view對象,讓listview能夠快速的擷取view使listview的顯示流暢。而導緻OOM的問題也出在這裡,由于整個回收機制把所有的imageview中的bitmap對象也儲存下來,在進行不斷的滑屏操作中,RecycleBin 類越來越大,最終導緻OOM 的發生。
當然,根據整個思路,要避免OOM實際上也很簡單,我們隻需要在虛拟機中開辟一個記憶體塊,專門用于儲存bitmap對象的 map對象(一般而言用LRU算法實作),所有的imageview的應用都通過整個map 對象進行引用,當這個map對象大于一定程度的時候釋放部分bitmap,這就可以保證RecycleBin在儲存這些imageview的時候,而這些imageview裡面的bitmap對象時通過一個固定的記憶體塊裡面擷取,隻要我們開辟的用于引用的bitmap 的記憶體塊的大小合理,那樣就永遠也不會發生oom了。
至于其他繼承自AbsListView 的View 其回收機制都一樣。。
花了,幾個小時,把AdapterView 相關源碼看完,大緻計算了行數有3w 來行代碼了,當然,不會是一行不漏的看過去。 這裡分享一個看源碼的方法。首先,有接口和,抽象類的地方,一定要把所有方法看全,這一塊基本上是屬于要一行不漏的看完。實際上這些接口,和抽象類是我們看源碼重要的索引,那些4,5k行的代碼,實際上,裡面的關鍵,都是這些接口,和相應的抽象類的擴充。
本文轉自 liam2199 部落格,原文連結: http://blog.51cto.com/youxilua/1197715 如需轉載請自行聯系原作者