我正在开发一个用于显示大量 RSS 源的 Android 应用程序(是的,我知道已经有很多这样的应用程序)。要显示的数据由内容提供商支持,我希望向后兼容 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) ...
}
}
通过此设置,所有内容都会按预期加载。然而,UI 挂起/卡顿在加载列表内容期间随机。通常不足以获得 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()
,在 UI 线程上查询内容提供者,这当然是一个坏主意,肯定会挂起 UI!
我看了一下JavaDoc http://developer.android.com/reference/android/widget/CursorTreeAdapter.html#getChildrenCursor%28android.database.Cursor%29(API 级别 4)CursorTreeAdapter
(超类SimpleCursorTreeAdapter
)和对于getChildrenCursor()
它说:
“如果您想异步查询提供程序以防止阻塞 UI,可以返回 null 并稍后调用 setChildrenCursor(int, Cursor)。”.
伟大的!让我们这样做吧!我改变了我的实施getChildrenCursor()
启动一个新任务,负责执行查询并在适配器上设置子游标。开始任务后,我只是返回 nullgetChildrenCursor()
:
@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 模拟器上重新安装了该应用程序并启动了它。它的工作方式就像一个魅力,再见无响应的用户界面!但喜悦并没有持续多久……接下来要做的就是在目标设备上运行相同的代码(在本例中是运行 Android 2.2 的三星 Galaxy S)。然后我得到以下内容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
告诉我它无法处理从异步返回 nullgetChildrenCursor()
。他们似乎在 Android 2.3 中解决了这个问题,但是在早期版本的 Android 中你应该如何解决这个问题呢?我真的必须编写自己的实现吗CursorTreeAdapter
能够异步刷新子光标?
有没有人遇到过同样的问题,甚至找到了(可行的)解决方法?如果没有,有人能给我提示如何继续吗?我真的很感激任何帮助我可以得到!
提前致谢!
问候,
雅各布