ExpandableListView的SimpleCursorTreeAdapter和CursorLoader

too*_*o42 22 android expandablelistadapter simplecursoradapter android-cursorloader

我试图通过使用异步查询提供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);
  }
}
Run Code Online (Sandbox Code Playgroud)

这是我的适配器哪个子类 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); 
    } 

  }

}
Run Code Online (Sandbox Code Playgroud)

问题是当我点击其中一个父组时,三件事中的一件发生在看似不一致的方式中.

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
Run Code Online (Sandbox Code Playgroud)

-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
Run Code Online (Sandbox Code Playgroud)

该小组没有扩大,而且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
Run Code Online (Sandbox Code Playgroud)

这一次,NullPointerException不会被抛出.而不是B组扩张,A组扩大.

任何人都可以解释setChildrenCursor()电话所展示的行为吗?

我在想如何实例化组/子CursorLoader有一个问题onCreateLoader().对于小组CursorLoader我只想要手机中的所有群组.孩子CursorLoader应该包含组内的所有联系人.有没有人有任何想法可能是什么问题?

UPDATE

感谢@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;
}
Run Code Online (Sandbox Code Playgroud)

这肯定更有意义,并且没有表现出有时而不是其他时间扩大的群体的一些不稳定行为.

但是,有些联系人正在他们不属于的组下显示.还有一些团体确实有联系,但不会显示任何联系人.所以似乎getChildrenCursor()问题现在可以解决了.

但现在它看起来是如何在onCreateLoader()方法中实例化CursorLoaders的问题.在子方法的方法中CursorLoader返回的是否onCreateLoader()被不正确地实例化?

UPDATE

所以我发现了我的一个问题.在getChildrenCursor()方法中,如果我将groupId传递给initLoader()方法然后在onCreateLoader()方法中,当CursorLoader创建它时,它将获得查询的正确groupid参数.但是,在onLoadFinished()调用中setChildrenCursor()传递的第一个参数的加载器ID不是groupPosition.我猜我必须将加载程序ID映射到某些数据结构中的组位置.但我不确定这是否是最佳方法.有没有人有什么建议?

too*_*o42 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

这是我的适配器哪个子类 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;
  }

}
Run Code Online (Sandbox Code Playgroud)

  • 注意,机器人具有SparseIntArray(见http://developer.android.com/reference/android/util/SparseIntArray.html),这是优于HashMap中<整数,整数>使用并用于相同的目的. (2认同)