灰度的时候出的 crash
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(2131624042, class android.widget.ListView) with Adapter(class com.sankuai.meituan.meituanwaimaibusiness.modules.order.adapter.j)]
at android.widget.ListView.layoutChildren(ListView.java:1572)
at android.widget.AbsListView.onLayout(AbsListView.java:2586)
at android.view.View.layout(View.java:15752)
at android.view.ViewGroup.layout(ViewGroup.java:4932)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1692)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1534)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1443)
at android.view.View.layout(View.java:15752)
at android.view.ViewGroup.layout(ViewGroup.java:4932)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1692)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1534)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1443)
at android.view.View.layout(View.java:15752)
at android.view.ViewGroup.layout(ViewGroup.java:4932)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:15752)
at android.view.ViewGroup.layout(ViewGroup.java:4932)
at com.android.internal.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:464)
at android.view.View.layout(View.java:15752)
at android.view.ViewGroup.layout(ViewGroup.java:4932)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:15752)
at android.view.ViewGroup.layout(ViewGroup.java:4932)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2410)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2125)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1292)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6678)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:813)
at android.view.Choreographer.doCallbacks(Choreographer.java:613)
at android.view.Choreographer.doFrame(Choreographer.java:583)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:799)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:146)
at android.app.ActivityThread.main(ActivityThread.java:5752)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
at dalvik.system.NativeStart.main(Native Method)
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.
大概的意思是当 adapter 的数据改变时要及时调用notifyDataSetChanged()
,并且是在 UI 线程进行的,检查了OrderAdpater
的setData()
调用处,均在 UI 线程
查看 ListView 源码,找到抛出异常的地方
// Handle the empty set by removing all views that are visible
// and calling it a day
if (mItemCount == 0) {
resetList();
invokeOnItemScrollListener();
return;
} else if (mItemCount != mAdapter.getCount()) {
throw new 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(" + getId() + ", " + getClass()
+ ") with Adapter(" + mAdapter.getClass() + ")]");
}
可以看出 mItemCount != mAdapter.getCount()
导致的。
mAdapter.getCount()
很好理解,就是我们复写Adapter 的 getCount()
方法。
的调用处有2个地方
在
public void setAdapter(ListAdapter adapter)
中
mItemCount = mAdapter.getCount();
在
onMeasure()
时
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
所以有时没有调用
notify
并不会引起崩溃还有一个地方比较隐蔽,在
ListView
的父类的父类AdapterView
中,在notifyDataSetChanged()
时调用
@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();
}
所以崩溃的原因就是list 的数据变化时,没有及时notifyDataSetChanged()
或者onMeasure()
导致mItemCount
和getCount()
的数据不一致
在检查我们的adapter 的 setData()
方法
public void setData(ArrayList<Order> data) {
this.mOrderList = data;
notifyDataSetChanged();
}
public void notifyDataSetChanged() {
mHandler.removeMessages(MSG_NOTIFY_DATA_CHANGED);
mHandler.sendMessage(mHandler.obtainMessage(MSG_NOTIFY_DATA_CHANGED));
}
public OrderAdapter(Context context, String page, boolean autoRefresh) {
mContext = context;
mOrderViewBinder = new OrderViewBinder(context, page);
mAutoRefreshable = autoRefresh;
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
int what = msg.what;
switch (what) {
case MSG_NOTIFY_DATA_CHANGED:
OrderAdapter.super.notifyDataSetChanged();
if (mAutoRefreshable) {
Message refreshMsg = mHandler.obtainMessage(MSG_NOTIFY_DATA_CHANGED);
mHandler.sendMessageDelayed(refreshMsg, REFRESH_FREQ);
}
default:
return;
}
}
};
}
在改变数据之后并没有及时的notifyDataSetChanged()
,而是发了一个 message ,在 handler中在调用notifyDataSetChanged
,在数据改变之后,可能 handler 还没收到消息,导致mItemCount
和getCount()
的数据不一致
解决办法
- 在
handler
中在对数据赋值并notify - 在
setdata
之后立即notify