Android 通过CursorAdapter和CursorLoader实现SearchView。swapCursor正在给AsaledataException

Android 通过CursorAdapter和CursorLoader实现SearchView。swapCursor正在给AsaledataException,android,sqlite,searchview,android-cursoradapter,android-cursorloader,Android,Sqlite,Searchview,Android Cursoradapter,Android Cursorloader,我试图在片段中实现SearchView,通过CursorLoader直接从sqlite查询搜索结果,并通过自定义CursorAdapter在同一片段中呈现搜索结果。搜索建议也可以直接从sqlite查询,并通过自定义游标适配器(CountrySuggestionsAdapter)呈现。 因此,我有一个用于获取建议的游标加载程序(加载程序id=0),另一个用于获取搜索结果的游标加载程序(加载程序id=1)。目前装载建议存在3个问题: 1) 键入第一个字母不会显示任何建议,即它不会调用自定义游标适配器

我试图在片段中实现SearchView,通过CursorLoader直接从sqlite查询搜索结果,并通过自定义CursorAdapter在同一片段中呈现搜索结果。搜索建议也可以直接从sqlite查询,并通过自定义游标适配器(CountrySuggestionsAdapter)呈现。 因此,我有一个用于获取建议的游标加载程序(加载程序id=0),另一个用于获取搜索结果的游标加载程序(加载程序id=1)。目前装载建议存在3个问题:

1) 键入第一个字母不会显示任何建议,即它不会调用自定义游标适配器的bindView。它只在第二次输入后才开始显示建议

2) 如果我输入“Un”,它将给出建议,如果我输入“Ur”,它将给出此错误

android.database.StaleDataException:尝试访问已关闭的 光标窗口。最可能的原因:光标在 调用此方法。 位于android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:139) 位于android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:74) 位于android.support.v4.widget.CursorAdapter.getItemId(CursorAdapter.java:226) 位于android.widget.AutoCompleteTextView.buildImeCompletions(AutoCompleteTextView.java:1132) 在android.widget.AutoCompleteTextView.showDropDown(AutoCompleteTextView.java:1091)中 在android.widget.AutoCompleteTextView.updateDropDownForFilter(AutoCompleteTextView.java:974)中 位于android.widget.AutoCompleteTextView.onFilterComplete(AutoCompleteTextView.java:956) 在android.widget.Filter$ResultsHandler.handleMessage(Filter.java:285)中 位于android.os.Handler.dispatchMessage(Handler.java:102) 位于android.os.Looper.loop(Looper.java:135) 位于android.app.ActivityThread.main(ActivityThread.java:5294) 位于java.lang.reflect.Method.invoke(本机方法) 位于java.lang.reflect.Method.invoke(Method.java:372) 在com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run上(ZygoteInit.java:904) 位于com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699)

3) 首先,我输入“Un”,它给出建议,我点击第一个建议,它成功地根据它呈现给我一个列表。然后,我在搜索视图中另外键入第三个字母,例如“Uni”,它会崩溃,给我这个字母:

java.lang.IllegalStateException:尝试重新打开已关闭的 对象:SQLiteQuery:从中选择_id、countryNameRu、countryId countryNameRu喜欢“Un%”的国家 在android.database.sqlite.SQLiteClosable.AcquisiteReference(SQLiteClosable.java:55)中 位于android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:58) 位于android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:152) 位于android.database.sqlite.SQLiteCursor.onMove(SQLiteCursor.java:124) 位于android.database.AbstractCursor.moveToPosition(AbstractCursor.java:214) 位于android.support.v4.widget.CursorAdapter.getItemId(CursorAdapter.java:225) 在android.widget.AdapterView.rememberSyncState(AdapterView.java:1226) 位于android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:820) 在android.widget.AbsListView$AdapterDataSetObserver.onChanged(AbsListView.java:6156)中 位于android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37) 位于android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50) 位于android.support.v4.widget.CursorAdapter.swapCursor(CursorAdapter.java:347) 位于android.support.v4.widget.CursorAdapter.changeCursor(CursorAdapter.java:315) 位于android.support.v4.widget.CursorFilter.publishResults(CursorFilter.java:68) 在android.widget.Filter$ResultsHandler.handleMessage(Filter.java:282)中 位于android.os.Handler.dispatchMessage(Handler.java:102) 位于android.os.Looper.loop(Looper.java:135) 位于android.app.ActivityThread.main(ActivityThread.java:5294) 位于java.lang.reflect.Method.invoke(本机方法) 位于java.lang.reflect.Method.invoke(Method.java:372) 在com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run上(ZygoteInit.java:904) 位于com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699)

我已经在互联网上寻找解决方案好几天了,现在我有点绝望了。我曾考虑过实现ContentProvider,但它会有什么帮助?它是绝对必要的吗

片段:

import android.database.Cursor;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.widget.SearchView;


import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Callback;

