SimpleCursorTreeAdapter和CursorLoader for ExpandableListView

我想asynchronous查询提供者通过使用CursorLoaderSimpleCursorTreeAdapter

这是我的Fragment类,它实现了CursorLoader

 public class GroupsListFragment extends ExpandableListFragment implements LoaderManager.LoaderCallbacks<Cursor> { private final String DEBUG_TAG = getClass().getSimpleName().toString(); private static final String[] CONTACTS_PROJECTION = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME }; private static final String[] GROUPS_SUMMARY_PROJECTION = new String[] { ContactsContract.Groups.TITLE, ContactsContract.Groups._ID, ContactsContract.Groups.SUMMARY_COUNT, ContactsContract.Groups.ACCOUNT_NAME, ContactsContract.Groups.ACCOUNT_TYPE, ContactsContract.Groups.DATA_SET }; GroupsAdapter mAdapter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); populateContactList(); getLoaderManager().initLoader(-1, null, this); } public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. Log.d(DEBUG_TAG, "onCreateLoader for loader_id " + id); CursorLoader cl; if (id != -1) { // child cursor Uri contactsUri = ContactsContract.Data.CONTENT_URI; String selection = "((" + ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME + " NOTNULL) AND (" + ContactsContract.CommonDataKinds.GroupMembership.HAS_PHONE_NUMBER + "=1) AND (" + ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME + " != '') AND (" + ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID + " = ? ))"; String sortOrder = ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME + " COLLATE LOCALIZED ASC"; String[] selectionArgs = new String[] { String.valueOf(id) }; cl = new CursorLoader(getActivity(), contactsUri, CONTACTS_PROJECTION, selection, selectionArgs, sortOrder); } else { // group cursor Uri groupsUri = ContactsContract.Groups.CONTENT_SUMMARY_URI; String selection = "((" + ContactsContract.Groups.TITLE + " NOTNULL) AND (" + ContactsContract.Groups.TITLE + " != '' ))"; String sortOrder = ContactsContract.Groups.TITLE + " COLLATE LOCALIZED ASC"; cl = new CursorLoader(getActivity(), groupsUri, GROUPS_SUMMARY_PROJECTION, selection, null, sortOrder); } return cl; } public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. int id = loader.getId(); Log.d(DEBUG_TAG, "onLoadFinished() for loader_id " + id); if (id != -1) { // child cursor if (!data.isClosed()) { Log.d(DEBUG_TAG, "data.getCount() " + data.getCount()); try { mAdapter.setChildrenCursor(id, data); } catch (NullPointerException e) { Log.w("DEBUG","Adapter expired, try again on the next query: " + e.getMessage()); } } } else { mAdapter.setGroupCursor(data); } } public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // is about to be closed. int id = loader.getId(); Log.d(DEBUG_TAG, "onLoaderReset() for loader_id " + id); if (id != -1) { // child cursor try { mAdapter.setChildrenCursor(id, null); } catch (NullPointerException e) { Log.w("TAG", "Adapter expired, try again on the next query: " + e.getMessage()); } } else { mAdapter.setGroupCursor(null); } } /** * Populate the contact list */ private void populateContactList() { // Set up our adapter mAdapter = new GroupsAdapter(getActivity(),this, android.R.layout.simple_expandable_list_item_1, android.R.layout.simple_expandable_list_item_1, new String[] { ContactsContract.Groups.TITLE }, // Name for group layouts new int[] { android.R.id.text1 }, new String[] { ContactsContract.Contacts.DISPLAY_NAME }, // Name for child layouts new int[] { android.R.id.text1 }); setListAdapter(mAdapter); } } 

这里是我的适配器,它的子类SimpleCursorTreeAdapter

 public class GroupsAdapter extends SimpleCursorTreeAdapter { private final String DEBUG_TAG = getClass().getSimpleName().toString(); private ContactManager mActivity; private GroupsListFragment mFragment; // Note that the constructor does not take a Cursor. This is done to avoid // querying the database on the main thread. public GroupsAdapter(Context context, GroupsListFragment glf, int groupLayout, int childLayout, String[] groupFrom, int[] groupTo, String[] childrenFrom, int[] childrenTo) { super(context, null, groupLayout, groupFrom, groupTo, childLayout, childrenFrom, childrenTo); mActivity = (ContactManager) context; mFragment = glf; } @Override protected Cursor getChildrenCursor(Cursor groupCursor) { // Given the group, we return a cursor for all the children within that group int groupId = groupCursor.getInt(groupCursor .getColumnIndex(ContactsContract.Groups._ID)); Log.d(DEBUG_TAG, "getChildrenCursor() for groupId " + groupId); Loader loader = mActivity.getLoaderManager().getLoader(groupId); if ( loader != null && loader.isReset() ) { mActivity.getLoaderManager().restartLoader(groupId, null, mFragment); } else { mActivity.getLoaderManager().initLoader(groupId, null, mFragment); } } } 

问题是,当我点击其中一个父组的三个事情之一发生似乎是不一致的时尚。

1)小组打开,小孩出现在下面

2)该组没有打开,并且setChildrenCursor()调用抛出一个NullPointerException错误, NullPointerException错误在try catch块中被捕获

