Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> 分析Android AdapterView源碼以及其相關回收機制

分析Android AdapterView源碼以及其相關回收機制

編輯:Android開發實例

前言

忽然,發現,網上的公開資料都是教你怎麼繼承一個baseadapter,然後重寫那幾個方法,再調用相關view的 setAdpater()方法, 接著,你的item 就顯示在手機屏幕上了。很少有人關注android adpater模式機制的實現原理,比較深入的也不過是說說adapter getview()中的回收情況。今天把相關的源碼看了一遍,把自己的理解記錄下來。

AdpaterView 概覽

AdpaterView

api手冊的說明:An AdapterView is a view whose children are determined by an Adapter.

實際上android裡面ListView, GridView, Spinner , Gallery等view都是基於設計模式上的設配器模式實現的,只要熟悉設配器模式的相關知識,就知道如何從源碼裡面找到相關的實現線索。

認識AdapterView

源碼鏈接https://github.com/android/platformframeworksbase/blob/master/core/java/android/widget/AdapterView.java

要理解listview等的實現,其父類是不得不看。源碼有1200多行。閱讀完AdapterView,能搞明白以下問題

  1. 響應數據的更改。

    (793 - 842)

  2. 知道點擊view的時候,獲得對應的位置.

    (593 - 615)

響應數據的更改

這裡假設你已經打開了AdpaterView 的 793 到 842 行。。

在我剛開始用adapterview 的時候,最讓我費勁的就是,為什麼我調用adpater 的 notifyDataSetChanged() 就能更新view 的狀態了呢,然後跟調用notifyDataSetInvalidated() 兩者之間又有什麼區別呢?以前,找了一下資料,沒找到很詳細的說明,現在從源碼裡面找答案的話,就很清晰了。

首先,我們要明白一種設計模式:觀察者設計模式。

我相信你,應該能明白觀察者模式是個什麼樣的實現了。。。

AdapterView 之所以能對Adapter 的數據更新進行響應,就是因為其在Adapter上注冊了一個數據觀察者(AdapterDataSetObserver(793 - 842 ))的內部類,所以,我們只要對adpater 狀態的改變發送一個通知,就能讓AdapterView調用相應的方法了。

DataSetObservable 的源碼,記得要把其父類也看了。 https://github.com/android/platformframeworksbase/blob/master/core/java/android/database/DataSetObservable.java

現在我們就能解決我們一開始的疑問notifyDataSetChanged() 與notifyDataSetInvalidated() 具體回到AdapterView 產生什麼影響?

我們對比一下onChange()onInvalidated() 方法,就能對比得出,前者會對當前位置的狀態進行同步,而後者會重置所有位置的狀態。從代碼的注釋裡面還可以獲取得到更多的信息。

這樣,我們以後調用notifyDataSetChanged()和notifyDataSetInvalidated() 就更加明白會發生什麼情況了。

點擊item 怎麼能夠獲取到當前的位置

這裡假設你已經打開了AdpaterView 的 593 - 615 行。。

對於getPositionForView() 這個方法,你肯定沒用過,要搞明白為什麼我們能夠獲取到adapterView 裡面item view對應的位置,我們需要看 其直接子類:AbsListView.class

源碼相關:(2130-2197) (2196 - 2279)

這裡又用到一種設計模式:委托模式

假設你已經搞懂委托模式的概念,首先我們來看源碼(2130 - 2197)。

obtainView() 方法名中我們可以知道,這是一個用於生成itemView的方法。把這塊代碼看完,以後,會不會有個疑問呢(先不用管回收那塊)? position 到哪裡了?我們可以看到這個方法實際上並沒有對我們的itemview 設置了任何的監聽器,那為什麼最後能對我們的itemview的動作進行反應呢?

接下來我們看:源碼(2196 - 2279)

從代碼裡面我們可以看出這是一個委托類,對item 的動作進行初始化,以及響應對應的操作,從源碼裡面我們可以獲知得到,一個item view 為什麼能對click,longclick,select 動作進行響應,然後,通過調用performItemClick() 最終把事件調用到AdapterView(292-303)的performItemClick() 裡面的監聽器方法.

如果,你對委托模式不熟的話,要明白這裡的話,需要花點時間。

認識 AbsListView 回收機制

源碼: AbsListView.class

長期以來,都有這麼一個說法,listview 會自動把不可見的view進行回收,但是長期以來,我都沒看到有人對其回收機制進行分析說明

回收執行者:RecycleBin

我們回到之前看過的AbsListView.class

obtainView()(2130-2197)

你會看到一個

mRecycler 的變量。

接下來,通過搜索我們可以得知這個變量是在(308)進行初始化,這是一個內部類的

RecycleBin的實例(6139 - 6507)

看到這類,我們大致可以知道,這個類是這個absListView 回收機制的實現者。

請 跳轉到(6139)

現在,我們來看一下這個類的注釋,大體的意思這個類是用來幫助復用view的,用2個不同級別的方式進行存儲(The RecycleBin has two levels of storage)(個人感覺描述得挺變扭的,還是看原文好了。。)

  1. ActiveViews : 一開始顯示在屏幕的view
  2. ScrapViews: 潛在的一些可以讓adpater 使用的old views。

