使用CursorLoader从ContentProvider加载数据时出错

时间:2014-01-07 12:27:45

标签: android android-contentprovider android-loadermanager

我正在构建一个Android应用程序,该应用程序使用OSM数据为来自一组给定位置的用户提供路径。用户可以键入他们希望进入SearchView的位置,并且当用户键入搜索结果时,搜索结果将被缩小以缩小结果,然后他们可以从下拉ListView中选择目标。使用onQueryTextChange()完成此过滤。我使用ContentProvider从数据库查询此数据并实现LoaderManager.LoaderCallbacks接口以重新查询ContentProvider并为要使用的适配器提供新数据。

所有这些都在绝大部分时间内完成,并且完全符合预期。但是,很少,应用程序将崩溃与以下堆栈跟踪。

java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteQuery: SELECT _id, suggest_text_1, suggest_intent_data FROM Locations WHERE (suggest_text_1 LIKE ?)
    at android.database.sqlite.SQLiteClosable.acquireReference(SQLiteClosable.java:55)
    at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:58)
    at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:152)
    at android.database.sqlite.SQLiteCursor.onMove(SQLiteCursor.java:124)
    at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:214)
    at android.database.CursorWrapper.moveToPosition(CursorWrapper.java:162)
    at android.support.v4.widget.CursorAdapter.getItemId(CursorAdapter.java:225)
    at android.widget.AdapterView.rememberSyncState(AdapterView.java:1195)
    at android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:811)
    at android.widget.AbsListView$AdapterDataSetObserver.onChanged(AbsListView.java:6280)
    at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
    at android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50)
    at android.support.v4.widget.CursorAdapter.swapCursor(CursorAdapter.java:347)
    at android.support.v4.widget.SimpleCursorAdapter.swapCursor(SimpleCursorAdapter.java:326)
    at android.support.v4.widget.CursorAdapter.changeCursor(CursorAdapter.java:315)
    at android.support.v4.widget.CursorFilter.publishResults(CursorFilter.java:68)
    at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:282)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:136)
    at android.app.ActivityThread.main(ActivityThread.java:5017)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
    at dalvik.system.NativeStart.main(Native Method)

通常当我清除SearchView以键入其他搜索时会发生此错误,而其他时候会在用户键入时发生此错误。

编辑:包含部分ContentProvider,省略插入,删除和更新。

public class SearchContentProvider extends ContentProvider {
    private DbHelper helper;

    private static final String AUTH = "com.dgh1.Navigation.SearchContentProvider";
    private static final String LOCATIONS_PATH = "Location";
    private static final String GEOFENCES_PATH = "Fences";
    private static final String PEOPLE_PATH = "People";
    public static final Uri LOCATION_URI = Uri.parse("content://" + AUTH + "/" +     LOCATIONS_PATH);
    public static final Uri GEOFENCE_URI = Uri.parse("content://" + AUTH + "/" + GEOFENCES_PATH);
    public static final Uri PEOPLE_URI = Uri.parse("content://" + AUTH + "/" +   PEOPLE_PATH);

    private static final int LOCATIONS = 10;
    private static final int NAME_LOCATION = 11;
    private static final int NODE_LOCATION = 20;
    private static final int GEOFENCES = 30;
    private static final int MARKERS = 40;
    private static final int GEOFENCE = 50;
    private static final int PEOPLE = 60;
    private static final int PERSON = 70;

    private static final String URI_ERROR = "Unknown URI: ";

    private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        matcher.addURI(AUTH, LOCATIONS_PATH, LOCATIONS);
        matcher.addURI(AUTH, GEOFENCES_PATH, GEOFENCES);
        matcher.addURI(AUTH, PEOPLE_PATH, PEOPLE);
        matcher.addURI(AUTH, LOCATIONS_PATH + "/#", NODE_LOCATION);
        matcher.addURI(AUTH, GEOFENCES_PATH + "/#", GEOFENCE);
        matcher.addURI(AUTH, PEOPLE_PATH + "/#", PERSON);
        matcher.addURI(AUTH, LOCATIONS_PATH + "/*", NAME_LOCATION);
    }

    @Override
    public boolean onCreate() {
        helper = new DbHelper(getContext());
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[]  selectionArgs, String sortOrder) {
        Cursor cursor = null;
        int uriType = matcher.match(uri);

        switch (uriType) {
            case LOCATIONS:
                if ( selectionArgs == null ) {
                    cursor = helper.getAllLocations();
                } else {
                    cursor = helper.getSuggestionsData(projection, selection,     selectionArgs);
                }
                break;
            case NODE_LOCATION:
                cursor = helper.getSingleLocationById(uri.getLastPathSegment());
                break;
            case NAME_LOCATION:
                cursor = helper.getSingleLocationByName(selectionArgs[0]);
                break;
            case MARKERS:
                cursor = helper.getAllMarkers();
                break;
            case GEOFENCES:
                cursor = helper.getAllFences();
                break;
            case PEOPLE:
                if ( selectionArgs == null ) {
                    cursor = helper.getAllPeople();
                } else {
                    cursor = helper.findPersonById(selectionArgs[0]);
                }
                break;
            default:
                Log.d(URI_ERROR, uri.toString());
        }
        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }
    .
    .
    .
}