3)组没有打开,也没有错误发生

以下是在一个组展开并显示子项的情况下的一些debugging输出:

当所有的小组都显示出来时:

 05-20 10:08:22.765: D/GroupsListFragment(22132): onCreateLoader for loader_id -1 05-20 10:08:23.613: D/GroupsListFragment(22132): onLoadFinished() for loader_id -1 

-1是组光标的loader_id

然后,如果我特别select一个组(我们称之为组A),则输出:

 05-20 23:22:31.140: D/GroupsAdapter(13844): getChildrenCursor() for groupId 67 05-20 23:22:31.140: D/GroupsListFragment(13844): onCreateLoader for loader_id 67 05-20 23:22:31.254: D/GroupsListFragment(13844): onLoadFinished() for loader_id 67 05-20 23:22:31.254: D/GroupsListFragment(13844): data.getCount() 4 05-20 23:22:31.254: W/GroupsListFragment(13844): Adapter expired, try again on the next query: null 

该组不扩展,并捕获NullPointerException 。 然后,如果我select另一个组(我们只是称之为B组),它会输出:

 05-20 23:25:38.089: D/GroupsAdapter(13844): getChildrenCursor() for groupId 3 05-20 23:25:38.089: D/GroupsListFragment(13844): onCreateLoader for loader_id 3 05-20 23:25:38.207: D/GroupsListFragment(13844): onLoadFinished() for loader_id 3 05-20 23:25:38.207: D/GroupsListFragment(13844): data.getCount() 6 

这一次,不抛出NullPointerException 。 而不是B组扩大,组A扩大。

任何人都可以解释setChildrenCursor()调用正在展示的行为吗?

我在考虑如何在onCreateLoader()中实例化组/子CursorLoaders时遇到问题。 对于CursorLoader组,我只是想让我的手机中的所有组。 子CursorLoader应该包含一个组中的所有联系人。 有没有人有任何想法可能是什么问题?

UPDATE

