22

Tôi cố gắng để đồng bộ truy vấn một nhà cung cấp bằng cách sử dụng một CursorLoader với một SimpleCursorTreeAdapterSimpleCursorTreeAdapter và CursorLoader cho ExpandableListView

Đây là lớp học của tôi Fragment mà thực hiện các 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); 
    } 
} 

Và đây là bộ chuyển đổi của tôi mà lớp con 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); 
    } 

    } 

} 

vấn đề là khi tôi nhấp vào một trong các nhóm phụ huynh, một trong ba điều xảy ra trong những gì dường như là một thời trang không phù hợp.

1) Hoặc là nhóm mở ra và những đứa trẻ xuất hiện bên dưới nó

2) Nhóm này không mở và cuộc gọi setChildrenCursor() ném một lỗi NullPointerException mà bị kẹt trong khối try catch

3) nhóm này không mở và không có lỗi được ném

Dưới đây là một số kết quả gỡ lỗi trong một tình huống trong đó một nhóm được mở rộng và hiển thị các trẻ em:

Khi tất cả các nhóm disp xếp chồng nó ouputs:

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 là loader_id của con trỏ nhóm

Sau đó, nếu tôi chọn một nhóm đặc biệt (chúng ta hãy chỉ gọi nó là nhóm A) nó ra:

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 

Nhóm không mở rộng và bị bắt giữ NullPointerException. Sau đó, nếu tôi chọn một nhóm khác (chúng ta hãy chỉ gọi nó là nhóm B) nó ra:

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 

Thời gian này, NullPointerException không ném. Và thay vì nhóm B mở rộng, nhóm A được mở rộng.

Mọi người có thể giải thích hành vi mà cuộc gọi setChildrenCursor() đang diễn ra không?

Tôi nghĩ có vấn đề với cách các trình tải con trỏ nhóm/con được khởi tạo trong onCreateLoader(). Đối với nhóm CursorLoader tôi chỉ muốn tất cả các nhóm trong điện thoại của tôi. Đứa trẻ CursorLoader phải chứa tất cả các liên hệ trong một nhóm. Có ai có bất kỳ ý tưởng gì có thể là vấn đề?

CẬP NHẬT

Nhờ @ lời khuyên Yam của tôi giờ đây đã sửa đổi phương pháp getChildrenCursor(). Bây giờ tôi đang chọn vị trí groupCursor không phải là giá trị của ContactsContract.Groups._ID để chuyển vào lời gọi initLoader(). Tôi cũng đã thay đổi logic để gọi restartLoader() chỉ khi bộ nạp không phải là null và bộ nạp làReset là sai.

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

Điều này chắc chắn có ý nghĩa hơn và không thể hiện một số hành vi thất thường của nhóm mở rộng đôi khi và không phải lúc khác.

Tuy nhiên, có các liên hệ đang được hiển thị trong nhóm mà chúng không thuộc về. Và cũng có một số nhóm có liên hệ với họ nhưng sẽ không hiển thị bất kỳ liên hệ nào. Vì vậy, có vẻ như vấn đề getChildrenCursor() hiện có thể được giải quyết.

Nhưng bây giờ có vẻ như đây là vấn đề về cách Trình tạo con trỏ được khởi tạo theo phương thức onCreateLoader(). Có phải CursorLoader được trả về theo phương thức onCreateLoader() cho con trỏ được khởi tạo không đúng cách không?

CẬP NHẬT

Vì vậy, tôi đã xác định được một trong những vấn đề của tôi. Trong phương thức getChildrenCursor() nếu tôi chuyển nhómId vào phương thức initLoader(), thì trong phương thức onCreateLoader(), khi CursorLoader được tạo, nó sẽ nhận được thông số groupid chính xác cho truy vấn. Tuy nhiên, trong số onLoadFinished(), cuộc gọi tới setChildrenCursor() đang được chuyển qua id trình tải cho thông số đầu tiên không phải là nhóm đặt giá. Tôi đoán tôi phải ánh xạ id của trình tải lên các vị trí nhóm trong một số cấu trúc dữ liệu. Nhưng tôi không chắc đây có phải là cách tiếp cận tốt nhất hay không. Có ai có bất cứ đề nghị?

