在UI线程上运行的SQLite查询,配合ExpandableListView/SimpleCursorTreeAdapter使用。

3
我正在开发一款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) ...
    }
}

使用这种设置,所有内容都按预期加载。然而,在加载列表内容时,UI会随机挂起/卡顿。通常不足以导致ANR,但仍然非常明显和令人恼火!
为了调试此问题,我启用了android.os.StrictMode(非常好的新功能/工具!),并启动了模拟器(在Android 2.3上)。当列表内容被加载时,我得到以下StrictMode输出:
严格模式违规; ~持续时间=1522毫秒:android.os.StrictMode $ StrictModeDiskReadViolation: policy = 23 violation = 2 在android.os.StrictMode $ AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:745)处 在android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1345)处 在android.database.sqlite.SQLiteQueryBuilder.query(SQLiteQueryBuilder.java:330)处 在com.mycompany.myapp.provider.RSSFeedProvider.query(RSSFeedProvider.java:128)处 在android.content.ContentProvider $ Transport.query(ContentProvider.java:187)处 在android.content.ContentResolver.query(ContentResolver.java:262)处 在android.app.Activity.managedQuery(Activity.java:1550)处 在com.mycompany.myapp.activity.MultipleFeedsActivity $ RssFeedLatestListAdapter.getChildrenCursor(MultipleFeedsActivity.java:388)处 在android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106)处 在android.widget.CursorTreeAdapter.getChildrenCount(CursorTreeAdapter.java:178)处 在android.widget.ExpandableListConnector.refreshExpGroupMetadataList(ExpandableListConnector.java:561)处 在android.widget.ExpandableListConnector.expandGroup(ExpandableListConnector.java:682)处 在android.widget.ExpandableListConnector.expandGroup(ExpandableListConnector.java:636)处 在android.widget.ExpandableListView.expandGroup(ExpandableListView.java:608)处 在com.mycompany.myapp.activity.MultipleFeedsActivity.onReceiveResult(MultipleFeedsActivity.java:335)处 在com.mycompany.myapp.service.FeedResultReceiver.onReceiveResult(FeedResultReceiver.java:40)处 在android.os.ResultReceiver$MyRunnable.run(ResultReceiver.java:43)处 在android.os.Handler.handleCallback(Handler.java:587)处 在android.os.Handler.dispatchMessage(Handler.java:92)处 在android.os.Looper.loop(Looper.java:123)处 在android.app.ActivityThread.main(ActivityThread.java:3647)处 在java.lang.reflect.Method.invokeNative(Native Method)处 在java.lang.reflect.Method.invoke(Method.java:507)处 在com.android.internal.os.ZygoteInit $ MethodAndArgsCaller.run(ZygoteInit.java:839)处 在com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)处 在dalvik.system.NativeStart.main(Native Method)处
这看起来很合理!在 SimpleCursorTreeAdapter.getChildrenCursor() 中,内容提供者在UI线程上被查询,这当然是一个坏主意,肯定会挂起UI!
我查看了CursorTreeAdapterJavaDoc(API级别4),对于getChildrenCursor(),它说:
“如果要异步查询提供程序以防止阻塞UI,则可以返回null,并在稍后调用setChildrenCursor(int,Cursor)。”
太好了!让我们这样做!我更改了getChildrenCursor()的实现,启动一个新任务负责执行查询并在适配器上设置子游标。 在启动任务之后,在getChildrenCursor()中只需返回null:
@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的源代码告诉我,它无法处理异步从getChildrenCursor()返回的null。他们似乎在Android 2.3中解决了这个问题,但是在早期版本的Android中该怎么办呢?我真的需要编写自己的CursorTreeAdapter实现才能异步刷新子游标吗?
有人遇到过同样的问题,并且可能找到了(可行的)解决方法吗?如果没有,有人可以给我提示怎么继续吗?我真的非常感激任何帮助!
提前感谢!
问候, Jacob

我正在这里做类似的事情 https://dev59.com/Juo6XIcBkEYKwwoYQiO7 - Etienne Lawlor
2个回答

2
如果他们在2.3 SimpleCursorTreeAdapter中修复了它,为什么不直接下载java文件并将其包含在您的项目中,然后使用该版本而不是手机上的版本?
我还没有查看代码,但只要他们没有进行任何新的2.3 API调用,它应该可以工作。

只是想注意,我需要类似的功能,但我已经在使用继承自CursorTreeAdapter的自定义适配器,而不是使用SimpleCursorTreeAdapter。我的代码从getChildrenCursor()返回null,在我的2.0模拟器和Galaxy Tab(2.2)模拟器上运行良好。因此,也许创建一个自定义适配器是解决方案。 - mp2526

2

感谢您的反馈,mp2526!我很抱歉回复晚了!

这是一个很好的建议!然而,我真的认为在API 8中应该可以做到这一点(异步检索子游标),并且我可能误解了什么...显然,这是不可能的(至少不使用SimpleCursorTreeAdapter)!

无论如何,我对SimpleCursorTreeAdapter进行了API 8和API 9之间的差异比较,Android 2.3所做的就是惰性初始化childFromgroupFrom列ID,即不再在构造函数中初始化。相反,这些成员现在在第一次调用bindView()时初始化。这样,现在有时间异步检索子游标。由于在API 4和API 9之间似乎没有更改此类的公共API(除了新方法setViewText()),因此您使用此类的API版本9的解决方案应该可以正常工作!我尝试过了,它确实可以!

非常感谢您,mp2526!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接