0

В нашей компании мы разрабатываем приложение, которое отображает временную шкалу. Мы готовы разрешить пользователю прокручивать его (почти) на неопределенный срок.Как реализовать бесконечный CursorAdapter?

В настоящее время существует 2 фактов, чтобы рассмотреть следующие вопросы:

  • Загрузка большой курсор может иметь плохое влияние на спектаклях (особенно верно для старых устройств)
  • курсоров seem to have a size limit 1Мб

В текущей реализации мы загружаем по умолчанию 40 элементов, а затем, когда пользователь прокручивается за пределы определенного порога, мы повторяем запрос, увеличивая ограничение до 40 + 20 элементов и так далее.

Однако этот подход выглядит довольно слабым, потому что он сталкивается с обоими принципами, изложенными ранее: запрос в конечном итоге станет довольно большим, и в какой-то момент курсор может достигнуть предела памяти 1 МБ (мы загружаем много строк).

Теперь мы думаем о эксплуатируя MergeCursor и действуйте следующим образом:

  1. нагрузки курсор из 40 пунктов в первый раз
  2. Когда пользователь прокручивает выше определенного уровня, мы загружаем другой курсор, содержащий следующие 40 элементов и установите в адаптере курсора MergeCursor, который соединяет новый курсор с предыдущим.
  3. Продолжайте этот подход до тех пор, пока не будет выполнено X шагов (в зависимости от тестов), чтобы избежать попадания какого-либо исключения OOM. В конце курсор временной шкалы будет конкатенацией курсоров X.

Что вы думаете об этом подходе? Любая слабость (кроме накладных расходов, которые должны быть небольшими)?

В случае, можете ли вы указать/описать лучшие решения?

Заранее спасибо

+0

Просто интересно вслух: если вы ограничите данные курсора только тем, что вам нужно для показа целей, будет ли еще какой-либо практический риск столкнуться с возможными описанными проблемами? Я использовал «CursorAdapter» с тысячами элементов, не замечая при этом больших результатов производительности (хотя он становится более сложным с быстрыми индексами прокрутки и раздела). Тем не менее, интересный вопрос! Я предполагаю, что решение, содержащее ограниченное количество страниц, решит проблему. Любые дополнительные ограничения? То есть вышеупомянутая быстрая прокрутка и/или секционирование индексации? –

+0

use AbstractWindowedCursor then – pskink

+0

спасибо за комментарии, я исследовал больше и, вероятно, нашел решение моих сомнений. Дайте мне знать, если я ошибаюсь. – Giordano

ответ

0

в комментариях pskink предлагает использовать AbstractWindowedCursor.

Я не был знаком с этим классом и немного исследовал его. Оказывается, что SQLiteCursor уже расширяет его. В документации указано следующее:

Курсор владеет окном курсора, которое он использует. Когда курсор закрыт, его окно также закрывается. Аналогично, когда окно, используемое курсором, изменяется, его старое окно закрывается. Эта политика строгого владения гарантирует, что окна курсора не будут протекать.

Это означает, что в любой момент времени в памяти хранится только небольшая часть данных, запрошенных из БД.Это интересная часть кода в SQLiteCursor:

@Override 
public boolean onMove(int oldPosition, int newPosition) { 
    // Make sure the row at newPosition is present in the window 
    if (mWindow == null || newPosition < mWindow.getStartPosition() || 
      newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) { 
     fillWindow(newPosition); 
    } 

    return true; 
} 

@Override 
public int getCount() { 
    if (mCount == NO_COUNT) { 
     fillWindow(0); 
    } 
    return mCount; 
} 

private void fillWindow(int requiredPos) { 
    clearOrCreateWindow(getDatabase().getPath()); 

    try { 
     if (mCount == NO_COUNT) { 
      int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0); 
      mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true); 
      mCursorWindowCapacity = mWindow.getNumRows(); 
      if (Log.isLoggable(TAG, Log.DEBUG)) { 
       Log.d(TAG, "received count(*) from native_fill_window: " + mCount); 
      } 
     } else { 
      int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 
        mCursorWindowCapacity); 
      mQuery.fillWindow(mWindow, startPos, requiredPos, false); 
     } 
    } catch (RuntimeException ex) { 
     // Close the cursor window if the query failed and therefore will 
     // not produce any results. This helps to avoid accidentally leaking 
     // the cursor window if the client does not correctly handle exceptions 
     // and fails to close the cursor. 
     closeWindow(); 
     throw ex; 
    } 
} 

Это означает 2 вещи:

  1. она должна быть безопасной, чтобы загрузить весь набор данных, так как он не будет полностью храниться в памяти. Только часть его (CursorWindow) находится в памяти в любое время. Предел размера 1 Мбайт либо (возможно) миф, либо относится к объекту CursorWindow, и в этом случае он является безопасным размером
  2. Производительность не должна быть проблемой, так как курсор всегда работает на фиксированном количестве данные. Вероятно, исходный запрос (который вычисляет общий размер набора данных, хранящийся в переменной mCount) может иметь некоторое влияние на воспринимаемую производительность. Мне нужно еще раз проверить это.

В заключение, скорее всего, нет необходимости использовать трюк MergeCursor или чрезмерно беспокоиться о OOM.

Я мог бы лучше изучить исходный код, но я был немного обманут тем, что я читал в Интернете.

+0

Я проверил, размер окна - 'com.android.internal.R.integer.config_cursorWindowSize' килобайты (см. Конструктор' CursorWindow'). В источниках, которые я проверил, значение было установлено на 2048, однако оно, скорее всего, изменяется в зависимости от устройства. «Курсор» хранит в этом буфере столько строк, сколько может, и когда он достигает позиции, которая находится за пределами этого окна, создается новый 'CursorWindow', а предыдущий очищается. Довольно аккуратно. – Giordano