初始化加载程序并设置适配器:

public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.options_menu, menu);

    SearchManager manager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    MenuItem item = menu.findItem(R.id.action_search);
    SearchView view = (SearchView) MenuItemCompat.getActionView(item);
    view.setIconified(false);
    view.setSearchableInfo(
            manager.getSearchableInfo(getComponentName()));
    view.setSuggestionsAdapter(adapter);
    view.setOnQueryTextListener(this);

    getSupportLoaderManager().initLoader(1, null, this);

    return true;
}

LoaderManager.LoaderCallbacks实现:

@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
    return new CursorLoader(this, SearchContentProvider.CONTENT_URI, new String[] { DbHelper.ID, DbHelper.LOCATION_NAME, DbHelper.LOCATION_NODE_ID },
                                                            DbHelper.LOCATION_NAME + " LIKE ?", new String[] { "%" + cursorFilter + "%"}, null );
}

@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
    if ( !(cursor.isClosed()) )
        adapter.swapCursor(cursor);
    }
}

@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
    adapter.swapCursor(null);
}

OnQueryTextchange():

@Override
public boolean onQueryTextChange(String s) {
    cursorFilter = !TextUtils.isEmpty(s) ? s : null;
    getSupportLoaderManager().restartLoader(0, null, this);
    return true;
}

来自DbHelper的声明:

public static final String LOCATION_TABLE = "Locations";
public static final String LOCATION_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1;
public static final String LOCATION_NODE_ID = SearchManager.SUGGEST_COLUMN_INTENT_DATA;

来自onCreate()的声明:

adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, null, new String[] { DbHelper.LOCATION_NAME },
                                            new int[] { android.R.id.text2 }, 0);

我的问题是以前有没有人经历过这个问题,如果有的话,你找到了解决办法吗?或者我的实施完全错了?

1 个答案:

答案 0 :(得分:5)

这是我在AutocompleteTextView中为加载器工作所做的事情。您基本上禁用了AutocompleteTextView自己的过滤机制,而是使用加载器API。以下示例使用Activity。如果使用片段,请根据需要进行调整。

String mCurrentFilter = "";

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...
    mACTextView = new NonFilterableAutoCompleteTextView(this);
    // Start with a null cursor since data is not ready yet
    mAdapter = new CursorAdapter(this, null, 0){...};
    mACTextView.setAdapter(mAdapter);
    mACTextView.addTextChangedListener(new TextWatcher() {

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

        }

        @Override
        public void afterTextChanged(Editable s) {

            mCurrentFilter = s.toString();
            getLoaderManager().restartLoader(LOADER_SUGGESTIONS, null, ExampleActivity.this);
        }
    });
    getLoaderManager().initLoader(LOADER_SUGGESTIONS, null, this);
}

@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {

    switch (id) {
        case LOADER_SUGGESTIONS:
            // Use the current filter to perform the query. For simplicity sake, assume an empty filter ("") will return all records.
            return new CursorLoader(getActivity(), Uri.parse("Whatever your content provider URI is " + mCurrentFilter)), null, null, null, null);

        ...
    }

    return null;
}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

    switch (loader.getId()) {
        case LOADER_SUGGESTIONS:
            mAdapter.swapCursor(data);
            break;
        ...
    }
}

/**
 * An AutoCompleteTextView which does not perform any background filtering. This class will
 * not perform any filtering and is intended to be used with CursorLoaders and CursorAdapters and
 * have the cursor in the adapter swapped when the loader has new data.
 * <p/>
 * This is required since using the standard AutoCompleteTextView with CursorLoaders and swapCursor
 * causes races conditions with the widget's own filtering happening in the background. The default filtering mechanism
 * will run on a background thread with an instance of the old cursor.
 *
 * @author AngraX
 */
public static class NonFilterableAutoCompleteTextView extends AutoCompleteTextView {

    public NonFilterableAutoCompleteTextView(Context context) {
        super(context);
    }

    public NonFilterableAutoCompleteTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public NonFilterableAutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void performFiltering(CharSequence text, int keyCode) {
        // I say NO!
    }
}