感谢@ Yam的build议,我现在修改了getChildrenCursor()方法。 我现在selectgroupCursor位置而不是ContactsContract.Groups._ID的值传递给initLoader()调用。 我也改变了逻辑来调用restartLoader()只有当加载器不为null和加载器isReset为假。

 protected Cursor getChildrenCursor(Cursor groupCursor) { // Given the group, we return a cursor for all the children within that // group int groupPos = groupCursor.getPosition(); Log.d(DEBUG_TAG, "getChildrenCursor() for groupPos " + groupPos); Loader loader = mActivity.getLoaderManager().getLoader(groupPos); if (loader != null && !loader.isReset()) { mActivity.getLoaderManager().restartLoader(groupPos, null, mFragment); } else { mActivity.getLoaderManager().initLoader(groupPos, null, mFragment); } return null; } 

这绝对是更有意义的,并没有performance出一些有时而不是其他时间的不规则行为。

但是,在他们不属于的组下显示有联系人。 而且也有一些组中有联系人,但不会显示任何联系人。 所以现在可以解决getChildrenCursor()问题了。

但是现在看起来像是在onCreateLoader()方法中实例化CursorLoaders的问题。 CursorLoader是否在onCreateLoader()方法中返回,导致子游标被不正确地实例化?

UPDATE

所以我已经确定了我的一个问题。 在getChildrenCursor()方法中,如果我将groupId传递给initLoader()方法,然后在onCreateLoader()方法中创buildCursorLoader ,则会为查询获取正确的groupid参数。 然而,在onLoadFinished()setChildrenCursor() onLoadFinished()的调用正在传递第一个参数而不是groupPosition的加载器ID。 我猜我必须映射加载器ID到一些数据结构中的组位置。 但我不确定这是否是最好的方法。 有没有人有什么build议?

所以我觉得我需要将loaderids映射到groupPositions,这解决了我的问题:

这是我的Fragment类,它实现了CursorLoader

 public class GroupsListFragment extends ExpandableListFragment implements LoaderManager.LoaderCallbacks<Cursor> { private final String DEBUG_TAG = getClass().getSimpleName().toString(); private static final String[] CONTACTS_PROJECTION = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME }; private static final String[] GROUPS_PROJECTION = new String[] { ContactsContract.Groups.TITLE, ContactsContract.Groups._ID }; GroupsAdapter mAdapter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); populateContactList(); // Prepare the loader. Either re-connect with an existing one, // or start a new one. Loader loader = getLoaderManager().getLoader(-1); if (loader != null && !loader.isReset()) { getLoaderManager().restartLoader(-1, null, this); } else { getLoaderManager().initLoader(-1, null, this); } } public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. Log.d(DEBUG_TAG, "onCreateLoader for loader_id " + id); CursorLoader cl; if (id != -1) { // child cursor Uri contactsUri = ContactsContract.Data.CONTENT_URI; String selection = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND (" + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND (" + ContactsContract.Contacts.DISPLAY_NAME + " != '') AND (" + ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID + " = ? ))"; String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"; String[] selectionArgs = new String[] { String.valueOf(id) }; cl = new CursorLoader(getActivity(), contactsUri, CONTACTS_PROJECTION, selection, selectionArgs, sortOrder); } else { // group cursor Uri groupsUri = ContactsContract.Groups.CONTENT_URI; String selection = "((" + ContactsContract.Groups.TITLE + " NOTNULL) AND (" + ContactsContract.Groups.TITLE + " != '' ))"; String sortOrder = ContactsContract.Groups.TITLE + " COLLATE LOCALIZED ASC"; cl = new CursorLoader(getActivity(), groupsUri, GROUPS_PROJECTION, selection, null, sortOrder); } return cl; } public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. int id = loader.getId(); Log.d(DEBUG_TAG, "onLoadFinished() for loader_id " + id); if (id != -1) { // child cursor if (!data.isClosed()) { Log.d(DEBUG_TAG, "data.getCount() " + data.getCount()); HashMap<Integer,Integer> groupMap = mAdapter.getGroupMap(); try { int groupPos = groupMap.get(id); Log.d(DEBUG_TAG, "onLoadFinished() for groupPos " + groupPos); mAdapter.setChildrenCursor(groupPos, data); } catch (NullPointerException e) { Log.w("DEBUG","Adapter expired, try again on the next query: " + e.getMessage()); } } } else { mAdapter.setGroupCursor(data); } } public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // is about to be closed. int id = loader.getId(); Log.d(DEBUG_TAG, "onLoaderReset() for loader_id " + id); if (id != -1) { // child cursor try { mAdapter.setChildrenCursor(id, null); } catch (NullPointerException e) { Log.w("TAG", "Adapter expired, try again on the next query: " + e.getMessage()); } } else { mAdapter.setGroupCursor(null); } } /** * Populate the contact list */ private void populateContactList() { // Set up our adapter mAdapter = new GroupsAdapter(getActivity(),this, android.R.layout.simple_expandable_list_item_1, android.R.layout.simple_expandable_list_item_1, new String[] { ContactsContract.Groups.TITLE }, // Name for group layouts new int[] { android.R.id.text1 }, new String[] { ContactsContract.Contacts.DISPLAY_NAME }, // Name for child layouts new int[] { android.R.id.text1 }); setListAdapter(mAdapter); } } 

