3

我正在开发一个用于显示许多 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 策略违规;~duration=1522 毫秒:android.os.StrictMode$StrictModeDiskReadViolation:策略=23 违规=2
D/StrictMode(395):在 android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:745)
D/StrictMode(395):在 android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1345)
D/StrictMode(395):在 android.database.sqlite.SQLiteQueryBuilder.query(SQLiteQueryBuilder.java:330)
D/StrictMode(395):在 com.mycompany.myapp.provider.RSSFeedProvider.query(RSSFeedProvider.java:128)
D/StrictMode(395):在 android.content.ContentProvider$Transport.query(ContentProvider.java:187)
D/StrictMode(395):在 android.content.ContentResolver.query(ContentResolver.java:262)
D/StrictMode(395):在 android.app.Activity.managedQuery(Activity.java:1550)
D/StrictMode(395):在 com.mycompany.myapp.activity.MultipleFeedsActivity$RssFeedLatestListAdapter.getChildrenCursor(MultipleFeedsActivity.java:388)
D/StrictMode(395):在 android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106)
D/StrictMode(395):在 android.widget.CursorTreeAdapter.getChildrenCount(CursorTreeAdapter.java:178)
D/StrictMode(395):在 android.widget.ExpandableListConnector.refreshExpGroupMetadataList(ExpandableListConnector.java:561)
D/StrictMode(395):在 android.widget.ExpandableListConnector.expandGroup(ExpandableListConnector.java:682)
D/StrictMode(395):在 android.widget.ExpandableListConnector.expandGroup(ExpandableListConnector.java:636)
D/StrictMode(395):在 android.widget.ExpandableListView.expandGroup(ExpandableListView.java:608)
D/StrictMode(395):在 com.mycompany.myapp.activity.MultipleFeedsActivity.onReceiveResult(MultipleFeedsActivity.java:335)
D/StrictMode(395):在 com.mycompany.myapp.service.FeedResultReceiver.onReceiveResult(FeedResultReceiver.java:40)
D/StrictMode(395):在 android.os.ResultReceiver$MyRunnable.run(ResultReceiver.java:43)
D/StrictMode(395):在 android.os.Handler.handleCallback(Handler.java:587)
D/StrictMode(395):在 android.os.Handler.dispatchMessage(Handler.java:92)
D/StrictMode(395):在 android.os.Looper.loop(Looper.java:123)
D/StrictMode(395):在 android.app.ActivityThread.main(ActivityThread.java:3647)
D/StrictMode(395):在 java.lang.reflect.Method.invokeNative(本机方法)
D/StrictMode(395):在 java.lang.reflect.Method.invoke(Method.java:507)
D/StrictMode(395):在 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
D/StrictMode(395):在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
D/StrictMode(395):在 dalvik.system.NativeStart.main(本机方法)

这似乎有道理!在SimpleCursorTreeAdapter.getChildrenCursor()中,在 UI 线程上查询内容提供者,这当然是个坏主意,肯定会挂掉 UI!

我查看了(的超类)的JavaDoc(API 级别 4),它说: CursorTreeAdapterSimpleCursorTreeAdaptergetChildrenCursor()

“如果您想异步查询提供程序以防止阻塞 UI,可以返回 null 并在稍后调用 setChildrenCursor(int, Cursor)。” .

伟大的!让我们这样做吧!我更改了我的实现getChildrenCursor()以启动一个新任务,该任务负责执行查询并在适配器上设置子光标。开始任务后,我只返回 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;
}

,其中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):致命异常:主要
E/AndroidRuntime(23191): java.lang.NullPointerException
E/AndroidRuntime(23191):在 android.widget.SimpleCursorTreeAdapter.initFromColumns(SimpleCursorTreeAdapter.java:194)
E/AndroidRuntime(23191):在 android.widget.SimpleCursorTreeAdapter.initChildrenFromColumns(SimpleCursorTreeAdapter.java:205)
E/AndroidRuntime(23191):在 android.widget.SimpleCursorTreeAdapter.init(SimpleCursorTreeAdapter.java:186)
E/AndroidRuntime(23191):在 android.widget.SimpleCursorTreeAdapter.(SimpleCursorTreeAdapter.java:136)
E/AndroidRuntime(23191):在 com.mycompany.myapp.activity.MultipleFeedsActivity$RssFeedLatestListAdapter.(MultipleFeedsActivity.java:378)
E/AndroidRuntime(23191):在 com.mycompany.myapp.activity.MultipleFeedsActivity$2.run(MultipleFeedsActivity.java:150)
E/AndroidRuntime(23191):在 android.os.Handler.handleCallback(Handler.java:587)
E/AndroidRuntime(23191):在 android.os.Handler.dispatchMessage(Handler.java:92)
E/AndroidRuntime(23191): 在 android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime(23191): 在 android.app.ActivityThread.main(ActivityThread.java:4627)
E/AndroidRuntime(23191):在 java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(23191):在 java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime(23191):在 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:871)
E/AndroidRuntime(23191): 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:629)
E/AndroidRuntime(23191): 在 dalvik.system.NativeStart.main(Native Method)

快速查看 的源代码SimpleCursorTreeAdapter告诉我,它无法处理从getChildrenCursor(). 他们似乎已经在 Android 2.3 中解决了这个问题,但是你应该如何在早期版本的 Android 中解决这个问题?我真的必须编写自己的实现CursorTreeAdapter才能异步刷新子光标吗?

有没有人遇到过同样的问题,甚至可能找到了(可行的)解决方法?如果没有,任何人都可以给我一个关于如何进行的提示!?我真的很感激我能得到的任何帮助!

提前致谢!

问候,雅各布

4

2 回答 2

2

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

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

于 2011-01-21T03:43:26.037 回答
2

感谢您的反馈,mp2526!我为我迟到的回复道歉!

这是一个很好的建议!但是,我真的认为应该可以在 API 8 中执行此操作(异步检索子光标),并且我误解了任何内容……显然,这是不可能的(至少不使用SimpleCursorTreeAdapter)!

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

非常感谢,mp2526!

于 2011-01-26T12:46:35.603 回答