В моем приложении для Android есть базовая реализация RecyclerView
, которая показывает записи из базы данных, используя Комната с библиотекой Paging .
Метод Даокоторый выбирает записи из базы данных:
@Query("SELECT * FROM entries")
abstract fun findAll(): DataSource.Factory<Int, Entry>
И вот как LiveData
построен из возвращенного DataSource.Factory
в моем ViewModel
:
private val config = PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(512)
.setPrefetchDistance(256)
.setPageSize(256)
.build()
val entries = LivePagedListBuilder(entryService.findAll(), config).build()
// (entryService here just calls through to the Dao)
Я просто наблюдаю заэто LiveData
и передать обновленный PagedList
моему RecyclerView.Adapter
.
Соответствующему фрагменту RecyclerView.Adapter
:
private val differ = AsyncPagedListDiffer<Entry>(this, DIFF_CALLBACK)
fun submitList(list: PagedList<Entry>) {
differ.submitList(list)
}
(я не использую PagedListAdapter
, потому что яМне понадобится более детальный контроль в моем адаптере позже)
Кажется, все работает нормально, но когда я начал отслеживать использование памяти приложением, я начал подозревать, что элементы PagedList
не обрабатываются должным образом.
Я добавил следующее Callback
в submitList()
:
list.addWeakCallback(differ.currentList?.snapshot(), object : PagedList.Callback() {
override fun onChanged(position: Int, count: Int) {
Log.d("_tag", "Position: $position | Changed: $count" +
" | Size: ${list.size}")
}
override fun onInserted(position: Int, count: Int) {
Log.d("_tag", "Position: $position | Inserted: $count" +
" | Size: ${list.size}")
}
override fun onRemoved(position: Int, count: Int) {
Log.d("_tag", "Position: $position | Removed: $count" +
" | Size: ${list.size}")
}
})
После этого я вставил 5 000 записей в базу данных, и соответствующая запись в журнале была следующей:
D / _tag: Position: 0 |Вставлено: 512 |Размер: 512
Это нормально, это ожидаемый результат.
Но когда я начинаю прокручивать список вниз, записи журнала выглядят следующим образом:
D / _tag: Позиция: 512 |Вставлено: 256 |Размер: 768
D / _tag: Позиция: 768 |Вставлено: 256 |Размер: 1024
D / _tag: Позиция: 1024 |Вставлено: 256 |Размер: 1280
D / _tag: Позиция: 1280 |Вставлено: 256 |Размер: 1536
И тогда журналы останавливаются здесь, больше нет записей журнала, независимо от того, сколько я прокручиваю вниз после этого пункта.
Мои вопросы относительно этого поведения:
Почему onRemoved()
никогда не вызывается?
Является ли PagedList
постоянно растущим списком, а предметы остаются в памяти?
Разве смысл пейджинга не в том, чтобы загружать новые записи по запросу и удалять также старых (не видимых)?
Почемуостанавливаются ли журналы после 1536-й записи, даже если я прокручиваю путь мимо этой записи?
Странно то, что записи отображаются правильно, даже если прокрутить до самого конца списка.
Я был бы очень признателен, если бы кто-то мог объяснить мне поведение.
(также приветствуются ответы на Java)
EDIT :
Код всего адаптера:
class EntryListAdapter
: RecyclerView.Adapter<EntryListAdapter.EntryViewHolder>() {
private val differ = AsyncPagedListDiffer<Entry>(this, DIFF_CALLBACK)
fun submitList(list: PagedList<Entry>) {
differ.submitList(list)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
EntryViewHolder {
val inflater = LayoutInflater.from(parent.context)
val entryItemView = inflater.inflate(R.layout.item_entry, parent, false)
return EntryViewHolder(entryItemView)
}
override fun onBindViewHolder(holder: EntryViewHolder, position: Int) {
differ.getItem(position)?.let { holder.bindTo(it) }
}
override fun getItemId(position: Int): Long {
return differ.getItem(position)?.id ?: RecyclerView.NO_ID
}
override fun getItemCount(): Int {
return differ.currentList?.size ?: 0
}
inner class EntryViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView) {
fun bindTo(entry: Entry) {
itemView.textEntryTitle.text = entry.title
}
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Entry>() {
override fun areItemsTheSame(entry1: Entry, entry2: Entry): Boolean {
return entry1.id == entry2.id
}
override fun areContentsTheSame(entry1: Entry, entry2: Entry): Boolean
{
return entry1 == entry2
}
}
}
}
EDIT2 :
Макет Fragment
, в котором размещается RecyclerView
:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mainRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>