删除项目后 CursorAdapter 崩溃

Crash in CursorAdapter after deleting items

从数据库 table 中删除数据后,我的代码崩溃了。 我正在使用带有 CursorWrapper 的 CursorAdapter。

场景是:

  1. CursorAdapter 从游标加载器接收一个包装游标
  2. 从 ContentProvider 中删除所有项目(调用了 notifyChange)

这是删除后的堆栈跟踪:

java.lang.IllegalStateException: couldn't move cursor to position 0
                                                                              at android.widget.CursorAdapter.getView(CursorAdapter.java:281)
                                                                              at android.widget.AbsListView.obtainView(AbsListView.java:2929)
                                                                              at android.widget.ListView.makeAndAddView(ListView.java:1945)
                                                                              at android.widget.ListView.fillSpecific(ListView.java:1379)
                                                                              at android.widget.ListView.layoutChildren(ListView.java:1712)
                                                                              at android.widget.AbsListView.onLayout(AbsListView.java:2723)
                                                                              at android.view.View.layout(View.java:17938)
                                                                              at android.view.ViewGroup.layout(ViewGroup.java:5812)
                                                                              at android.widget.FrameLayout.layoutChildren(FrameLayout.java:344)
                                                                              at android.widget.FrameLayout.onLayout(FrameLayout.java:281)
                                                                              at android.view.View.layout(View.java:17938)
                                                                              at android.view.ViewGroup.layout(ViewGroup.java:5812)
                                                                              at android.widget.FrameLayout.layoutChildren(FrameLayout.java:344)
                                                                              at android.widget.FrameLayout.onLayout(FrameLayout.java:281)
                                                                              at android.view.View.layout(View.java:17938)
                                                                              at android.view.ViewGroup.layout(ViewGroup.java:5812)
                                                                              at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1742)
                                                                              at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1585)
                                                                              at android.widget.LinearLayout.onLayout(LinearLayout.java:1494)
                                                                              at android.view.View.layout(View.java:17938)
                                                                              at android.view.ViewGroup.layout(ViewGroup.java:5812)
                                                                              at android.support.v4.widget.DrawerLayout.onLayout(DrawerLayout.java:1187)
                                                                              at android.view.View.layout(View.java:17938)
                                                                              at android.view.ViewGroup.layout(ViewGroup.java:5812)
                                                                              at android.widget.FrameLayout.layoutChildren(FrameLayout.java:344)
                                                                              at android.widget.FrameLayout.onLayout(FrameLayout.java:281)
                                                                              at android.view.View.layout(View.java:17938)
                                                                              at android.view.ViewGroup.layout(ViewGroup.java:5812)
                                                                              at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1742)
                                                                              at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1585)
                                                                              at android.widget.LinearLayout.onLayout(LinearLayout.java:1494)
                                                                              at android.view.View.layout(View.java:17938)
                                                                              at android.view.ViewGroup.layout(ViewGroup.java:5812)
                                                                              at android.widget.FrameLayout.layoutChildren(FrameLayout.java:344)
                                                                              at android.widget.FrameLayout.onLayout(FrameLayout.java:281)
                                                                              at android.view.View.layout(View.java:17938)
                                                                              at android.view.ViewGroup.layout(ViewGroup.java:5812)
                                                                              at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1742)
                                                                              at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1585)
                                                                              at android.widget.LinearLayout.onLayout(LinearLayout.java:1494)
                                                                              at android.view.View.layout(View.java:17938)
                                                                              at android.view.ViewGroup.layout(ViewGroup.java:5812)
                                                                              at android.widget.FrameLayout.layoutChildren(FrameLayout.java:344)
                                                                              at android.widget.FrameLayout.onLayout(FrameLayout.java:281)
                                                                              at com.android.internal.policy.PhoneWindow$DecorView.onLayout(PhoneWindow.java:3193)
                                                                              at android.view.View.layout(View.java:17938)
                                                                              at android.view.ViewGroup.layout(ViewGroup.java:5812)
                                                                              at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2666)
                                                                              at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2367)
                                                                              at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1437)
                                                                              at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7397)
                                                                              at android.view.Choreographer$CallbackRecord.run(Choreographer.java:920)
                                                                              at android.view.Choreographer.doCallbacks(Choreographer.java:695)
                                                                              at android.view.Choreographer.doFrame(Choreographer.java:631)
                                                                              at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:906)
                                                                              at android.os.Handler.handleCallback(Handler.java:739)
                                                                              at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                              at android.os.Looper.loop(Looper.java:158)
                                                                              at android.app.ActivityThread.main(ActivityThread.java:7224)
                                                                              at java.lang.reflect.Method.invoke(Native Method)
                                                                              at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
                                                                              at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

游标包装取自here

