SQLite 查询使用 ExpandableListView/SimpleCursorTreeAdapter 在 UI 线程上运行

发布于 2024-10-11 20:06:28 字数 8338 浏览 8 评论 0原文

我正在开发一个用于显示大量 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!

我查看了 JavaDocCursorTreeAdapter(SimpleCursorTreeAdapter 的超类)的 a>(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)。然后,在加载列表提要内容期间,我遇到了以下异常:

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 实现才能异步刷新子光标吗?

有没有人遇到过同样的问题,甚至找到了(可行的)解决方法?如果没有,有人能给我提示如何继续吗? 我非常感谢我能得到的任何帮助

提前致谢!

问候, 雅各布

I'm in the progress of developing an Android app for displaying a number of RSS feeds (yes, I know there are many apps like this already). The data to be displayed is backed by a content provider, and I want to be backward compatible with API level 4.

I'm using an ExpandableListView to display the content of three different RSS feeds. The ExpandableListView's adapter is implemented as a sub-class of 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) ...
    }
}

With this setup, all content is loaded as expected. However, the UI hangs/stutters randomly during the loading of the list content. Usually not enough to get an ANR, but still noticeable and very annoying!

In order to debug this issue, I enabled android.os.StrictMode (great new feature/tool btw!), and started up the emulator (on Android 2.3). When the list content is loaded, I get the following StrictMode output:

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)

This seems reasonable! In SimpleCursorTreeAdapter.getChildrenCursor(), the content provider is queried on the UI thread, which of course is a bad idea that will most definitely hang the UI!

I had a look at the JavaDoc (API level 4) of CursorTreeAdapter (a super-class of SimpleCursorTreeAdapter) and for getChildrenCursor() it says:

"If you want to asynchronously query a provider to prevent blocking the UI, it is possible to return null and at a later time call setChildrenCursor(int, Cursor).".

Great! Let's do that! I change my implementation of getChildrenCursor() to start a new task responsible for performing the query and setting the children cursor on the adapter. After having started the task, I just return null in 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;
}

, where the RefreshChildrenCursorTask is implemented as:

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);
        }
    }
}

I reinstalled the app on the 2.3 emulator and started it up. It worked like a charm, bye bye non-responsive UI! But the joy didn't last long ... The next thing to do was to run the same code on the target device (in this case a Samsung Galaxy S running Android 2.2). I then got the following Exception during the loading of the list feed content:

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)

A quick look at the source code of SimpleCursorTreeAdapter tells me that there is no way that it can cope with null being returned asynchronously from getChildrenCursor(). They seem to have solved this problem in Android 2.3, but how are you supposed to get around it in earlier versions of Android? Do I really have to write my own implementation of CursorTreeAdapter to be able to refresh the children cursor asynchronously?

Has anybody had the same problem and perhaps even found a (feasible) workaround? If not, could anybody give me a hint on how to proceed!? I would really appreciate any help I can get!

Thanks in advance!

Regards,
Jacob

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

许一世地老天荒 2024-10-18 20:06:28

如果他们在 2.3 SimpleCursorTreeAdapter 中修复了它,为什么不直接下载 java 文件并将其包含在您的项目中并使用该版本而不是手机上的版本呢?

我没有查看代码,但只要他们没有进行任何新的 2.3 API 调用,它就应该可以工作。

If they fixed it in the 2.3 SimpleCursorTreeAdapter, why don't you just download the java file and include it in your project and use that version rather than the one on the phone?

I haven't looked at the code, but as long as they didn't make any new 2.3 API calls, it should work.

森末i 2024-10-18 20:06:28

感谢您的反馈,mp2526!对于我迟到的回复,我深表歉意!

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

无论如何,我对 SimpleCursorTreeAdapter 的 API 8 和 API 9 进行了区分,他们在 Android 2.3 中所做的是 childFromgroupFrom列 ID 现在被延迟初始化,即不再在构造函数中。相反,这些成员现在在第一次调用 bindView() 时初始化。这样,现在就有时间异步检索子游标。由于 API 4 和 API 9 之间此类的公共 API 似乎没有任何更改(新方法 setViewText() 除外),因此您的解决方案使用此 API 版本 9类应该无需更改即可工作!我尝试了一下,果然成功了!

非常感谢,mp2526!

Thanks for your feedback, mp2526! I apologize for my late response!

That is a great suggestion! However, I really thought that it should be possible to do this (retrieve the children cursors asynchronouosly) in API 8, and that I had misunderstood anything ... Apparently, it isn't possible (at least not with the use of SimpleCursorTreeAdapter)!

Anyway, I made a diff between API 8 and API 9 for SimpleCursorTreeAdapter, and what they have done in Android 2.3 is that the childFrom and groupFrom column ids are now initialized lazily, i.e. no longer in the constructor. Instead, these members are now initialized in the first call to bindView(). That way, there's now time to asynchronously retrieve the children cursors. As there doesn't seem to be any changes to the public API of this class between API 4 and API 9 (except the new method setViewText()), your solution to use the API version 9 of this class should work without changes! I tried it out, and it did!

Thanks a lot, mp2526!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文