Фильтрация ListView с помощью CursorLoader И FilterQueryProvider?

В нашем проекте мы много работаем со списками и использовали их в прошлом по следующему «шаблону»:

ListView находится во фрагменте, инициализируется в onActivityCreated, где мы сначала запускаем CursorLoaders, а затем в onFinish swapCusor на ListAdapter. Затем мы реализовали функцию поиска с помощью filterQueryProvider, который просто возвращает курсор с contentResolver.query (...). Если я сделал некоторые изменения ориентации, когда что-то было выбрано в списке, во многих случаях (не регулярно) я получал следующую ошибку:

android.database.StaleDataException: Attempted to access a cursor after it has been closed.
12-05 10:36:59.531: E/ACRA(12079):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1967)
12-05 10:36:59.531: E/ACRA(12079):  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1992)
12-05 10:36:59.531: E/ACRA(12079):  at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3378)
12-05 10:36:59.531: E/ACRA(12079):  at android.app.ActivityThread.access$700(ActivityThread.java:127)
12-05 10:36:59.531: E/ACRA(12079):  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1162)
12-05 10:36:59.531: E/ACRA(12079):  at android.os.Handler.dispatchMessage(Handler.java:99)
12-05 10:36:59.531: E/ACRA(12079):  at android.os.Looper.loop(Looper.java:137)
12-05 10:36:59.531: E/ACRA(12079):  at android.app.ActivityThread.main(ActivityThread.java:4448)
12-05 10:36:59.531: E/ACRA(12079):  at java.lang.reflect.Method.invokeNative(Native Method)
12-05 10:36:59.531: E/ACRA(12079):  at java.lang.reflect.Method.invoke(Method.java:511)
12-05 10:36:59.531: E/ACRA(12079):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:823)
12-05 10:36:59.531: E/ACRA(12079):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:590)
12-05 10:36:59.531: E/ACRA(12079):  at dalvik.system.NativeStart.main(Native Method)
12-05 10:36:59.531: E/ACRA(12079): Caused by: android.database.StaleDataException: Attempted to access a cursor after it has been closed.
12-05 10:36:59.531: E/ACRA(12079):  at android.database.BulkCursorToCursorAdaptor.throwIfCursorIsClosed(BulkCursorToCursorAdaptor.java:75)
12-05 10:36:59.531: E/ACRA(12079):  at android.database.BulkCursorToCursorAdaptor.getColumnNames(BulkCursorToCursorAdaptor.java:170)
12-05 10:36:59.531: E/ACRA(12079):  at android.database.AbstractCursor.getColumnIndex(AbstractCursor.java:248)
12-05 10:36:59.531: E/ACRA(12079):  at android.database.AbstractCursor.getColumnIndexOrThrow(AbstractCursor.java:266)
12-05 10:36:59.531: E/ACRA(12079):  at android.database.CursorWrapper.getColumnIndexOrThrow(CursorWrapper.java:78)
12-05 10:36:59.531: E/ACRA(12079):  at android.support.v4.widget.CursorAdapter.swapCursor(CursorAdapter.java:344)
12-05 10:36:59.531: E/ACRA(12079):  at 

это тот случай, когда используется swapCursor в onLoadFinished после смены ориентации.

Теперь я повторно реализовал функциональность фильтра, используя restartLoader с аргументами и используя Contacts.CONTENT_FILTER_URI с добавленным путем, который является ограничением, а затем заменил этот курсор в onLoadFinished, поэтому я удалил filterQueryProvider, который, похоже, работает нормально.

Возникает вопрос: есть ли возможность (или хорошая практика) изначально использовать CursorLoader И filterQueryProvider? или мне нужно решить? потому что тот же результат, которого я достигаю, просто используя filterQueryProvider, и просто выполняю фильтрацию с ограничением null, которое просто загружает мой требуемый список контактов и затем выполняет фильтрацию.

Какие-нибудь рекомендации? Я не нашел математической информации об этом через гугл;)

это моя текущая реализация LoaderCallbacks, кстати:

 private LoaderManager.LoaderCallbacks<Cursor> phoneBookContactsLoaderCallback = new LoaderManager.LoaderCallbacks<Cursor>() {

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            // if(constraint != null && constraint.length() > 0) {
            // selection = Contacts.DISPLAY_NAME + " LIKE ?";
            // selectionArgs = new String [] {"%" + constraint + "%"};
            // }
            numLoaderManagersRunning++;
            String constraint = null;
            if(args != null){
                constraint = args.getString(CONSTRAINT);
            }

            Uri uri = null;

            if(constraint!= null && !constraint.isEmpty()){
                uri =  Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, constraint);
            } else {
                uri = Contacts.CONTENT_URI;
            }


            return new CursorLoader(getActivity(), uri , PROJECTION_PHONEBOOK_CONTACTS,
                    null, null, Contacts.DISPLAY_NAME + " COLLATE NOCASE ASC");
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            Logger.e(TAG, "Load finished ");
            // phoneBookContactsCursorAdapter.swapCursor(new MatrixCursor(new String [] { Contacts._ID,
            // Contacts.DISPLAY_NAME, Contacts.PHOTO_ID }));
            phoneBookContactsCursorAdapter.swapCursor(data);
            if (actualMultiFilterListener != null){
                actualMultiFilterListener.onFilterComplete(data.getCount());
            }