这里是我的适配器,它的子类SimpleCursorTreeAdapter

 public class GroupsAdapter extends SimpleCursorTreeAdapter { private final String DEBUG_TAG = getClass().getSimpleName().toString(); private ContactManager mActivity; private GroupsListFragment mFragment; protected final HashMap<Integer, Integer> mGroupMap; // Note that the constructor does not take a Cursor. This is done to avoid // querying the database on the main thread. public GroupsAdapter(Context context, GroupsListFragment glf, int groupLayout, int childLayout, String[] groupFrom, int[] groupTo, String[] childrenFrom, int[] childrenTo) { super(context, null, groupLayout, groupFrom, groupTo, childLayout, childrenFrom, childrenTo); mActivity = (ContactManager) context; mFragment = glf; mGroupMap = new HashMap<Integer, Integer>(); } @Override protected Cursor getChildrenCursor(Cursor groupCursor) { // Given the group, we return a cursor for all the children within that group int groupPos = groupCursor.getPosition(); int groupId = groupCursor.getInt(groupCursor .getColumnIndex(ContactsContract.Groups._ID)); Log.d(DEBUG_TAG, "getChildrenCursor() for groupPos " + groupPos); Log.d(DEBUG_TAG, "getChildrenCursor() for groupId " + groupId); mGroupMap.put(groupId, groupPos); Loader loader = mActivity.getLoaderManager().getLoader(groupId); if ( loader != null && !loader.isReset() ) { mActivity.getLoaderManager().restartLoader(groupId, null, mFragment); } else { mActivity.getLoaderManager().initLoader(groupId, null, mFragment); } return null; } //Accessor method public HashMap<Integer, Integer> getGroupMap() { return mGroupMap; } } 

在我的情况下,我使用initLoader(或restartLoader)的第一个参数给予callback的组位置,并使用Bundle来获取getChildrenCursor中的子数据。

像以下一样;

 public class ExpandableListAdapter extends SimpleCursorTreeAdapter implements LoaderManager.LoaderCallbacks<Cursor> { private Context mContext; private LoaderManager mManager; public ExpandableListAdapter( Context context, ExpandableListAdapterListener listener, LoaderManager manager, Cursor groupCursor, int groupLayout, String[] groupFrom, int[] groupTo, int childLayout, String[] childFrom, int[] childTo) { super(context, groupCursor, groupLayout, groupFrom, groupTo, childLayout, childFrom, childTo); mContext = context; mManager = manager; } @Override protected Cursor getChildrenCursor(Cursor groupCursor) { final long idGroup = groupCursor.getLong(groupCursor.getColumnIndex("_id")); Bundle bundle = new Bundle(); bundle.putLong("idGroup", idGroup); int groupPos = groupCursor.getPosition(); if (mManager.getLoader(groupPos) != null && !mManager.getLoader(groupPos).isReset()) { mManager.restartLoader(groupPos, bundle, this); } else { mManager.initLoader(groupPos, bundle, this); } return null; } @Override public Loader<Cursor> onCreateLoader(int groupPos, Bundle bundle) { long idGroup = bundle.getLong("idGroup"); return new CursorLoader( mContext, Provider.URI, new String[]{Table.ID, Table.ID_GROUP, Table.TITLE, Table.CONTEXT}, Table.ID_GROUP + " = ?", new String[]{String.valueOf(idGroup)}, Table.CREATED + " DESC" ); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { setChildrenCursor(loader.getId(), cursor); } @Override public void onLoaderReset(Loader<Cursor> loader) { } } 

我有使用ExpandableListView不好的经验。 它在不同的Android版本中的行为是不同的。 如果你还没有深入,你可能会考虑重新devise你的界面。

无论如何,对你的问题,我build议你检查这3点。

首先,在你调用init的子游标加载器

 mActivity.getLoaderManager().initLoader(groupId, null, mFragment); 

您传入的groupI是ContactsContract.Groups._ID的值。 然后,你在setChildrenCursor的第一个参数中使用这个id。 这可能是错误的。 不要将groupId传入initLoader,而是尝试传入组光标位置。 例如:

 int iGroupPos = groupCursor.getPosition(); if ( loader != null && !loader.isReset()) mActivity.getLoaderManager().restartLoader(iGroupPos, null, mFragment); else mActivity.getLoaderManager().initLoader(iGroupPos, null, mFragment); 

其次,你可以看到,在我上面提到的代码中,只有在loader不为null且loader isReset为false时,才可能调用restartLoader。

第三,你需要为getChildrenCursor调用返回一个值,我相信它应该是null。