/**

* 包裹 Cursor 并允许其位置被过滤掉、重复或重新排序。常见的制作方法 * FilteredCursor 对象由 {@link FilteredCursorFactory} 提供。 * * 请注意,如果源 Cursor 超过 {@link android.database.CursorWindow} 的大小,则 FilteredCursor * 由于频繁的 CursorWindow 缓存未命中,最终可能会导致性能极差。在那些情况下是 * 建议源 Cursor 包含较少的数据。 * * @author 雅各布惠特克艾布拉姆斯 */ public class FilteredCursor 扩展了 CursorWrapper {

// Globally map master Cursor to FilteredCursors, when all FilteredCursors are closed go ahead and close the master
// This would need to go into a singleton if other classes similar to FilteredCursor exist
private static final Map<Cursor, Set<FilteredCursor>> sMasterCursorMap =
        Collections.synchronizedMap(new WeakHashMap<Cursor, Set<FilteredCursor>>());

private int[] mFilterMap;
private int mPos = -1;
private final Cursor mCursor;
private boolean mClosed;

/**
 * Create a FilteredCursor that appears identical to its wrapped Cursor.
 */
public static FilteredCursor createUsingIdentityFilter(Cursor cursor) {
    if (cursor == null) {
        return null;
    }
    return new FilteredCursor(cursor);
}

/**
 * Create a new FilteredCursor using the given filter. The filter specifies where rows of the given Cursor should
 * appear in the FilteredCursor. For example if filter = { 5, 9 } then the FilteredCursor will have two rows, the
 * first row maps to row 5 in the source Cursor and the second row maps to row 9 in the source cursor. Returns null
 * if the provided cursor is null. A value of -1 in the filter is treated as an empty row in the Cursor with no data,
 * see {@link FilteredCursor#isPositionEmpty()}.
 */
public static FilteredCursor createUsingFilter(Cursor cursor, int[] filter) {
    if (cursor == null) {
        return null;
    }
    if (filter == null) {
        throw new NullPointerException();
    }
    return new FilteredCursor(cursor, filter);
}

private FilteredCursor(Cursor cursor) {
    this(cursor, null);
    resetToIdentityFilter();
}

private FilteredCursor(Cursor cursor, int[] filterMap) {
    super(cursor);
    mCursor = cursor;
    mFilterMap = filterMap;
    attachToMasterCursor();
}

public int[] getFilterMap() {
    return mFilterMap;
}

/**
 * Reset the filter so it appears identical to its wrapped Cursor.
 */
public FilteredCursor resetToIdentityFilter() {
    int count = mCursor.getCount();
    int[] filterMap = new int[count];

    for (int i = 0; i < count; i++) {
        filterMap[i] = i;
    }

    mFilterMap = filterMap;
    mPos = -1;
    return this;
}

/**
 * Returns true if the FilteredCursor appears identical to its wrapped Cursor.
 */
public boolean isIdentityFilter() {
    int count = mCursor.getCount();
    if (mFilterMap.length != count) {
        return false;
    }

    for (int i = 0; i < count; i++) {
        if (mFilterMap[i] != i) {
            return false;
        }
    }

    return true;
}

/**
 * Rearrange the filter. The new arrangement is based on the current filter arrangement, not on the source Cursor's
 * arrangement.
 */
public FilteredCursor refilter(int[] newArrangement) {
    final int newMapSize = newArrangement.length;
    int[] newMap = new int[newMapSize];
    for (int i = 0; i < newMapSize; i++) {
        newMap[i] = mFilterMap[newArrangement[i]];
    }

    mFilterMap = newMap;
    mPos = -1;
    return this;
}

/**
 * True if the current cursor position has no data. Attempting to access data in an empty row with any of the getters
 * will throw {@link UnsupportedOperationException}.
 */
public boolean isPositionEmpty() {
    return mFilterMap[mPos] == -1;
}

private void throwIfEmptyRow() {
    if (isPositionEmpty()) {
        throw new UnsupportedOperationException("Cannot access data in an empty row");
    }
}

public void swapItems(int itemOne, int itemTwo) {
    int temp = mFilterMap[itemOne];
    mFilterMap[itemOne] = mFilterMap[itemTwo];
    mFilterMap[itemTwo] = temp;
}

@Override
public int getCount() {
    return mFilterMap.length;
}

@Override
public int getPosition() {
    return mPos;
}

@Override
public boolean moveToPosition(int position) {
    // Make sure position isn't past the end of the cursor
    final int count = getCount();
    if (position >= count) {
        mPos = count;
        return false;
    }

    // Make sure position isn't before the beginning of the cursor
    if (position < 0) {
        mPos = -1;
        return false;
    }

    final int realPosition = mFilterMap[position];

    // When moving to an empty position, just pretend we did it
    boolean moved = realPosition == -1 ? true : super.moveToPosition(realPosition);
    if (moved) {
        mPos = position;
    } else {
        mPos = -1;
    }
    return moved;
}

@Override
public final boolean move(int offset) {
    return moveToPosition(mPos + offset);
}

@Override
public final boolean moveToFirst() {
    return moveToPosition(0);
}

@Override
public final boolean moveToLast() {
    return moveToPosition(getCount() - 1);
}

@Override
public final boolean moveToNext() {
    return moveToPosition(mPos + 1);
}

@Override
public final boolean moveToPrevious() {
    return moveToPosition(mPos - 1);
}

@Override
public final boolean isFirst() {
    return mPos == 0 && getCount() != 0;
}

@Override
public final boolean isLast() {
    int count = getCount();
    return mPos == (count - 1) && count != 0;
}

@Override
public final boolean isBeforeFirst() {
    if (getCount() == 0) {
        return true;
    }
    return mPos == -1;
}

@Override
public final boolean isAfterLast() {
    if (getCount() == 0) {
        return true;
    }
    return mPos == getCount();
}

@Override
public boolean isNull(int columnIndex) {
    throwIfEmptyRow();
    return mCursor.isNull(columnIndex);
}

@Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
    throwIfEmptyRow();
    mCursor.copyStringToBuffer(columnIndex, buffer);
}

