【问题标题】:CursorLoader and CursorTreeAdapterCursorLoader 和 CursorTreeAdapter
【发布时间】:2011-11-08 18:35:02
【问题描述】:

我正在尝试将 CursorLoader 与 CursorTreeAdapter 一起使用,但遇到了一个我无法弄清楚的问题。 (如果您有这方面的工作示例,请随时跳过其余部分并将其附在下面。我将非常感谢) 我第一次打开一个组时,一切正常。如果我随后关闭并重新打开该组,则会收到溢出错误。这是错误:

V/SpellBook(29520): Activity being created 
D/SpellBook(29520): onCreateLoader for id 123456 
V/SpellBook(29520): Resuming 
V/SpellBook(29520): Processing query for uri content://com.zalzala.spellbook.SpellProvider/levels/bard 
D/SpellBook(29520): onLoadFinished() for id 123456 
D/SpellBook(29520): onCreateLoader for id 3 
V/SpellBook(29520): Processing query for uri content://com.zalzala.spellbook.SpellProvider/class/bard/3 
D/SpellBook(29520): onLoadFinished() for id 3 

这里一切都很好。到目前为止,我已经打开了活动并打开了第 3 组。如果我关闭第 3 组并再次打开它会发生以下情况:

D/SpellBook(29520): onLoadFinished() for id 3 
D/SpellBook(29520): onLoadFinished() for id 3 
D/SpellBook(29520): onLoadFinished() for id 3 
D/SpellBook(29520): onLoadFinished() for id 3 
D/SpellBook(29520): onLoadFinished() for id 3 
(... and a lot more of these) 
    E/AndroidRuntime(29520): FATAL EXCEPTION: main 
E/AndroidRuntime(29520): java.lang.StackOverflowError 
E/AndroidRuntime(29520):    at java.util.HashMap.get(HashMap.java:302) 
E/AndroidRuntime(29520):    at android.database.sqlite.SQLiteCursor.getColumnIndex(SQLiteCursor.java:355)
E/AndroidRuntime(29520):    at android.database.CursorWrapper.getColumnIndex(CursorWrapper.java:67) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment$MyExpandableListAdapter.getChildrenCursor(SpellListView.java:216) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.setChildrenCursor(CursorTreeAdapter.java:159) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:183) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:1) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:413) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:547) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment$MyExpandableListAdapter.getChildrenCursor(SpellListView.java:217) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.setChildrenCursor(CursorTreeAdapter.java:159) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:183) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:1) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:413) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:547) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment$MyExpandableListAdapter.getChildrenCursor(SpellListView.java:217) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.setChildrenCursor(CursorTreeAdapter.java:159) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:183) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:1) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:413) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:547) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment$MyExpandableListAdapter.getChildrenCursor(SpellListView.java:217) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.setChildrenCursor(CursorTreeAdapter.java:159) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:183) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:1) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:413) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:547) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment$MyExpandableListAdapter.getChildrenCursor(SpellListView.java:217) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.setChildrenCursor(CursorTreeAdapter.java:159) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:183) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:1) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:413) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:547) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment$MyExpandableListAdapter.getChildrenCursor(SpellListView.java:217) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.ja 
W/ActivityManager( 6887):   Force finishing activity com.zalzala.spellbook/.SpellListView

因此,由于某种原因,onLoadFinished() 在重新打开组时会一遍又一遍地为子光标调用。

这是我的加载器实现:

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // TODO Auto-generated method stub
        Log.d(Spellbook.TAG,"onCreateLoader for id "+id);
        if (id <123456){
            Uri spellUri = Uri.withAppendedPath(SpellProvider.CONTENT_URI, "class");
            spellUri = Uri.withAppendedPath(spellUri, mCLASS);
            spellUri = ContentUris.withAppendedId(spellUri, id);
            return new CursorLoader(getActivity(), spellUri, null, null, null, null);
        }else {
            //get group cursor
            Uri groupUri = Uri.withAppendedPath(SpellProvider.CONTENT_URI, "levels");
            groupUri = Uri.withAppendedPath(groupUri, mCLASS);
            return new CursorLoader(getActivity(), groupUri, null, null, null, null);
        }
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // TODO Auto-generated method stub
        int id = loader.getId();
        Log.d(Spellbook.TAG,"onLoadFinished() for id "+id);
        if (id < 123456){
            //child cursor
            ((CursorTreeAdapter) mAdapter).setChildrenCursor(id, data);
        } else {
            ((CursorTreeAdapter) mAdapter).setGroupCursor(data);
        }

    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // TODO Auto-generated method stub
        int id = loader.getId();
        Log.d(Spellbook.TAG,"onLoaderReset() for id "+id);
        if (id < 123456){
            //child cursor
            ((CursorTreeAdapter) mAdapter).setChildrenCursor(id, null);
        } else {
            ((CursorTreeAdapter) mAdapter).setGroupCursor(null);
        }

    }

