SQLite-запрос выполняется в потоке пользовательского интерфейса с ExpandableListView / SimpleCursorTreeAdapter - PullRequest
3 голосов
/ 12 января 2011

Я занимаюсь разработкой приложения для Android для отображения нескольких RSS-каналов (да, я знаю, что таких приложений уже много). Данные для отображения поддерживаются поставщиком контента, и я хочу быть обратно совместимым с API уровня 4.

Я использую ExpandableListView для отображения содержимого трех различных RSS-каналов. Адаптер ExpandableListView реализован как подкласс SimpleCursorTreeAdapter:

private class RssFeedLatestListAdapter extends SimpleCursorTreeAdapter {

    public static final String FEED_NAME_COLUMN = "feedName";

    public RssFeedLatestListAdapter(Context ctx, Cursor groupCursor, int groupLayout,
            String[] groupFrom, int[] groupTo, int childLayout, String[] childFrom,
            int[] childTo) {
        super(ctx, groupCursor, groupLayout, groupLayout, groupFrom, groupTo, childLayout, childFrom, childTo);
    }

    @Override
    protected Cursor getChildrenCursor(final Cursor groupCursor) {
        final String feedName = groupCursor.getString(groupCursor.getColumnIndex(FEED_NAME_COLUMN));
        return managedQuery(LATEST_URI, null,
                FeedItemColumns.CATEGORY + " = ? AND " + FeedItemColumns.FEED + " = ?",
                new String[] { mCategory.getId(), feedName }, null);
    }

    @Override
    protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
        super.bindGroupView(view, context, cursor, isExpanded);

        // Bind group view (impl details not important) ...
    }

    @Override
    protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
        super.bindChildView(view, context, cursor, isLastChild);

        // Bind child view (impl details not important) ...
    }
}

При этой настройке весь контент загружается как положено. Однако пользовательский интерфейс зависает / заикается случайным образом во время загрузки содержимого списка. Обычно не достаточно, чтобы получить ANR, но все же заметно и очень раздражает!

Чтобы устранить эту проблему, я включил android.os.StrictMode (отличная новая функция / инструмент, кстати!) И запустил эмулятор (на Android 2.3). Когда содержимое списка загружено, я получаю следующий вывод StrictMode:

D/StrictMode(  395): StrictMode policy violation; ~duration=1522 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=23 violation=2
D/StrictMode(  395):  at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:745)
D/StrictMode(  395):  at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1345)
D/StrictMode(  395):  at android.database.sqlite.SQLiteQueryBuilder.query(SQLiteQueryBuilder.java:330)
D/StrictMode(  395):  at com.mycompany.myapp.provider.RSSFeedProvider.query(RSSFeedProvider.java:128)
D/StrictMode(  395):  at android.content.ContentProvider$Transport.query(ContentProvider.java:187)
D/StrictMode(  395):  at android.content.ContentResolver.query(ContentResolver.java:262)
D/StrictMode(  395):  at android.app.Activity.managedQuery(Activity.java:1550)
D/StrictMode(  395):  at com.mycompany.myapp.activity.MultipleFeedsActivity$RssFeedLatestListAdapter.getChildrenCursor(MultipleFeedsActivity.java:388)
D/StrictMode(  395):  at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106)
D/StrictMode(  395):  at android.widget.CursorTreeAdapter.getChildrenCount(CursorTreeAdapter.java:178)
D/StrictMode(  395):  at android.widget.ExpandableListConnector.refreshExpGroupMetadataList(ExpandableListConnector.java:561)
D/StrictMode(  395):  at android.widget.ExpandableListConnector.expandGroup(ExpandableListConnector.java:682)
D/StrictMode(  395):  at android.widget.ExpandableListConnector.expandGroup(ExpandableListConnector.java:636)
D/StrictMode(  395):  at android.widget.ExpandableListView.expandGroup(ExpandableListView.java:608)
D/StrictMode(  395):  at com.mycompany.myapp.activity.MultipleFeedsActivity.onReceiveResult(MultipleFeedsActivity.java:335)
D/StrictMode(  395):  at com.mycompany.myapp.service.FeedResultReceiver.onReceiveResult(FeedResultReceiver.java:40)
D/StrictMode(  395):  at android.os.ResultReceiver$MyRunnable.run(ResultReceiver.java:43)
D/StrictMode(  395):  at android.os.Handler.handleCallback(Handler.java:587)
D/StrictMode(  395):  at android.os.Handler.dispatchMessage(Handler.java:92)
D/StrictMode(  395):  at android.os.Looper.loop(Looper.java:123)
D/StrictMode(  395):  at android.app.ActivityThread.main(ActivityThread.java:3647)
D/StrictMode(  395):  at java.lang.reflect.Method.invokeNative(Native Method)
D/StrictMode(  395):  at java.lang.reflect.Method.invoke(Method.java:507)
D/StrictMode(  395):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
D/StrictMode(  395):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
D/StrictMode(  395):  at dalvik.system.NativeStart.main(Native Method)

Это кажется разумным! В SimpleCursorTreeAdapter.getChildrenCursor() поставщик контента запрашивается в потоке пользовательского интерфейса, что, конечно, является плохой идеей, которая наверняка повесит пользовательский интерфейс!

Я посмотрел на JavaDoc (уровень API 4) CursorTreeAdapter (суперкласс SimpleCursorTreeAdapter) и для getChildrenCursor() написано:

"Если вы хотите выполнить асинхронный запрос к поставщику, чтобы предотвратить блокировку пользовательского интерфейса, можно вернуть значение null, а позднее вызвать setChildrenCursor (int, Cursor)." .

