在用户滚动时更新了ListView数据,可能导致在layoutChildren()中因为itemCount与adapter.count()不一致,产生IllegalStateException。
protected void layoutChildren() {
...
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() + ")]");
}
...
}
原因:
-
notifyDataChanged()的调用链为:
notifyDataChanged() -> AdapterDataSetObserver.onChanged() -> ListView.requestLayout()布局任务会被放到消息队列中,等待vsync信号到来时执行。在执行ListView.onMeasure()时,会更新itemCount的数值为adapter.count()。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ... mItemCount = mAdapter == null ? 0 : mAdapter.getCount(); ... } -
用户上下滚动ListView,产生的输入事件也被放到消息队列中,等待vsync信号到来时执行。这些输入事件会最终引起ListView.layoutChildren()被调用。
-
Choreographer 执行顺序:
- 输入事件处理(
ConsumeBatchedInputRunnable)。 - 动画处理。
- 布局与绘制(
performTraversals())。
- 输入事件处理(
-
在vsync信号到来时输入事件是早于布局任务被执行的,在数据更新之后,出现没有调用onMeasure()就执行layoutChildren()的情况,从而出现itemCount与adapter.count()的数值不一致。