我正在尝试使用CursorLoader
SimpleCursorTreeAdapter
这是我的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
错误,该错误会在try catch块中被捕获
3)该组未打开且未引发错误
以下是扩展组并显示子项的场景中的一些调试输出:
当显示所有组时,输出:
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
然后,如果我特别选择一个组(让我们称之为组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
被捕获。然后,如果我选择另一个组(我们只是将其称为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()
中实例化组/子CursorLoader存在问题。对于小组CursorLoader
,我只想要手机中的所有群组。子CursorLoader
应包含组内的所有联系人。有没有人有任何想法可能是什么问题?
更新
感谢@ Yam的建议,我现在修改了getChildrenCursor()
方法。我现在选择groupCursor位置而不是ContactsContract.Groups._ID的值来传递给initLoader()调用。我还改变了逻辑,只有当loader不为null且loader isReset为false时才调用restartLoader()。
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;
}
这显然更有意义,并且没有表现出有时扩张的群体的一些不稳定行为,而不是其他时间。
但是,有些联系人正在他们不属于的组下显示。还有一些团体确实有联系,但不会显示任何联系人。所以似乎现在可以解决getChildrenCursor()
问题。
但是现在它看起来是如何在onCreateLoader()
方法中实例化CursorLoaders的问题。 CursorLoader
方法中返回的onCreateLoader()
是否未正确实例化子游标?
更新
所以我发现了我的一个问题。在getChildrenCursor()
方法中,如果我将groupId传递给initLoader()
方法,然后在onCreateLoader()
方法中,则在创建CursorLoader
时,它将为查询获取正确的groupid参数。但是,在onLoadFinished()
中,对setChildrenCursor()
的调用将传递第一个参数的加载程序ID,而不是groupPosition。我猜我必须将加载程序ID映射到某些数据结构中的组位置。但我不确定这是否是最佳方法。有没有人有任何建议?
答案 0 :(得分:16)
所以我发现我需要将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;
}
}
答案 1 :(得分:2)
在我的例子中,我使用initLoader(或restartLoader)的第一个参数来给回调组的位置,并使用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) {
}
}
答案 2 :(得分:1)
我使用ExpandableListView的经验不佳。它在不同Android版本中的行为是不同的。如果您还没有深入研究,可以考虑重新设计界面。
无论如何,对于你的问题,我建议你回顾这3点。
首先,在你的init中调用子游标加载器
mActivity.getLoaderManager().initLoader(groupId, null, mFragment);
您传入的groupId是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。