一、bug分析
Adapter数据集List变化后,没有及时调用Adapter.notifyDataSetChanged方法来刷新listview视图。
mAdapter = new JobSearchAdapter(this, mDatas, new OnClickListener(){});
@Override
public void refresh() {
HashMap<String, String> map = createListRequestParams(true, false);
NetManager.GET(createListUrl()).addParam(map)
.request(new HttpCallback<BaseResult<JobResultBean>>() {
@Override
public void onSuccess(BaseResult<JobResultBean> data) {
if (!isFinishing()) {
listData = data;
mDatas.clear();
if (refreshLayout == null) return;
if (listData == null || listData.data == null) {
//错误状态
onRefreshError(ChrBaseListFragment.NoDataType.LOAD_FILED, "");
return;
}
//省略部分代码
showListData(listData);
}protected void showListData(BaseResult<JobResultBean> data) {
mDatas.clear();
if (data.getData().recruitList != null) {
mDatas.addAll(data.getData().recruitList);
}
//省略部分代码
mAdapter.notifyDataSetChanged();
mCurrentPage++;//请求一次后,pageNum+1
}
通过代码可以看出,如果listData == null || listData.data == null,那么就不会执行后面的mAdapter.notifyDataSetChanged()方法,而mDatas数据集是通过引用的方式传值,这就导致mDatas发生变化后,内存中数据集就发生了变化。但是RecyclerView没有及时刷新UI(同步数据集和listView的对应)。这样当发生页面重新渲染,重新绘制对应的item view时,就会出现数据集为空,但index对应不上的bug。
通过bugly反馈,发现只有部分手机会出现,并不是必现。
解决方式
1、及时通知刷新
if (listData == null || listData.data == null) {
//错误状态
mAdapter.notifyDataSetChanged(); onRefreshError(ChrBaseListFragment.NoDataType.LOAD_FILED, "");
return;
}
2、不使用数据集引用方式,在每次需要更新listview数据时,都重新赋值。setData和notifyDataSetChanged 成对出现
mAdapter = new JobSearchAdapter(this, new ArrayList<IJobBaseBean>() , new OnClickListener())
mAdapter.setData(mDatas);
mAdapter.notifyDataSetChanged();
二、Adapter.notifyDataSetChanged 方法
listView中item布局视图刷新采用观察者模式(订阅-发布模式)。ListView.setAdapter核心源码如下:
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
mItemCount = mAdapter.getCount();
mDataSetObserver = new AdapterDataSetObserver();//创建一个订阅者
mAdapter.registerDataSetObserver(mDataSetObserver);//将订阅者注册到发布者里(实质是添加到一个数组,发布者维护了一个订阅者数组)
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
requestLayout();
}
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public abstract class Observable<T> {
/**
* The list of observers. An observer can be in the list at most
* once and will never be null.
*/
protected final ArrayList<T> mObservers = new ArrayList<T>();
public void registerObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
if (mObservers.contains(observer)) {
throw new IllegalStateException("Observer " + observer + " is already registered.");
}
mObservers.add(observer);
}
}
public void unregisterObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
int index = mObservers.indexOf(observer);
if (index == -1) {
throw new IllegalStateException("Observer " + observer + " was not registered.");
}
mObservers.remove(index);
}
}
Adapter通知列表刷新方法
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
DataSetObservable中调用
public class DataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
最终是调用订阅者自己的onChanged方法。
AdapterDataSetObserver具体实现类主要是集成自AdapterView类的内部类
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();//重新执行measure、layout、draw来绘制listview
}