ListView等列表数据缓存管理
目的
- 列表数据量很多(几千或者过万)的时候进行本地数据缓存管理,以减少内存占有量。
- 实现列表数据的预加载
主要原理
![](https://img.haomeiwen.com/i4188482/15582e62a2087fd8.png)
- 红色部分为存储在内存中的数据
- 绿色表示App正在使用的数据(包括屏幕上显示的和列表预加载的)
- 缓存使用链表管理,以利于添加和删除数据
- 链表中缓存的是以下对象:
class ListCacheItem<T> extends LinkedListEntry<ListCacheItem> {
int index;
///列表中使用和数据库中存储的都是data数据
T _data;
}
- 列表中使用和数据库中存储的都是
ListCacheItem
中的_data
数据
主要设计思路如上图所示。其流程如下:
(1) 从数据库请求首页数据,将数据转换成ListCacheItem
存储到链表中的同时保存到本地数据库
(2) 列表往上滚动,当列表显示或者预加载的数据快到末尾的时候执行网络请求加载更多
(3) 将服务器返回的数据转换成ListCacheItem
存储到链表末尾,并保存到数据库。同时删除链表头部不再被列表使用的数据
(4) 如果继续往上滚动,重复第(2)和第(3)步
(5)如果往下滚动,则判断列表使用的数据是否接近链表头部了,如果接近了就先往头部填充ListCacheItem
(此时_data
为空),然后从数据库加载对应的数据,得到数据后判断对应的ListCacheItem
是否还在链表中,如果还在就将数据传递给对应的_data
,同时判断数据是否正被列表使用(则正在被用于构建UI),如果正被列表使用就执行UI刷新操作。同时删除链表尾部不再被列表使用的数据
(6) 如果往上滚动,则逻辑类似步骤(5)或者步骤(2)
相关widget
- 列表中的item要包裹在
ListCacheBuilder
中
备注:mount的时候执行的_checkIndex
方法是检查对应的ListCacheItem
是否在链表范围内,如果超出了,则链表缓存数据要做处理,以保证列表正在使用的数据都在链表缓存中。设计该逻辑的原因是flutter中的列表会自动缓存部分widget,也就是列表滚动过程中,UI的显示不一定执行children的创建。为了准确知道当前列表显示那些数据,所以在mount的时候做检查判断。
源码
import 'dart:async';
import 'dart:collection';
import 'package:flutter/material.dart';
typedef Widget ListWidgetBuilder<T>(ListCacheItem<T> cacheItem, BuildContext context);
const int ExtraExtent = 10;
enum ItemIndexDirection {
///获取更多数据,下标增长
bigger,
///获取旧数据,下标减小
smaller,
}
class ListCacheBuilder<T> extends StatefulWidget {
final ListCacheItem<T> cacheItem;
final ListWidgetBuilder<T> builder;
const ListCacheBuilder({Key key, @required this.builder, @required this.cacheItem})
: assert(builder != null && cacheItem != null),
super(key: key);
@override
StatefulElement createElement() => ListCacheElement(this);
@override
_ListCacheBuilderState<T> createState() => _ListCacheBuilderState<T>();
}
class ListCacheElement extends StatefulElement {
ListCacheElement(ListCacheBuilder widget) : super(widget);
@override
void mount(Element parent, newSlot) {
super.mount(parent, newSlot);
(widget as ListCacheBuilder).cacheItem._isInView = true;
(widget as ListCacheBuilder).cacheItem._checkIndex();
}
@override
void unmount() {
super.unmount();
(widget as ListCacheBuilder).cacheItem._isInView = false;
(widget as ListCacheBuilder).cacheItem._onDataSetCall = null;
}
}
class _ListCacheBuilderState<T> extends State<ListCacheBuilder<T>> {
@override
void initState() {
super.initState();
if (widget.cacheItem != null) {
widget.cacheItem._isInView = true;
}
setupRefresh();
}
void setupRefresh() {
if (widget.cacheItem != null) {
widget.cacheItem._onDataSetCall = (data) {
setState(() {});
};
}
}
@override
void didUpdateWidget(ListCacheBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.cacheItem != null) {
widget.cacheItem._isInView = true;
}
setupRefresh();
}
@override
Widget build(BuildContext context) {
return widget.builder(widget.cacheItem, context);
}
}
class ListCacheItem<T> extends LinkedListEntry<ListCacheItem> {
ListCacheItem({@required this.index, @required T data, @required ListItemMountCallBack mountCall})
: assert(mountCall != null),
_data = data,
_mountCall = mountCall;
ListItemMountCallBack _mountCall;
int index;
T _data;
T get data => _data;
set data(T tmp) {
_data = tmp;
if (_onDataSetCall != null && _isInView) {
_onDataSetCall(_data);
}
}
void _checkIndex() {
if (_mountCall != null) {
_mountCall.checkIndex(index);
}
}
///是否正在视图中
bool _isInView = false;
///可能由于加载数据慢问题造成一开始data为空,等有数据的时候,会触发onDataSetCall
ValueChanged<T> _onDataSetCall;
}
class ListDataCacheManager<T> {
ListDataCacheManager({
this.suggestCacheNum = 100,
this.dbLoadExtent = 20,
this.networkLoadExtent = 25,
this.dbLoadPageCount = 50,
this.loadCacheData,
this.requestNetworkData,
}) : assert(dbLoadExtent > 0 && networkLoadExtent > 0 && dbLoadPageCount > 0),
assert(suggestCacheNum > dbLoadExtent) {
_itemMountCallBack = ListItemMountCallBack(_checkIndex);
}
///设置缓存数量,实际缓存数量不一定是该数值
///建议设置为大于_dbLoadPageCount + 当前页面显示条数
final int suggestCacheNum;
///还差几条数据滚到_cacheList尽头的时候进行数据库查询更多数据
final int dbLoadExtent;
///还差几条数据滚到_cacheList尽头的时候进行网络请求获取更多数据
final int networkLoadExtent;
///数据库查找数据,每次查询多少条出来
final int dbLoadPageCount;
Future<List<T>> Function(int startIndex, int endIndex) loadCacheData;
void Function(int startIndex) requestNetworkData;
///内存中存储的数据
LinkedList<ListCacheItem> _cacheList = LinkedList<ListCacheItem>();
///上一个返回的item
ListCacheItem _lastItem;
///当前缓存,内存或者数据库存在的数据总数
int _itemCount = 0;
int get itemCount => _itemCount;
///item mount的时候回调
ListItemMountCallBack _itemMountCallBack;
///用于区分数据库中数据
String get type => '${this.hashCode.toString()}_$T';
///表识正在加载网络数据
bool _isLoadingMoreNetworkData = false;
bool get isLoadingNetworkData => _isLoadingMoreNetworkData;
void enableNetworkLoading() {
_isLoadingMoreNetworkData = false;
}
void unableNetworkLoading() {
_isLoadingMoreNetworkData = true;
}
///晴空所有数据,包括数据库中数据
void clear() {
_itemMountCallBack?.close();
_itemMountCallBack = ListItemMountCallBack(_checkIndex);
_cacheList.clear();
_itemCount = 0;
}
///index自动递增
void addAll(List<T> list) {
list?.forEach((f) {
_cacheList.add(ListCacheItem<T>(index: itemCount, data: f, mountCall: _itemMountCallBack));
_itemCount++;
});
_clearNotInViewItemInCacheList(ItemIndexDirection.smaller);
}
///获取index数据,一般只用于构建列表的时候获取数据,其他情况使用注意会造成缓存数据和显示数据不匹配问题
ListCacheItem<T> dataAtIndex(int index) {
assert(index < itemCount && index >= 0);
if (index >= itemCount || index < 0) {
return null;
}
_checkIndex(index);
ListCacheItem<T> result = _findData(index);
if (result != null) {
_lastItem = result;
}
return result;
}
///检查判断index,看是否需要请求网络数据或者从数据库加载数据
void _checkIndex(int index) {
if (index + dbLoadExtent >= _cacheList.last.index && _cacheList.last.index + 1 < itemCount) {
ListCacheItem<T> lastItem = _cacheList.last;
_addEmptyDataToCacheList(ItemIndexDirection.bigger, index);
///加载数据库中数据,加载当前_cacheList后面的数据
if (_cacheList.last != lastItem && lastItem.next != null && _cacheList.last != null) {
Timer.run(() {
_loadDbData(
startItem: lastItem.next,
endItem: _cacheList.last,
direction: ItemIndexDirection.bigger,
);
});
}
}
if (index < _cacheList.first.index + dbLoadExtent && _cacheList.first.index > 0) {
ListCacheItem<T> firstItem = _cacheList.first;
_addEmptyDataToCacheList(ItemIndexDirection.smaller, index);
///加载数据库中数据,加载当前_cacheList前面的数据
if (_cacheList.first != null && _cacheList.first != firstItem && firstItem.previous != null) {
Timer.run(() {
_loadDbData(
startItem: _cacheList.first,
endItem: firstItem.previous,
direction: ItemIndexDirection.smaller,
);
});
}
}
if (!_isLoadingMoreNetworkData && index + networkLoadExtent >= itemCount && requestNetworkData != null) {
///加载网络数据
_isLoadingMoreNetworkData = true;
Timer.run(() {
requestNetworkData(itemCount);
});
}
}
///向_cacheList中添加数据,如果下标index没超出范围,每次添加的数量为_dbLoadPageCount,
void _addEmptyDataToCacheList(ItemIndexDirection direction, int currentIndex) {
assert(direction != null);
if (direction == ItemIndexDirection.bigger) {
for (int index = 0;
!(_cacheList.last.index > currentIndex + dbLoadExtent && index >= dbLoadPageCount) &&
_cacheList.last.index + 1 < itemCount;
index++) {
_cacheList.add(ListCacheItem<T>(data: null, index: _cacheList.last.index + 1, mountCall: _itemMountCallBack));
}
} else {
for (int index = 0;
!(_cacheList.first.index + dbLoadExtent < currentIndex && index >= dbLoadPageCount) &&
_cacheList.first.index > 0;
index++) {
_cacheList
.addFirst(ListCacheItem<T>(data: null, index: _cacheList.first.index - 1, mountCall: _itemMountCallBack));
}
}
}
///查找cacheList中数据,如果查处当前_cacheList缓存范围,则返回的
ListCacheItem<T> _findData(int index) {
assert(index != null && index >= 0);
///判断是否为_lastItem临近的数据
if (_lastItem != null && _lastItem.list != null) {
if (_lastItem.index >= index) {
ListCacheItem<T> tmpItem = _lastItem;
do {
if (tmpItem.index == index) {
break;
}
tmpItem = tmpItem.previous;
} while (tmpItem != null);
return tmpItem?.index == index ? tmpItem : null;
} else {
ListCacheItem<T> tmpItem = _lastItem.next;
while (tmpItem != null) {
if (tmpItem.index == index) {
break;
}
tmpItem = tmpItem.next;
}
return tmpItem?.index == index ? tmpItem : null;
}
}
//判断cacheList缓存中是否有该index数据
else if (index >= _cacheList.first.index && index <= _cacheList.last.index) {
if (index * 2 > (_cacheList.first.index + _cacheList.last.index)) {
///偏尾部
ListCacheItem<T> tmpItem = _cacheList.last;
do {
if (tmpItem.index == index) {
break;
}
tmpItem = tmpItem.previous;
} while (tmpItem != null);
return tmpItem?.index == index ? tmpItem : null;
} else {
///偏头部
return _cacheList.singleWhere((test) => test.index == index, orElse: () => null);
}
}
return ListCacheItem(index: index, mountCall: _itemMountCallBack, data: null);
}
///startItem是index小的,endItem是index大的
void _loadDbData(
{@required ListCacheItem<T> startItem,
@required ListCacheItem<T> endItem,
@required ItemIndexDirection direction}) {
assert(startItem != null && endItem != null);
if (loadCacheData != null) {
loadCacheData(startItem.index, endItem.index).then((tmpList) {
bool isReversed = false;
ListCacheItem<T> item;
Iterable<T> iterable;
if (startItem.list != null) {
///startItem还在_cacheList中
iterable = tmpList;
item = startItem;
} else if (startItem.list == null && endItem.list != null) {
///startItem不在_cacheList中,endItem还在_cacheList中
iterable = tmpList.reversed;
item = endItem;
isReversed = true;
}
if (item != null) {
iterable?.firstWhere((f) {
if (f != null) {
item.data = f;
}
if (isReversed) {
item = item.previous;
if (item == null || item.list == null) {
///到头了或者item已经不在_cacheList中
return true;
}
} else {
item = item.next;
if (item == null || item.list == null) {
///到头了或者item已经不在_cacheList中
return true;
}
}
return false;
}, orElse: () => null);
}
///清反方向的数据
_clearNotInViewItemInCacheList(
direction == ItemIndexDirection.bigger ? ItemIndexDirection.smaller : ItemIndexDirection.bigger);
});
}
}
///清除不在视图中的数据
void _clearNotInViewItemInCacheList(ItemIndexDirection direction) {
assert(direction != null);
if (_cacheList != null && _cacheList.isNotEmpty) {
if (direction == ItemIndexDirection.smaller) {
///删下标小的那边
ListCacheItem<T> tmpItem = _cacheList.first;
while (tmpItem._isInView != true && _cacheList.length > suggestCacheNum) {
if (tmpItem.index - _cacheList.first.index >= dbLoadExtent + ExtraExtent) {
_cacheList.remove(_cacheList.first);
}
tmpItem = tmpItem.next;
}
} else if (direction == ItemIndexDirection.bigger) {
///删下标大的那边
ListCacheItem<T> tmpItem = _cacheList.last;
while (tmpItem._isInView != true && _cacheList.length > suggestCacheNum) {
if (_cacheList.last.index - tmpItem.index >= dbLoadExtent + ExtraExtent) {
_cacheList.remove(_cacheList.last);
}
tmpItem = tmpItem.previous;
}
}
}
}
}
class ListItemMountCallBack {
ValueChanged<int> _mount;
bool _enable = true;
ListItemMountCallBack(ValueChanged<int> mount) : _mount = mount;
void checkIndex(int index) {
if (_enable && _mount != null) {
_mount(index);
}
}
///关闭回调
void close() {
_enable = false;
_mount = null;
}
}
网友评论