@Override
public byte[] getBlob(int columnIndex) {
    throwIfEmptyRow();
    return mCursor.getBlob(columnIndex);
}

@Override
public double getDouble(int columnIndex) {
    throwIfEmptyRow();
    return mCursor.getDouble(columnIndex);
}

@Override
public float getFloat(int columnIndex) {
    throwIfEmptyRow();
    return mCursor.getFloat(columnIndex);
}

@Override
public int getInt(int columnIndex) {
    throwIfEmptyRow();
    return mCursor.getInt(columnIndex);
}

@Override
public long getLong(int columnIndex) {
    throwIfEmptyRow();
    return mCursor.getLong(columnIndex);
}

@Override
public short getShort(int columnIndex) {
    throwIfEmptyRow();
    return mCursor.getShort(columnIndex);
}

@Override
public String getString(int columnIndex) {
    throwIfEmptyRow();
    return mCursor.getString(columnIndex);
}

@Override
public boolean isClosed() {
    return mClosed || getMasterCursor().isClosed();
}

@Override
public void close() {
    // Mark this Cursor as closed
    mClosed = true;

    // Only close the wrapped cursor if no other FilteredCursors are referencing the master cursor
    Cursor masterCursor = getMasterCursor();

    Set<FilteredCursor> linkedFilteredCursorSet = sMasterCursorMap.get(masterCursor);
    if (linkedFilteredCursorSet == null) {
        super.close(); // Shouldn't ever happen?
    } else {
        linkedFilteredCursorSet.remove(this);
        if (linkedFilteredCursorSet.isEmpty()) {
            super.close();
        }
    }

    if (masterCursor.isClosed()) {
        sMasterCursorMap.remove(masterCursor);
    }
}

private void attachToMasterCursor() {
    Cursor masterCursor = getMasterCursor();
    Set<FilteredCursor> filteredCursorSet = sMasterCursorMap.get(masterCursor);
    if (filteredCursorSet == null) {
        filteredCursorSet = Collections.synchronizedSet(new HashSet<FilteredCursor>());
        sMasterCursorMap.put(masterCursor, filteredCursorSet);
    }
    filteredCursorSet.add(this);
}

/** Returns the first non-CursorWrapper instance contained within this object. */
public Cursor getMasterCursor() {
    Cursor cursor = mCursor;

    while (cursor instanceof CursorWrapper) {
        cursor = ((CursorWrapper) cursor).getWrappedCursor();
    }

    return cursor;
}

/** Returns the first FilteredCursor wrapped by the provided cursor or null if no FilteredCursor is found. */
public static FilteredCursor unwrapFilteredCursor(Cursor cursor) {
    while (cursor instanceof CursorWrapper) {
        if (cursor instanceof FilteredCursor) {
            return (FilteredCursor)cursor;
        } else {
            cursor = ((CursorWrapper) cursor).getWrappedCursor();
        }
    }

    return null;
}

内容提供商删除

public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
    SQLiteDatabase db = getWritableDb();

    int result = 0;
    switch (sUriMatcher.match(uri)) {
        case EVENT_LIST:
            result = db.delete(EventDataContract.TABLE_NAME, selection, selectionArgs);
            getContext().getContentResolver().notifyChange(uri, null);
            break;
        default:
            throw new IllegalArgumentException("Unsupported URI: " + uri);
    }

    return result;
}

光标加载器

public class EventsCurorLoader extends CursorLoader {

public EventsCurorLoader(Context context) {
    super(context, EventData.CONTENT_URI, EventData.PROJECTION_ALL, null, null, EventDataContract.EventDataColumns.TIMESTAMP + " DESC");
}

@Override
protected Cursor onLoadInBackground() {
    Cursor c = super.onLoadInBackground();

    // Filter the cursor using user preferences
    final Set<String> filter = GeneralSettings.getInstance(getContext()).getEventsFilter();

    return FilteredCursorFactory.createUsingSelector(c, new FilteredCursorFactory.Selector() {
        @Override
        public boolean select(Cursor cursor) {
            String type = cursor.getString(EventData.TYPE_COLUMN_ID);
            return filter.contains(type);
        }
    });
}

}

找到问题了。我使用了错误的 CursorAdapter 构造函数。 这是正确的:

public EventsAdapter(Context context) {
        super(context, null, 0);

    }