美文网首页
Adapter.notifyDataSetChanged与Lis

Adapter.notifyDataSetChanged与Lis

作者: 空而小sao | 来源:发表于2017-09-23 14:23 被阅读0次
    二哈镇楼

    笔者在实际开发中碰到的问题,在这里记录一下

    描述一下 碰见的问题:在一个listview页面中,onResume()回来,请求完数据后,对adapter进行notifyDataSetChanged后,需要对item进行ui操作。这时候首先页面展现的时候,ListView 会getView()一遍,请求完数据notifyDataSetChanged后,又会getView(),这时候直接进行UI操作的话 在主线程中 会先执行UI操作,然后在进行getView ,就会导致我的进行UI操作后的item 又被重新getView了一遍。


    图1

    notifyDataSetChanged 异步机制

    首先这个机制问题,不是直接在主线程中去更新UI,来看一下里面的代码

    /**
     * Notifies the attached observers that the underlying data has been changed
     * and any View reflecting the data set should refresh itself.
     */
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }
    ***
    继续进入 DataSetObservable:
    ***
     /**
     * Invokes {@link DataSetObserver#onChanged} on each observer.
     * Called when the contents of the data set have changed.  The recipient
     * will obtain the new contents the next time it queries the data set.
     */
    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()方法,它的实现方法在DataSetObservable的子类中AdapterDataSetObserver实现。而AdapterDataSetObserver则是在listview.setadapter 时候将AdapterDataSetObserver创建并且绑定,可以看一下setAdapter()方法

    /**
     * Sets the data behind this ListView.
     *
     * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
     * depending on the ListView features currently in use. For instance, adding
     * headers and/or footers will cause the adapter to be wrapped.
     *
     * @param adapter The ListAdapter which is responsible for maintaining the
     *        data backing this list and for producing a view to represent an
     *        item in that data set.
     *
     * @see #getAdapter()
     */
    @Override
    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }
    
        resetList();
        mRecycler.clear();
    
        if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }
    
        mOldSelectedPosition = INVALID_POSITION;
        mOldSelectedRowId = INVALID_ROW_ID;
    
        // AbsListView#setAdapter will update choice mode states.
        super.setAdapter(adapter);
    
        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();
            checkFocus();
    
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);
    
            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
    
            int position;
            if (mStackFromBottom) {
                position = lookForSelectablePosition(mItemCount - 1, false);
            } else {
                position = lookForSelectablePosition(0, true);
            }
            setSelectedPositionInt(position);
            setNextSelectedPositionInt(position);
    
            if (mItemCount == 0) {
                // Nothing selected
                checkSelectionChanged();
            }
        } else {
            mAreAllItemsSelectable = true;
            checkFocus();
            // Nothing selected
            checkSelectionChanged();
        }
    
        requestLayout();
    }
    

    再看下AdapterDataSetObserver 中onChanged的实现

    class AdapterDataSetObserver extends DataSetObserver {
    
            private Parcelable mInstanceState = null;
    
            @Override
            public void onChanged() {
                mDataChanged = true;
                mOldItemCount = mItemCount;
                mItemCount = getAdapter().getCount();
    
                // Detect the case where a cursor that was previously invalidated has
                // been repopulated with new data.
                if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                        && mOldItemCount == 0 && mItemCount > 0) {
                    AdapterView.this.onRestoreInstanceState(mInstanceState);
                    mInstanceState = null;
                } else {
                    rememberSyncState();
                }
                checkFocus();
                requestLayout();
            }
    
            @Override
            public void onInvalidated() {
                mDataChanged = true;
    
                if (AdapterView.this.getAdapter().hasStableIds()) {
                    // Remember the current state for the case where our hosting activity is being
                    // stopped and later restarted
                    mInstanceState = AdapterView.this.onSaveInstanceState();
                }
    
                // Data is invalid so we should reset our state
                mOldItemCount = mItemCount;
                mItemCount = 0;
                mSelectedPosition = INVALID_POSITION;
                mSelectedRowId = INVALID_ROW_ID;
                mNextSelectedPosition = INVALID_POSITION;
                mNextSelectedRowId = INVALID_ROW_ID;
                mNeedSync = false;
    
                checkFocus();
                requestLayout();
            }
    

    这时候,才会调用view的requestLayout()放在主线程中重绘页面。所以notifyDataSetChanged调用的时候是异步调用观察者里的方法,然后push到主线程里去刷新UI。因此
    notifyDataSetChanged之后直接对UI进行操作的话,view的UI操作会在绘制队列,才导致问题的关键

    那如何解决问题呢

    都只要view中有个方法Post(),将任务push到主线程队列中,意思也是将任务添加到消息队列中,保证在UI线程执行。从本质上说,它还是依赖于以Handler、Looper、MessageQueue、Message为基础的异步消息处理机制。大家一定多碰到过,在onCreate()里面去获取一个view的高宽度时候,往往得到的数值是0;也都知道原因,view要经过onMeasure、onLayout和onDraw三个过程,在onCreate()时候明显,view 还没有到onLayout,也就没有宽度。这时候只要进行view.Post(),将计算高宽的方法放置post里面,这样获取的就没问题了。可以看下post的源码:

    public boolean post(Runnable action) {    
      final AttachInfo attachInfo = mAttachInfo;
      if (attachInfo != null) {       
      return attachInfo.mHandler.post(action);
      }    
    // Assume that post will succeed later       
      ViewRootImpl.getRunQueue().post(action); 
      return true;
    }
    
    

    原理就在view.Post()是将任务添加到这个view绘制结束后的消息队列中,保证了这个view绘制的优先性。受到了这个启发,我认为listview的adapter.ontifyDataSetchanged方法也是这样的道理,亲测将刷新UI操作放在listview.post()中进行操作,这时候打印出来的日志,就是我UI的操作在getview完成之后进行了。为了防止是因为异步的先后行,特的将getview次数增加到10次


    图2

    最后完美解决问题

    相关文章

      网友评论

          本文标题:Adapter.notifyDataSetChanged与Lis

          本文链接:https://www.haomeiwen.com/subject/qkxcextx.html