Отлично! Давайте сделаем это! Я изменил свою реализацию getChildrenCursor(), чтобы запустить новую задачу, отвечающую за выполнение запроса и установку дочернего курсора на адаптере. После запуска задания я просто возвращаю ноль в getChildrenCursor():

@Override
protected Cursor getChildrenCursor(final Cursor groupCursor) {
    final String feedName = groupCursor.getString(groupCursor.getColumnIndex(FEED_NAME_COLUMN));
    new RefreshChildrenCursorTask(groupCursor.getPosition()).execute(feedName);
    return null;
}

, где RefreshChildrenCursorTask реализован как:

private class RefreshChildrenCursorTask extends AsyncTask<String, Void, Cursor> {

    private int mGroupPosition;

    public RefreshChildrenCursorTask(int groupPosition) {
        this.mGroupPosition = groupPosition;
    }

    @Override
    protected Cursor doInBackground(String... params) {
        String feedName = params[0];
        return managedQuery(LATEST_URI, null,
                FeedItemColumns.CATEGORY + " = ? AND " + FeedItemColumns.FEED + " = ?",
                new String[] { mCategory.getId(), feedName }, null);
        }

        @Override
        protected void onPostExecute(Cursor childrenCursor) {
            mLatestListAdapter.setChildrenCursor(mGroupPosition, childrenCursor);
        }
    }
}

Я переустановил приложение на эмуляторе 2.3 и запустил его. Это работает как шарм, пока пока неотзывчивый пользовательский интерфейс! Но радость длилась недолго ... Следующее, что нужно было сделать, это запустить тот же код на целевом устройстве (в данном случае Samsung Galaxy S под управлением Android 2.2). Затем я получил следующее Exception во время загрузки содержимого списка рассылки:

E/AndroidRuntime(23191): FATAL EXCEPTION: main
E/AndroidRuntime(23191): java.lang.NullPointerException
E/AndroidRuntime(23191):  at android.widget.SimpleCursorTreeAdapter.initFromColumns(SimpleCursorTreeAdapter.java:194)
E/AndroidRuntime(23191):  at android.widget.SimpleCursorTreeAdapter.initChildrenFromColumns(SimpleCursorTreeAdapter.java:205)
E/AndroidRuntime(23191):  at android.widget.SimpleCursorTreeAdapter.init(SimpleCursorTreeAdapter.java:186)
E/AndroidRuntime(23191):  at android.widget.SimpleCursorTreeAdapter.(SimpleCursorTreeAdapter.java:136)
E/AndroidRuntime(23191):  at com.mycompany.myapp.activity.MultipleFeedsActivity$RssFeedLatestListAdapter.(MultipleFeedsActivity.java:378)
E/AndroidRuntime(23191):  at com.mycompany.myapp.activity.MultipleFeedsActivity$2.run(MultipleFeedsActivity.java:150)
E/AndroidRuntime(23191):  at android.os.Handler.handleCallback(Handler.java:587)
E/AndroidRuntime(23191):  at android.os.Handler.dispatchMessage(Handler.java:92)
E/AndroidRuntime(23191):  at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime(23191):  at android.app.ActivityThread.main(ActivityThread.java:4627)
E/AndroidRuntime(23191):  at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(23191):  at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime(23191):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:871)
E/AndroidRuntime(23191):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:629)
E/AndroidRuntime(23191):  at dalvik.system.NativeStart.main(Native Method)

Беглый взгляд на исходный код SimpleCursorTreeAdapter говорит мне, что нет способа справиться с нулевым возвратом асинхронно из getChildrenCursor(). Кажется, они решили эту проблему в Android 2.3, но как вы должны обойти это в более ранних версиях Android? Действительно ли мне нужно написать собственную реализацию CursorTreeAdapter, чтобы иметь возможность асинхронно обновлять дочерний курсор?

Кто-нибудь сталкивался с такой же проблемой и, возможно, даже нашел (возможный) обходной путь? Если нет, может кто-нибудь дать мне подсказку о том, как поступить !? Я был бы очень признателен за любую помощь Я могу получить!

Заранее спасибо!

С уважением, Jacob

Ответы [ 2 ]

2 голосов
/ 26 января 2011

Спасибо за ваш отзыв, mp2526!Я прошу прощения за мой поздний ответ!

Это отличное предложение!Тем не менее, я действительно думал, что это можно сделать (асинхронно извлекать дочерние курсоры) в API 8, и что я что-то неправильно понял ... По-видимому, это невозможно (по крайней мере, с использованием * 1003).*)!

В любом случае, я провел различие между API 8 и API 9 для SimpleCursorTreeAdapter, и в Android 2.3 они сделали то, что идентификаторы столбцов childFrom и groupFrom теперь инициализируются ленивот.е. больше не в конструкторе.Вместо этого эти члены теперь инициализируются при первом вызове bindView().Таким образом, теперь пришло время асинхронно извлекать дочерние курсоры.Поскольку между API 4 и API 9 изменений в общедоступном API этого класса, по-видимому, нет (за исключением нового метода setViewText()), ваше решение использовать версию API 9 этого класса должно работать без изменений!Я попробовал это, и это сделал!

Большое спасибо, mp2526!

2 голосов
/ 21 января 2011

Если они исправили это в 2.3 SimpleCursorTreeAdapter, почему бы вам просто не скачать java-файл и не включить его в свой проект и не использовать эту версию вместо телефонной?посмотрел на код, но пока они не делали никаких новых вызовов API 2.3, он должен работать.

...