2013/07/29

java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. !!

環境: android-17

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. [in ListView(16908298, class android.widget.ListView)
with Adapter(class android.widget.HeaderViewListAdapter)]

反覆檢查了一下所有使用 ListView, 確定都是在 UI Thread 改動, 但是卻一樣發生上面問題. 查看 Listview source code.

} 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. [in ListView(" + getId() + ", " + getClass() 
                        + ") with Adapter(" + mAdapter.getClass() + ")]");
            }

裡面判斷 mItemCount 是否等於 mAdapter.getCount(), 而 mItemCount 在以下兩個地方取得:
1. setAdapter 的時候
2. onMeasure 的時候, 也就是 UI 有變動的時候.

而我所使用的 Adapter 都是從 ArrayAdapter 來. 在 add object 時, 都會通知一下資料變動, 理論上, ListView 也會收到通知,
所以跟著 mItemCount 會跟著修改, 但實際上 .... 一樣出現上面錯誤 !!!!
public void add(T object) {
        synchronized (mLock) {
            if (mOriginalValues != null) {
                mOriginalValues.add(object);
            } else {
                mObjects.add(object);
            }
        }
        if (mNotifyOnChange) notifyDataSetChanged();
    }

Google 了半天, 最後我自己歸納以下結果, 當 setAdapter 後, 在大量新增 item 時, 有可能 ListView Upate 會跟不上 Adapter 的 Update.
所以當使用者捲動或者點擊時, 就有可能會出現這種狀況. (發生機率不高, 但從 Acra report, 我卻還滿常看到這 exception).

以下是我的解決方法, 到目前為止沒有在看到此 exception

import android.content.Context;
import android.widget.ArrayAdapter;

import java.util.List;

public class MyArrayAdapter extends ArrayAdapter {

    private List mObjects;
    private int count = 0;
    private boolean mNotifyOnChange = true;

    public MyArrayAdapter(Context context, int textViewResourceId, List objects) {
        super(context, textViewResourceId, objects);
        mObjects = objects;
        count = objects.size();
    }

    final public void beginTrans() {
        setNotifyOnChange(false);
        mNotifyOnChange = false;
    }


    final public void endTrans() {
        setNotifyOnChange(true);
        notifyDataSetChanged();
    }

    @Override
    final public int getCount() {
        return count;
    }

    @Override
    final public void notifyDataSetChanged() {
        count = mObjects.size();
        mNotifyOnChange = true;
        super.notifyDataSetChanged();
    }

    @Override
    final public T getItem(int position) {
        if (position >= getCount()) {
            return null;
        }
        if (position >= mObjects.size()) {
            return null;
        }
        return super.getItem(position);
    }

}


當要加入大量的 item 時, 則做以下動作.
1. call beginTrans
2. start to batch add items.
3. call endTrans

總算解決一個問題 :D