22

Я пытаюсь асинхронно запрос поставщика с использованием CursorLoader с SimpleCursorTreeAdapterSimpleCursorTreeAdapter и CursorLoader для ExpandableListView

Вот мой 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 ошибку, которая попадает в блок попробовать поймать

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 группового курсора

Затем, если я выбрать одну группу, в частности, (давайте просто называть его группа А) она выводит:

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()?

Я думаю, что существует проблема с тем, как создаются экземпляры CursorLoaders группы/ребенка в onCreateLoader(). Для группы CursorLoader я просто хочу, чтобы все группы в моем телефоне. Ребенок CursorLoader должен содержать все контакты внутри группы. У кого-нибудь есть идеи, что может быть проблемой?

UPDATE

Благодаря @ совет Ям, я теперь модифицированный метод getChildrenCursor(). Теперь я выбираю позицию groupCursor, а не значение ContactsContract.Groups._ID, чтобы перейти в вызов initLoader(). Я также изменил логику, чтобы вызвать restartLoader(), только когда loader не равен null, а loader isReset - false.

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() теперь могут быть решены.

Но теперь это вопрос о том, как CursorLoaders создаются в методе onCreateLoader(). Возвращается ли CursorLoader в методе onCreateLoader() для некорректного создания дочернего курсора?

UPDATE

Так я определил один из моих вопросов. В методе getChildrenCursor(), если я передаю groupId в метод initLoader(), то в методе onCreateLoader(), когда создается CursorLoader, он получит правильный параметр groupid для запроса. Однако в onLoadFinished() вызов setChildrenCursor() получает идентификатор загрузчика для первого параметра, а не groupPosition. Я предполагаю, что мне нужно сопоставить идентификаторы загрузчика сгруппированными позициями в некоторой структуре данных. Но я не уверен, что это лучший подход. У кого-нибудь есть предложения?

+0

Я только что сделал это, но он не использовал CursorLoader, поэтому меня бросает ...В моем представлении getChildrenCursor возвращает курсор. С помощью loadermanager, где этот курсор/данные действительно происходит? Если вы не кормите курсор в конструктор, то что вводится в 'getChildrenCursor' как groupCursor? – Barak

+0

ГруппаКупер был установлен в методе onLoadFinished() LoaderManager. Я прошел через код с помощью отладчика и в методе getChildenCursor(), groupCursor всегда определяется. – toobsco42

+0

Я не знаю, это звучит так, будто вы не всегда получаете населенного ребенкаCursor ... – Barak

ответ

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; 
    } 

} 
+2

Примечание. Android имеет SparseIntArray (см. Http://developer.android.com/reference/android/util/SparseIntArray.html), который лучше использовать, чем HashMap и служит для этой же цели. –

+0

Спасибо за рекомендацию. Я попробую. – toobsco42

+1

Согласно этому анализу: http://mobile.dzone.com/articles/tweaking-your-android они достигают аналогичных результатов для количества элементов, которые у меня есть в хэшмапе, который составляет менее 1000. – toobsco42

1

У меня плохой опыт использования ExpandableListView. Его поведение в разных версиях Android отличается. Если вы еще не слишком глубоко в нем, вам может потребоваться перепроектировать интерфейс.

В любом случае, на ваши вопросы, я предлагаю вам просмотреть эти 3 очка.

Во-первых, в вызове Инициализировать дети Cursor Loader

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

идентификатор_группы вы прошли в это значение ContactsContract.Groups._ID. Затем вы используете этот идентификатор в первом параметре setChildrenCursor. Возможно, это неправильно. Вместо передачи groupId в initLoader попробуйте перейти в позицию курсора в группе. Например:

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

Во-вторых, вы можете увидеть, что в коде я предложил выше, вы, вероятно, следует назвать restartLoader только тогда, когда погрузчик не утратившим погрузчик isReset ложна.

В-третьих, вам нужно вернуть значение для вызова getChildrenCursor, который, я считаю, должен быть пустым.

+0

Это определенно имеет смысл и, кажется, ближе к тому, что я хочу. Но у меня все еще есть проблемы, о которых я упомянул в UPDATE в вопросе выше. – toobsco42

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) { 
    } 
} 
+1

это не сработает, если вам когда-нибудь понадобится перезагрузить загрузчик, поскольку дополнительные функции используются только во время строительства погрузчика. –

Смежные вопросы