Adapter.notifyDataSetChanged()源碼分析以及與ListView.setAdapter的區別


一直很好奇,notifyDataSetChanged究竟是重繪了整個ListView還是只重繪了被修改的那些Item,它與重新設置適配器即調用setAdapter的區別在哪里?所以特地追蹤了一下源碼,過程如下:

一、notifyDataSetChanged實現機制

自定義Activity中有如下調用語句:

checkoutAdapter.notifyDataSetChanged();

點擊notifyDataSetChanged()進行代碼跟蹤。首先,進入到BaseAdapter的notifyDataSetChanged方法:

public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }

我們發現其實就是DataSetObservable這個對象在發生作用,點擊notifyChanged進行追蹤。

public class DataSetObservable extends Observable<DataSetObserver> {
    /**
     * Invokes onChanged on each observer. Called when the data set being observed has
     * changed, and which when read contains the new state of the data.
     */
    public void notifyChanged() {
        synchronized(mObservers) {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

繼續跟蹤onChanged(),我們發現DataSetObserver 是個抽象類,其派生類實例對象是在哪里指定的呢?根據經驗,我們需要回溯至adapter的構造過程。

public abstract class DataSetObserver {
    /**
     * This method is called when the entire data set has changed,
     * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}.
     */
    public void onChanged() {
        // Do nothing
    }

    /**
     * This method is called when the entire data becomes invalid,
     * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a
     * {@link Cursor}.
     */
    public void onInvalidated() {
        // Do nothing
    }
}

先看adapter的構造函數

        CheckOut_DishListViewAdapter checkoutAdapter;
//
綁定適配器 checkoutAdapter = new CheckOut_DishListViewAdapter( CheckOutActivity.this, list_dish); list_view_dish.setAdapter(checkoutAdapter);
public class CheckOut_DishListViewAdapter extends BaseAdapter {
    private DecimalFormat df = new DecimalFormat("######0.00");// 用於double保留兩位小數
    private LayoutInflater mInflater;
    private List<HashMap<String, Object>> list;

    public CheckOut_DishListViewAdapter(Context con,
            List<HashMap<String, Object>> list) {
        mInflater = LayoutInflater.from(con);
        this.list = list;
    }

顯然沒有DataSetObserver的有關信息。

再看ListView中的setAdapter方法,我們省略其他代碼,只看與DataSetObserver相關的部分,從mDataSetObserver = new AdapterDataSetObserver();可知,AdapterDataSetObserver是DataSetObserver的實例化類。

 1 @Override
 2     public void setAdapter(ListAdapter adapter) {
 3         ...
22         if (mAdapter != null) {
23             ...27 
28             mDataSetObserver = new AdapterDataSetObserver();
29             mAdapter.registerDataSetObserver(mDataSetObserver);
30 
31             ...46         } else {
47             ...51         }
52 
53         requestLayout();
54     }

查看AdapterDataSetObserver的onChanged方法:

 1 class AdapterDataSetObserver extends DataSetObserver
 2   {
 3     private Parcelable mInstanceState = null;
 4 
 5     AdapterDataSetObserver() {
 6     }
 7     public void onChanged() { 
 8       mDataChanged = true;
 9       mOldItemCount = mItemCount;
10       mItemCount = getAdapter().getCount();
11 
12       if ((getAdapter().hasStableIds()) && (mInstanceState != null) && (mOldItemCount == 0) && (mItemCount > 0))
13       {
14         onRestoreInstanceState(mInstanceState);
15         mInstanceState = null;
16       } else {
17         rememberSyncState();
18       }
19       checkFocus();
20       requestLayout();
21     }
22     //...省略不必要代碼
23 }

在第20行,我們看見了requestLayout(),它就是用來重繪界面的,點擊追蹤requestLayout時,無法繼續追蹤,這時通過查找系統源碼,我們發現AdapterDataSetObserver原來是抽象類AdapterView的內部類

public abstract class AdapterView<T extends Adapter> extends ViewGroup {
    ...
}
 1     class AdapterDataSetObserver extends DataSetObserver {
 2 
 3         private Parcelable mInstanceState = null;
 4 
 5         @Override
 6         public void onChanged() {
 7             mDataChanged = true;
 8             mOldItemCount = mItemCount;
 9             mItemCount = getAdapter().getCount();
10 
11             // Detect the case where a cursor that was previously invalidated has
12             // been repopulated with new data.
13             if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
14                     && mOldItemCount == 0 && mItemCount > 0) {
15                 AdapterView.this.onRestoreInstanceState(mInstanceState);
16                 mInstanceState = null;
17             } else {
18                 rememberSyncState();
19             }
20             checkFocus();
21             requestLayout();
22         }
23 
24         @Override
25         public void onInvalidated() {
26             mDataChanged = true;
27 
28             if (AdapterView.this.getAdapter().hasStableIds()) {
29                 // Remember the current state for the case where our hosting activity is being
30                 // stopped and later restarted
31                 mInstanceState = AdapterView.this.onSaveInstanceState();
32             }
33 
34             // Data is invalid so we should reset our state
35             mOldItemCount = mItemCount;
36             mItemCount = 0;
37             mSelectedPosition = INVALID_POSITION;
38             mSelectedRowId = INVALID_ROW_ID;
39             mNextSelectedPosition = INVALID_POSITION;
40             mNextSelectedRowId = INVALID_ROW_ID;
41             mNeedSync = false;
42 
43             checkFocus();
44             requestLayout();
45         }
46 
47         public void clearSavedState() {
48             mInstanceState = null;
49         }
50     }

在21行,我們又看見了requestLayout(),Ctrl+單擊該方法,進入到View類的同名方法

 1     /**
 2      * Call this when something has changed which has invalidated the
 3      * layout of this view. This will schedule a layout pass of the view
 4      * tree.
 5      */
 6     public void requestLayout() {
 7         if (ViewDebug.TRACE_HIERARCHY) {
 8             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
 9         }
10 
11         mPrivateFlags |= FORCE_LAYOUT;
12         mPrivateFlags |= INVALIDATED;
13 
14         if (mParent != null) {
15             if (mLayoutParams != null) {
16                 mLayoutParams.resolveWithDirection(getResolvedLayoutDirection());
17             }
18             if (!mParent.isLayoutRequested()) {
19                 mParent.requestLayout();
20             }
21         }
22     }

在第19行,我們發現該方法將requestLayout()任務上拋至其mParent,因此我們需要追蹤mParent,先來看看誰為它賦值:

 1     /*
 2      * Caller is responsible for calling requestLayout if necessary.
 3      * (This allows addViewInLayout to not request a new layout.)
 4      */
 5     void assignParent(ViewParent parent) {
 6         if (mParent == null) {
 7             mParent = parent;
 8         } else if (parent == null) {
 9             mParent = null;
10         } else {
11             throw new RuntimeException("view " + this + " being added, but"
12                     + " it already has a parent");
13         }
14     }

原來是assignParent,因此在構造子view的過程中,子view一定有assignParent的操作。根據View Tree的層級關系,我們可以猜測,這樣一層層的上拋請求,最后應該上拋至Activity的根View,這個根View是誰?根據我們對Activity加載布局流程的理解,這個根View其實就是DecorView,那么我們先來看看DecorView中是否有requestLayout方法的具體實現。

我們知道DecorView是PhoneWindow的內部類,進入DecorView類,

1 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

發現DecorView繼承自FrameLayout ,也即間接繼承自View,但DecorView中並未重寫requestLayout方法,說明DecorView並不是requestLayout的最終執行者,DecorView存在mParent,要想弄清楚DecorView的mParent是誰,我們有必要回顧一下DecorView是如何裝載到Activity的。

我們按照流程圖一級一級的找,在WindowManagerImpl中找到addView方法,發現新建了一個ViewRootImpl對象,並在最后調用ViewRootImpl的setView方法,接下來我們繼續跟進setView方法。

 1 private void addView(View view, ViewGroup.LayoutParams params,
 2             CompatibilityInfoHolder cih, boolean nest) {
 3             ...
 4         
 5             ViewRootImpl root;
 6             ...
 7             
 8             root = new ViewRootImpl(view.getContext());
 9             ...
10             root.setView(view, wparams, panelParentView);
11     } 

在ViewRootImpl的setView方法中找到如下代碼:view.assignParent(this);也即將DecorView的mParent指定為ViewRootImpl實例,並且在第6行發現調用了requestLayout方法。

 1      public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
 2          synchronized (this) {
 3              if (mView == null) {
 4                  mView = view;
 5                  ...
 6           requestLayout();
 7           ...
 8 
 9                  view.assignParent(this);
10                  ...
11      } 

進入到ViewRootImpl的requestLayout方法:

1     public void requestLayout() {
2         checkThread();
3         mLayoutRequested = true;
4         scheduleTraversals();
5     }

之后的流程參考從ViewRootImpl類分析View繪制的流程一文。

從以上分析可知,每一次notifyDataSetChange()都會引起界面的重繪,重繪的最終實現是在ViewRootImpl.java中。

二、notifyDataSetChanged與setAdapter區別

仔細閱讀ListView的setAdapter方法,當ListView之前綁定過adapter信息時,在這里會清除原有Adapter和數據集觀察者等信息,重置了ListView當前選中項等信息,並在方法的最后一句調用requestLayout進行界面的重繪。

 1 public void setAdapter(ListAdapter adapter) {
 2         // 與原有觀察者解綁定
 3         if (mAdapter != null && mDataSetObserver != null) {
 4             mAdapter.unregisterDataSetObserver(mDataSetObserver);
 5         }
 6 
 7         resetList();
 8         mRecycler.clear();
 9 
10         if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
11             mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
12         } else {
13             mAdapter = adapter;
14         }
15         
16         mOldSelectedPosition = INVALID_POSITION;
17         mOldSelectedRowId = INVALID_ROW_ID;
18 
19         // AbsListView#setAdapter will update choice mode states.
20         super.setAdapter(adapter);
21 
22         if (mAdapter != null) {
23             mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
24             mOldItemCount = mItemCount;
25             mItemCount = mAdapter.getCount();
26             checkFocus();
27             // 重新綁定新的數據集觀察者
28             mDataSetObserver = new AdapterDataSetObserver();
29             mAdapter.registerDataSetObserver(mDataSetObserver);
30 
31             mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
32 
33             int position;
34             if (mStackFromBottom) {
35                 position = lookForSelectablePosition(mItemCount - 1, false);
36             } else {
37                 position = lookForSelectablePosition(0, true);
38             }
39             setSelectedPositionInt(position);
40             setNextSelectedPositionInt(position);
41 
42             if (mItemCount == 0) {
43                 // Nothing selected
44                 checkSelectionChanged();
45             }
46         } else {
47             mAreAllItemsSelectable = true;
48             checkFocus();
49             // Nothing selected
50             checkSelectionChanged();
51         }
52         // 重繪
53         requestLayout();
54     }

由此可知,調用adapter.notifyDataSetChanged與listView.setAdapter函數都會引起界面重繪,區別是前者會保留原有位置、數據信息,后者是回到初始狀態。

 

 注:以上過程純屬個人探索,如有錯誤敬請批評指正。

參考文獻:

1.從ViewRootImpl類分析View繪制的流程(http://blog.csdn.net/feiduclear_up/article/details/46772477)

2.從源代碼的角度分析--在BaseAdapter調用notifyDataSetChanged()之后發生了什么(http://www.cnblogs.com/kissazi2/p/3721941.html )


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM