Я занимаюсь разработкой приложения для 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