//filterList("");
            numLoaderManagersRunning--;

            if (numLoaderManagersRunning <= 0) {
                // The list should now be shown.
                if (isResumed()) {
                    setListShown(true);
                } else {
                    setListShownNoAnimation(true);
                }
            }
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
//            phoneBookContactsCursorAdapter.swapCursor(null);
            Logger.e(TAG, "Load resetted ");
        }
    };

person Denny1989    schedule 05.12.2013    source источник
comment
вы закрываете курсор / базу данных вручную? Я читал, что CursorLoader делает это сам по себе, поэтому вам не придется с ним связываться.   -  person Lukasz 'Severiaan' Grela    schedule 05.12.2013
comment
думал, что это проблема, но я нигде этого не делал. Я думаю, что это комбинация курсора загрузчика и unmanagedCursor filterQueryProviders. Есть опыт работы с этим?   -  person Denny1989    schedule 05.12.2013
comment
Я думаю, что одна путаница заключается в том, что при использовании CursorLoader нет необходимости в FilterQueryProvider. Прочтите это полностью, включая onCreateLoader: developer.android.com/guide/ components / loaders.html # restarting. По сути, фильтр применяется во время создания CursorLoader. Таким образом, нет необходимости в FilterQueryProvider, используйте restartLoader при изменении строки фильтра.   -  person lilbyrdie    schedule 21.05.2014


Ответы (2)


Немного поздно, но я столкнулся с той же проблемой, и комментарии не были хорошо объясняющими. Для тех, кто сталкивается с проблемой, вот решение:

«Прекратите использовать FilterQueryProvider. CursorLoader также может заменить это: перезапустите его со своим фильтром, и все будет в порядке». (источник)

Что значит;

вместо этого:

@Override
public Cursor runQuery(CharSequence listId) {
    return mDb.getContacts(listId);  
}// DO NOT set direct database query, has no observation at all.

or

@Override
public Cursor runQuery(CharSequence listId) {
    return getContentManager.query(....,new String[]{listId}...
}// DO NOT return content manager result, forgets observers.

or

@Override
public Cursor runQuery(CharSequence listId) {
    Bundle bundle = new Bundle();
    bundle.putCharSequence("selected", listId);
    getLoaderManager().restartLoader(CURSOR_LOADER_CONTACTS, bundle, MainActivity.this);
    return null;
}// DO NOT restart the cursor loader with new info, screws observers. 

с комбинацией mContactAdapter.getFilter().filter(id+""); (при применении фильтра)

сделай это:

@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    switch (id) {
        case CURSOR_LOADER_CONTACTS:
            long mListId = 0;
            if (args != null) {
                mListId = args.getLong(CONTACT_FILTER);
            }
            return new CursorLoader(this, ContentProvider.CONTACT_DISPLAY_URI, null, ContactTable.COLUMN_GROUP_ID+"=?", new String[]{id+""}, null); 
    } //Apply filter here with new info comes from 'restartLoader'
    return null;
}

с комбинацией getLoaderManager().restartLoader(MainActivity.CURSOR_LOADER_CONTACTS, bundle, MainActivity.this);

при условии, что MainActivity.this реализует LoaderManager.LoaderCallbacks<Cursor>

person guness    schedule 12.01.2015

при использовании FilterQueryProvider используйте restartLoader вместо initLoader

person pskink    schedule 05.12.2013
comment
Вы имеете в виду restartLoader в onActivityCreated, а затем с помощью filterQueryProvider с unmanagedCursor? есть ли возможность использовать CursorLoader в рамках методов фильтра? - person Denny1989; 05.12.2013
comment
да нет. в последнем случае вам не нужен CursorLoader, поскольку runQuery of FQP выполняется в отдельном потоке без пользовательского интерфейса. - person pskink; 05.12.2013
comment
хорошо, так что вы рекомендуете (или говорите, что это допустимая возможность), что я просто использую filterQueryProvider, верно? У меня просто была проблема, что он изначально не заполнил список, и мне пришлось один раз вызвать filter (null) или filter (), что казалось не лучшим решением. Но это действительно технически то же самое, как я предполагаю, список заполняется асинхронно ..? - person Denny1989; 06.12.2013
comment
когда вам пришлось вызывать filter ()? - person pskink; 06.12.2013
comment
чтобы показать все контакты до того, как пользователь нажмет клавишу в поле поиска. потому что filterQueryProvider изначально не доставляет результаты, когда он установлен в список. поэтому список был пуст до того, как пользователь нажал клавишу. но список контактов должен быть виден до ввода, чтобы пользователь мог также прокручивать его. - person Denny1989; 06.12.2013
comment
мне кажется, вы не использовали механизм фильтрации buuldin: textFilterEnabled - person pskink; 06.12.2013
comment
не могли бы вы привести минимальный пример того, как я могу использовать эту функциональность для фильтрации курсора с помощью Contacts.CONTENT_FILTER_URI? - person Denny1989; 09.12.2013
comment
см. ответ Бена Х здесь stackoverflow.com/questions/2002607/ - person pskink; 09.12.2013