+0

Tôi vừa thực hiện việc này, nhưng chưa sử dụng Trình tải trang CursorLoader, do đó, việc đó đang ném tôi ...Trong quá trình thực hiện của tôi, getChildrenCursor trả về một con trỏ. Với loadermanager, con trỏ/dữ liệu đó thực sự đi đâu? Nếu bạn không cho con trỏ vào hàm tạo, cái gì đang được đưa vào 'getChildrenCursor' như groupCursor? – Barak

+0

GroupCursor đã được thiết lập trong phương thức onLoadFinished() của LoaderManager. Tôi đã bước qua mã với một trình gỡ lỗi và trong phương thức getChildenCursor(), groupCursor luôn được định nghĩa. – toobsco42

+0

Tôi không biết, nó thực sự có vẻ như bạn không phải lúc nào cũng nhận được một con người con ... – Barak

Trả lời

16

Vì vậy, tôi đã tìm ra rằng tôi cần phải lập bản đồ loaderids để groupPositions và điều này giải quyết vấn đề của tôi:

Đây là lớp học của tôi Fragment mà thực hiện các 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); 
    } 
} 

Và đây là bộ chuyển đổi của tôi mà lớp con 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

Lưu ý, Android có một SparseIntArray (xem http://developer.android.com/reference/android/util/SparseIntArray.html) tốt hơn để sử dụng hơn HashMap và phục vụ cùng một mục đích. –

+0

Cảm ơn bạn đã đề xuất. Tôi sẽ thử. – toobsco42

+1

Theo phân tích này: http://mobile.dzone.com/articles/tweaking-your-android chúng đạt được kết quả tương tự cho số phần tử tôi có trong bản đồ băm nhỏ hơn 1.000. – toobsco42

1

Tôi có kinh nghiệm xấu khi sử dụng ExpandableListView. Hành vi của nó trong các phiên bản Android khác nhau. Nếu bạn chưa quá sâu vào nó, bạn có thể xem xét việc thiết kế lại giao diện của mình.

Dù sao, với câu hỏi của bạn, tôi khuyên bạn nên xem lại 3 điểm này.

Thứ nhất, trong lệnh gọi init con trỏ nạp

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

Các groupId bạn thông qua vào là giá trị của ContactsContract.Groups._ID. Sau đó, bạn sử dụng id này trong tham số đầu tiên của setChildrenCursor. Điều này có lẽ là sai. Thay vì chuyển nhómId vào bộ nạp initLoader, hãy thử chuyển vào vị trí con trỏ nhóm. Ví dụ:

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

Thứ hai, bạn có thể thấy rằng trong các mã tôi đã gợi ý ở trên, có lẽ bạn nên gọi restartLoader chỉ khi nạp không phải là null và bộ nạp isReset là sai.

Thứ ba, bạn cần trả về một giá trị cho cuộc gọi getChildrenCursor, điều mà tôi cho là có lẽ là rỗng.

+0

Điều này chắc chắn có ý nghĩa và dường như gần gũi hơn với chính xác những gì tôi muốn. Nhưng tôi vẫn gặp vấn đề mà tôi đã đề cập trong CẬP NHẬT trong câu hỏi trên. – toobsco42

2

Trong trường hợp của tôi, tôi sử dụng đối số đầu tiên của initLoader (hoặc restartLoader) để cung cấp cho vị trí nhóm để gọi lại và sử dụng Bundle để lấy dữ liệu con trong getChildrenCursor.

Như sau;

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

điều này sẽ không hoạt động nếu bạn cần phải khởi động lại bộ nạp, vì các tính năng bổ sung chỉ được sử dụng trong thời gian xây dựng bộ tải. –

Các vấn đề liên quan