从源码分析 Android 中 ViewPager 的 Adapter 数据不更新问题

在 Android 中使用 ViewPager 时,经常需要动态增删 View 或者 Fragment ,但是当更新完数据并调用 notifyDataSetChanged() 方法之后,发现数据并没有更新。

对于一般类似组件的 BaseAdapter 比如 ListViewGridView 等只需要调用 notifyDataSetChanged() 方法,就会直接触发数据更新。因此,很多人认为这是 ViewPager 的一个 bug 。

通常的解决方案是重写 PagerAdaptergetItemPosition() 方法,并返回 POSITION_NONE ,以触发 PagerAdapter 销毁并重建对象。

下面我们通过源代码分析为什么必须 @override 这个方法。

首先看 PagerAdapter 源代码,它的 notifiDataSetChanged() 方法内部调用了一个 DataSetObservable 对象的 notifyChanged() 方法:

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

然后看看 DataSetObservable 源代码,它的 notifyChanged() 方法内部调用了 DataSetObserveronChanged() 方法:

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();
		}
	}
}

最后来看 ViewPager 源代码,它内部定义了一个继承自 DataSetObserverPagerObserver 内部类:

private class PagerObserver extends DataSetObserver {
	@Override
	public void onChanged() {
		dataSetChanged();
	}
	@Override
	public void onInvalidated() {
		dataSetChanged();
	}
}

而这个 PagerObserver 类的 onChanged() 方法内部调用了 dataSetChanged() 方法,那我们来看看这个方法的内部实现:

void dataSetChanged() {
	// This method only gets called if our observer is attached, so mAdapter is non-null.
	boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount();
	int newCurrItem = -1;
	boolean isUpdating = false;
	for (int i = 0; i < mItems.size(); i++) {
		final ItemInfo ii = mItems.get(i);
		final int newPos = mAdapter.getItemPosition(ii.object);
		if (newPos == PagerAdapter.POSITION_UNCHANGED) {
			continue;
		}
		if (newPos == PagerAdapter.POSITION_NONE) {
			mItems.remove(i);
			i--;
			if (!isUpdating) {
				mAdapter.startUpdate(this);
				isUpdating = true;
			}
			mAdapter.destroyItem(this, ii.position, ii.object);
			needPopulate = true;
			if (mCurItem == ii.position) {
				// Keep the current item in the valid range
				newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
			}
			continue;
		}
		if (ii.position != newPos) {
			if (ii.position == mCurItem) {
				// Our current item changed position. Follow it.
				newCurrItem = newPos;
			}
			ii.position = newPos;
			needPopulate = true;
		}
	}
	if (isUpdating) {
		mAdapter.finishUpdate(this);
	}
	Collections.sort(mItems, COMPARATOR);
	if (newCurrItem >= 0) {
		// TODO This currently causes a jump.
		setCurrentItemInternal(newCurrItem, false, true);
		needPopulate = true;
	}
	if (needPopulate) {
		populate();
		requestLayout();
	}
}

从中我们看到,它会判断 adaptergetItemPosition() 方法返回值,只有当返回值为 POSITION_NONE 时,才会调用 itemremove() 方法以及 adapterstartUpdate()destroyItem() 两个方法,进而去更新数据。而如果返回值为 POSITION_UNCHANGED ,则不执行任何操作。

再回过头看看 PagerAdaptergetItemPosition() 方法的默认实现:

public int getItemPosition(Object object) {
	return POSITION_UNCHANGED;
}

可以看到这个方法默认返回的恰好是 POSITION_UNCHANGED ,这样就不难理解为什么我们必须 @override 这个方法了。