我正在构建一个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);
我的问题是以前有没有人经历过这个问题,如果有的话,你找到了解决办法吗?或者我的实施完全错了?
答案 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!
}
}