一个ListView有关数据设置和刷新问题

问题描述

当数据变化是,没调notifyDataSetChanged方法刷新控件,滑动列表,就会报错如下

java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(2131559716, class android.widget.ListView) with Adapter(class kr)]
at android.widget.ListView.layoutChildren(ListView.java:1562)
 at android.widget.AbsListView.onTouchMove(AbsListView.java:3919)
 at android.widget.AbsListView.onTouchEvent(AbsListView.java:3788)
 at android.view.View.dispatchTouchEvent(View.java:8444)
 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2158)
 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2436)
 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2178)
 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2436)
 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2178)
 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2436)
 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2178)
 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2436)
 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2178)
 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2436)
 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2178)
 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2436)
 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2178)
 at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2386)
 at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1727)
 at android.app.Activity.dispatchTouchEvent(Activity.java:2764)
 at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2335)
 at android.view.View.dispatchPointerEvent(View.java:8655)
 at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4238)
 at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4094)
 at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3635)
 at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3694)
 at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3660)
 at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3773)
 at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3668)
 at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3830)
 at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3640)
 at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3694)
 at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3660)
 at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3668)
 at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3640)
 at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5940)
 at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5908)
 at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5872)
 at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6030)
 at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:211)
 at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)
 at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:197)
 ....

问题代码

public class VideoBroadcastViewsHolder {
......
private ListView mMsgListView;
private List<VideoBroadcast> mMsgDatas;
private BroadcastMsgAdapter mBroadcastMsgAdapter;
...
...
private void updateMsgList(VideoBroadcast videoBroadcast) {
    if (TextUtils.isEmpty(videoBroadcast.getMsg())) {
        return;
    }
    if (mMsgDatas == null) {
        mMsgDatas = new ArrayList<>();
        mBroadcastMsgAdapter = new BroadcastMsgAdapter(mContext,mMsgDatas);
        if (mMsgListView != null) {
            mMsgListView.setAdapter(mBroadcastMsgAdapter);
        }
    }
    mMsgDatas.add(videoBroadcast);
    toRefreshListView(RefreshDelayedTime);
}
 private void toRefreshListView(long delayedTime) {
    if (mMsgListView != null) {
        if (mListRefreshRunnable != null) {
            mMsgListView.removeCallbacks(mListRefreshRunnable);
        }
        DebugLog.d("VideoBroadcastViewsHolder", "toRefreshListView isListViewTouching : " + isListViewTouching + " ,delayedTime :" + delayedTime);
        if (isListViewTouching) {
            return;
        }
        if (System.currentTimeMillis() - mLastListRefreshTime >= RefreshIntervalTime) {
            mMsgListView.post(new ListRefreshRunnable());
        } else {
            mListRefreshRunnable = new ListRefreshRunnable();
            mMsgListView.postDelayed(mListRefreshRunnable, delayedTime);
        }
    }

}
......
}

public class BroadcastMsgAdapter extends BaseAdapter {

// 上下文
private Context mContext;

private List<VideoBroadcast> mDatas;

public BroadcastMsgAdapter(Context context ,List<VideoBroadcast> datas) {
    mContext = context;
    mDatas = datas;
}
...
}

问题分析

当列表适配器直接用的是外面传递进来的列表时,外围列表的管理者对列表进行数据增减,使其数据发生了变化,但应某种原因或者采用的具体策略,没及时通知适配器,在这中间时段,如果用户滑动列表,系统就会报错。

个人建议

让适配器拥有自己的数据列表,这样就可避免类似问题。数据的使用者使者拥有和管理自己的数据,降低耦合性,遵循代码设计原则。

简单修改后,适配器代码如下:

public class BroadcastMsgAdapter extends BaseAdapter {

// 上下文
private Context mContext;

private List<VideoBroadcast> mDatas;

public BroadcastMsgAdapter(Context context) {
    mContext = context;
    mDatas = new ArrayList<>();
}

public void setDatas(List<VideoBroadcast> datas) {
    if (datas != null && datas.size() > 0) {
        mDatas.clear();
        mDatas.addAll(datas);
    }
    notifyDataSetChanged();
}
...
}
  • 适当调整使用的地方,问题就OK了。*

小问题,通常都不好定位,需要自己平时注意小细节,培养良好的编程习惯。写代码时,多遵循设计原则!