我使用将自动重新查询指定为 false 的构造函数调用 CursorTreeAdapter。

    public class MyExpandableListAdapter extends CursorTreeAdapter {

        public MyExpandableListAdapter(Cursor cursor, Context context) {
            super(cursor, context, false); //do not auto requery. pretty sure CursorLoader needs this.
        }

        @Override
        protected Cursor getChildrenCursor(Cursor groupCursor) {
            // Given the group, we return a cursor for all the children within that group 
            int id = groupCursor.getInt(groupCursor.getColumnIndex(SpellDbAdapter.KEY_LEVEL));
            getLoaderManager().initLoader(id, null, ExpandableListCursorLoaderFragment.this);
            return null;
        }

感谢您的帮助!

【问题讨论】:

  • 我尝试了类似的实现,但无法使其正常工作。我不是继承 CursorTreeAdapter,而是继承 SimpleCursorTreeAdapter。 groupCursor 加载正常,我可以看到所有组,但是当我单击其中一个组时,它会调用 getChildrenCursor() 子例程,该子例程返回 null。我得到一个空指针异常错误。你遇到过这个问题吗?
  • 这听起来更像是你如何获得孩子的问题。您能否验证获取子光标的调用在测试中是否有效?
  • 我发现问题不在于 getChildrenCursor() 子例程,而在于对 setChildrenCursor() 子例程的调用。所以在我的 onLoadFinished() 方法中,我得到了加载器的 id。如果它是一个子光标,那么我将 setChildrenCursor 放在 try catch 块中 try{ mAdapter.setChildrenCursor(id, data); }catch(NullPointerException e){ Log.w("TAG", "适配器已过期,下一个查询重试:" + e.getMessage());现在我在抛出空指针异常时捕获它。但是,只有一些组被扩展以显示孩子们。
  • 我在这里做类似的事情stackoverflow.com/questions/10611927/…
  • 我在 SDK 示例 ApiDemos/src/com/example/android/apis/view/ExpandableList2.java 中关注了这个文件,我的列表运行良好……

标签: android adapter expandablelistview android-cursorloader


【解决方案1】:

这是一个老问题,但仍然没有正确答案(现在有)。

MyExpandableListAdapter 有一个 getChildrenCursor(Cursor) 方法:

@Override
protected Cursor getChildrenCursor(Cursor groupCursor) {
    int id = groupCursor.getInt(groupCursor.getColumnIndex(SpellDbAdapter.KEY_LEVEL));
    getLoaderManager().initLoader(id, null, ExpandableListCursorLoaderFragment.this);
    return null;
}

该方法使用 initLoader 调用启动 Loader。加载数据后,将调用 onLoadFinished 并设置子光标:

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // some code
    ((CursorTreeAdapter) mAdapter).setChildrenCursor(id, data);
    // some more code
}

CursorTreeAdapter.setChildrenCursor 方法如下:

public void setChildrenCursor(int groupPosition, Cursor childrenCursor) {
    MyCursorHelper childrenCursorHelper = getChildrenCursorHelper(groupPosition);
    childrenCursorHelper.changeCursor(childrenCursor, false);
}

它通过调用 CursorTreeAdapter.getChildrenCursorHelper 来检索 childrenCursorHelper:

synchronized MyCursorHelper getChildrenCursorHelper(int groupPosition, boolean requestCursor) {
    MyCursorHelper cursorHelper = mChildrenCursorHelpers.get(groupPosition);

    if (cursorHelper == null) {
        if (mGroupCursorHelper.moveTo(groupPosition) == null) return null;

        final Cursor cursor = getChildrenCursor(mGroupCursorHelper.getCursor());
        cursorHelper = new MyCursorHelper(cursor);
        mChildrenCursorHelpers.put(groupPosition, cursorHelper);
    }

    return cursorHelper;
}

在某些情况下,childrenCursorHelper 还不存在,该方法现在调用 getChildrenCursor(Cursor),这是启动整个调用链的方法。从逻辑上讲,我们会遇到 StackOverflowError。

这是 CursorTreeAdapter 类中的一个错误。虽然其他人建议改用 BaseExpandableAdapter,但我建议创建自定义 CursorTreeAdapter 类并进行必要的更改和修复。

可以通过更改 getChildrenCursorHelper 方法轻松修复 StackOverflowError。首先创建 MyCursorHelper,将其添加到 mChildrenCursorHelpers,然后如果 retrieveChildCursor 设置为 true(新参数),则检索子光标。从 setChildrenCursor 调用方法意味着我们传递 false 以不进入无限递归。

synchronized MyCursorHelper getChildrenCursorHelper(int groupPosition, boolean retrieveChildCursor) {
    MyCursorHelper cursorHelper = mChildrenCursorHelpers.get(groupPosition);

    if (cursorHelper == null && mGroupCursorHelper.moveTo(groupPosition) != null) {
        cursorHelper = new MyCursorHelper(null);
        mChildrenCursorHelpers.put(groupPosition, cursorHelper);

        if (retrieveChildCursor) {
            final Cursor cursor = getChildrenCursor(mGroupCursorHelper.getCursor());
            if (cursor != null) {
                cursorHelper.changeCursor(cursor, false);
            }
        }
    }

    return cursorHelper;
}

同时删除 MyCursorHelper.deactivate 方法中的代码,因为 CursorLoader 负责关闭过时的游标。

【讨论】:

    【解决方案2】:

    好的,与 Google 的某个人交谈,扩展列表可能早就应该被弃用了。如果您确实需要使用游标加载器和扩展列表,请扩展基本适配器。如果您真的不需要它,请继续,不要使用光标加载器。

    欢迎指正:)

    【讨论】:

    • ExpandableList 已弃用?我不这么认为,也许您在谈论 CursorTreeAdapter...
    【解决方案3】:

    onGroupCollapsed() 上致电destroyLoader()。但这并不能完全解决问题。查看CursorTreeAdapter 的实现,确保我不要将它与CursorLoader 一起使用。

    扩展BaseExpandableAdapter确实更好。这种方式可以避免不必要的(在使用加载器的情况下)使用内容观察器。在我的自定义适配器中,我将创建的加载器保持在稀疏数组中,并在组崩溃时销毁它们。效果很好:)

    【讨论】:

    • 但这不是首先破坏了使用装载机的目的吗?
    • 忘记我的回答。可能我应该删除它。查看 CursorTreeAdapter 的实现,请确保不要将它与 CursorLoader 一起使用。扩展基本可扩展适配器确实更好。
    • 其实,这是一个不错的建议。扩展基本可扩展适配器可能是要走的路。
    • 这种方式也可以避免不必要的(在使用加载器的情况下)使用内容观察器。在我的自定义适配器中,我一直在稀疏数组中创建加载器,并在组崩溃时销毁它们。效果很好:)
    • 酷。如果您不介意,请更新您的答案,我会接受。
    【解决方案4】:

    我认为当我们使用 initLoader 时,它会使堆栈变满, 尝试改用restartLoader。

    @Override
        protected Cursor getChildrenCursor(Cursor groupCursor) {
            // TODO Auto-generated method stub          
            Bundle b = new Bundle();
            //b.putString("Artist", "\""+groupCursor.getString(groupCursor.getColumnIndex(artist.ARTIST))+"\"");
            b.putString("Artist", groupCursor.getString(groupCursor.getColumnIndex(artist.ARTIST)));
            getLoaderManager().restartLoader(groupCursor.getPosition(), b, MpArtist.this);
            return null;
        }
    

    【讨论】:

      【解决方案5】:

      试试这个:

      @Override
      public void onLoadFinished(Loader<Cursor> loader, final Cursor data) {
          // ...
          final int id = loader.getId();
          new Handler().post(new Runnable() {
              @Override
              public void run() {
                  ((CursorTreeAdapter) mAdapter).setChildrenCursor(id, data);
              }
          });
       }
      

      【讨论】:

        猜你喜欢
        • 2017-02-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-29
        • 2012-03-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多