public class RoamingTariffs extends ProgressListFragment implements  LoaderManager.LoaderCallbacks<Cursor>{
    private RoamingTariffSearchResultsAdapter roamingTariffAdapter;
    private CountrySuggestionsAdaptor mSearchViewAdapter;
    RoamingTariffDbHelper dbHelper;

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.roaming_toolbar, menu);
        mSearchViewAdapter = new CountriesSearchResultsAdaptor(this.getActivity(),null);
        SearchView searchView  = (SearchView) menu.findItem(R.id.menu_search).getActionView();

        searchView.setSuggestionsAdapter(mSearchViewAdapter);
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener(){

            @Override
            public boolean onQueryTextSubmit(String s) {
                if(!s.isEmpty())
                  loadSuggestions(s,false);
                return true;
            }

            @Override
            public boolean onQueryTextChange(String s) {
                if(!s.isEmpty())
                    loadSuggestions(s,false);
                return true;
            }
        });

        searchView.setOnSuggestionListener(
                new SearchView.OnSuggestionListener(){

                    @Override
                    public boolean onSuggestionSelect(int position) {
                        Cursor cursor = (Cursor) mSearchViewAdapter.getItem(position);
                        String countryId1 = cursor.getString(1);
                        loadCountryDetails(countryId1);
                        return true;
                    }

                    @Override
                    public boolean onSuggestionClick(int position) {
                        Cursor cursor = (Cursor) mSearchViewAdapter.getItem(position);
                        String countryId1 = cursor.getString(1);
                        loadCountryDetails(countryId1);
                        return false;
                    }
                }
        );
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setHasOptionsMenu(true);

        roamingTariffAdapter = new RoamingTariffCursorAdapter(getActivity(), null);
        getListView().setAdapter(roamingTariffAdapter);

        dbHelper = new RoamingTariffDbHelper(getActivity());

        ((LoginActivity) getActivity()).getSupportActionBar().setTitle("Roaming Tariffs");
   RoamingTariffInterface roamingTariffService =
                RoamingTariffClient.getClient().create(RoamingTariffInterface.class);
        Call<List<CountryDto>> call = roamingTariffService.getCountries(countryId);
        call.enqueue(new Callback<List<CountryDto>>() {
            @Override
            public void onResponse(Call<List<CountryDto>> call, Response<List<CountryDto>> response) {
                List<CountryDto> countryList = response.body();
                if (countryList == null)
                    countryList = new ArrayList<CountryDto>();

                // CREATE SQLITE TABLE AND SAVE
                RoamingTariffDbHelper helper = new RoamingTariffDbHelper(getActivity());

                for (CountryDto countryDto : countryList) {
                    helper.addCountryEntry(countryDto);
                }

            }

            @Override
            public void onFailure(Call<List<CountryDto>> call, Throwable t) {
                showError();
            }
        });
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        showProgress();
        CursorLoader cursorLoader = null;
        switch (id){
            case 0://loadSuggestions
                final String queryText = args.getString("queryText");
                cursorLoader =  new CursorLoader(getActivity()) {
                    @Override
                    public Cursor loadInBackground() {
                        Cursor cursor =  dbHelper.getCountryNamesBySearchLetters(queryText);
                        return cursor;
                    }
                };
                break;
            case 1://load results
                final String countryId = args.getString("countryId");
                cursorLoader =  new CursorLoader(getActivity()) {
                    @Override
                    public Cursor loadInBackground() {
                        Cursor cursor =  dbHelper.getOperatorsByCountryId(countryId);
                        return cursor;
                    }
                };
                break;
        }
        return cursorLoader;
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        int loaderId = loader.getId();
        switch(loaderId){
            case 0://suggestions are ready to show
                if (data != null)
                    mSearchViewAdapter.swapCursor(data);
                break;
            case 1://search results are ready to show
                if(data !=null){  
                roamingTariffAdapter.swapCursor(data);}
                break;
        }
        showContent();
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        mSearchViewAdapter.swapCursor(null);
        roamingTariffAdapter.swapCursor(null);
    }

    public void loadSuggestions(String query){
        Bundle bundle = new Bundle();
        bundle.putString("queryText", query);
        getLoaderManager().restartLoader(0, bundle, this);
    }

    public void loadCountryDetails(String countryId){
        Bundle bundle = new Bundle();
        bundle.putString("countryId", countryId);
        getLoaderManager().restartLoader(1, bundle, this);
    }
...
}
RoamingTariffDbHelper的相关查询方法:

public Cursor getCountryNamesBySearchLetters(String startingStr){
        SQLiteDatabase db = this.getReadableDatabase();
        String query = RoamingTariffDbContract.SQL_SELECT_COUNTRIES_START_WITH+
                Helper.firstLetterToCapital(startingStr)+"%'";
        Cursor cursor = db.rawQuery(query,null);
        if(cursor == null){
            return null;
        }
        else if(!cursor.moveToFirst()) {
            cursor.close();
            return null;
        }
        return cursor;
    }
编辑:我遵照@pskink的建议删除了querytextlisteners并添加了它

 FilterQueryProvider fqp =new FilterQueryProvider() {
            @Override
            public Cursor runQuery(CharSequence constraint) {
                Cursor cursor = null;
                if(constraint.length()!=0)
                   cursor = dbHelper.getCountryNamesBySearchLetters(constraint.toString());
              return cursor;
            }
        };

问题2和3不见了。问题1依然存在。我在runQuery内部调试过,由于某种原因,第一个类型的约束为null,因此没有任何建议。虽然约束应该是我键入的第一个字母。原因是什么?

问题是光标已关闭。检查您的代码以确定确切的点我添加了另一个问题,您可以查看它吗请向Helper提供调试结果firstLetterToCapital(startingStr)或粘贴代码Yes,您应该在
CursorAdapter
上使用自定义
ContentProvider
和设置
FilterQueryProvider
而不是自定义
CursorLoader
@Ramit,这没什么。只是一个大写的公共静态字符串firstletttocapital(字符串区域设置){return Character.toUpperCase(locale.charAt(0))+locale.substring(1);}问题已经解决。检查您的代码以确定确切的点我添加了另一个问题,您能看一下吗
 FilterQueryProvider fqp =new FilterQueryProvider() {
            @Override
            public Cursor runQuery(CharSequence constraint) {
                Cursor cursor = null;
                if(constraint.length()!=0)
                   cursor = dbHelper.getCountryNamesBySearchLetters(constraint.toString());
              return cursor;
            }
        };