然後,注釋裡面已經說了,ActiveViews 怎麼變成 ScrapViews。就注釋提供的信息這裡我們有兩個疑問。

  1. 什麼時候產生 ActiveViews。
  2. 什麼時候產生 ScrapViews。

這要把這兩點搞清楚了,整個回收體系也就清楚了。

AbsListView的回收機制具體實現

從RecycleBin類的注釋裡面我們獲知,回收機制的第一步就是屏幕的view 放在ActiveViews,然後通過對ActiveViews進行降級變成ScrapViews,然後通過scrapViews 進行view 的復用

通過,一番的檢索,我們在Listview.class(1562行裡面找到fillActiveViews()的調用)。

我們觀察一下Listview.class(1460 - 1713) 看一下layoutChildren()這個方法是干嘛用的。

當我們看到(1550)行的時候,就會發現了這個回收類的賦值。接下來我們看下 listview是如何利用回收機制:

  1. 當數據發生改變的時候,把當前的view放到scrapviews裡面,否則標記為activeViews(1557 - 1562)
  2. recycleBin.removeSkippedScrap(); 移除所有old views
  3. recycleBin.scrapActiveViews(); 刷新緩存,將當前的ActiveVies 移動到 ScrapViews。

這裡干了些事情呢?我們回到(1557 - 1562) 我們可以看到一個變量dataChanged,從單詞的意思我們就可以,這裡的優化規則就是基於數據是否有變化,我們通過搜索成員變量mDataChanged在 (1693) 的時候變成了false 接著我們在makeAndAddView(1751 - 1775)發現了這個變量的使用。

閱讀(1756 - 1766) 我們可以看到回收機制的第一次使用,如果數據沒有發生改變,通過判斷ActiveViews(這些些view來自(1557 - 1562)) 列表裡面有沒有當前 活動view,有的話直接復用已經存在的view。這樣的好處就是直接復用當前已經存在的view,不需要通過adapter.getview()裡面獲取子view。

好了,接下來我們來看下makeAndAddView(1751 - 1775) 是如何通過adapter.getview()中 獲取到view。我們回到AbsListView.class(2130 - 2194)

在 (2134) 中我們看到一個很神秘的方法scrapView = mRecycler.getTransientStateView(position); 從單詞的意思裡面我們可以得知這是獲取一個瞬間狀態的view,這裡就有個疑問什麼是瞬間狀態的view?通過對源碼的層層分析終於在View 類的 hasTransientState()方法裡面找到描述。從描述中我們得知這個方法是用來標記這個view的瞬時狀態,用來告訴app無需關心其保存和恢復。從注釋中,官方告訴我這種具有瞬時狀態的view,用於在view動畫播放等情況中。

那麼,我們就可以明白這句話優化的是absListView 的列表動畫.

接著閱讀到一下代碼的時候,我就困惑了

scrapView = mRecycler.getScrapView(position);

從這行代碼裡面我們可知,復用的review是跟位置有關的,我們回去在看看(ListView 1557-1563)

       if (dataChanged) {
            for (int i = 0; i < childCount; i++) {
                recycleBin.addScrapView(getChildAt(i), firstPosition+i);
            }
        } else {
            recycleBin.fillActiveViews(childCount, firstPosition);
        }

我們可以發現,實際上這裡放進回收類裡面的只有當前的顯示的view,並沒有產生當前屏幕沒有的view,但是,實際使用中,當我們進行滾屏的時候,顯示下個view的時候,就已經能發現getView 第二個參數已經不為null了,那實際實現在哪裡了,我們通過搜索用到RecycleBin 的方法,找到

layoutChildren()

scrollListItemsBy()

onMeasure()

measureHeightOfChildren()

通過查看

scrollListItemsBy()

我們就能夠明白,當我們進行滾屏的時候,在listview 移除item view 的時候,把移除的item view放進了

recycleBin.addScrapView(last, mFirstPosition+lastIndex);

於是生成下一個view的時候就能夠復用之前的view了,搞清楚這個機制以後我們回到

AbsListView.class(2139 - 2168)

接下來代碼, 解答了我們一個經典的adapter 優化方法的由來

  View child;
    if (scrapView != null) {
        child = mAdapter.getView(position, scrapView, this);
        if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
        }

        if (child != scrapView) {
            mRecycler.addScrapView(scrapView, position);
            if (mCacheColorHint != 0) {
                child.setDrawingCacheBackgroundColor(mCacheColorHint);
            }
        } else {
            isScrap[0] = true;
            child.dispatchFinishTemporaryDetach();
        }
    } else {
        child = mAdapter.getView(position, null, this);

        if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
        }

        if (mCacheColorHint != 0) {
            child.setDrawingCacheBackgroundColor(mCacheColorHint);
        }
    }

實際上所謂的優化,就是通過利用已有產生的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行的代碼,實際上,裡面的關鍵,都是這些接口,和相應的抽象類的擴展。

 

轉自:http://www.cnblogs.com/youxilua/archive/2013/05/20/3087935.html

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved