美文网首页Flutter学习
flutter 列表数据缓存和预加载

flutter 列表数据缓存和预加载

作者: liboxiang | 来源:发表于2020-06-06 16:21 被阅读0次

ListView等列表数据缓存管理

目的

  • 列表数据量很多(几千或者过万)的时候进行本地数据缓存管理,以减少内存占有量。
  • 实现列表数据的预加载

主要原理

数据缓存管理.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;
  }
}

相关文章

  • flutter 列表数据缓存和预加载

    ListView等列表数据缓存管理 目的 列表数据量很多(几千或者过万)的时候进行本地数据缓存管理,以减少内存占有...

  • flutter 列表数据缓存和预加载

    ListView等列表数据缓存管理 dart pub :https://pub.dev/packages/list...

  • Exoplayer2学习-- 通过CacheUtil实现预加载(

    这篇文章记录exoplayer2中如何预加载数据流(或者叫缓存数据), 以及怎么删除已预加载的数据 下载功能: ...

  • iOS 预加载列表数据

    预加载(TableView或者CollectionView):在用户阅读了最新页码数据的70%时(根据实际情况调节...

  • 网络优化

    接口访问 减小数据包大小缓存时间控制版本控制keep-alive 图片加载 减小图片大小缓存预加载大小图组合

  • 预加载与智能预加载 (VIA)

    预加载与智能预加载(iOS) 网络与性能 预加载无限滚动列表Threshold惰性加载智能预加载 总结 前两次的分...

  • HTTP缓存详解

    除 HTTP 缓存之外,Web 性能优化还有很多其他途径,比如预加载和预渲染、脚本异步载入等 缓存的原因 减少服务...

  • 深度理解 图片预加载和缓存机制

    本文通过两个图片预加载案例引起的缓存相关问题,探讨了图片预加载处理技术,和浏览器网络请求以及缓存机制的一些问题。 ...

  • Fluid 配置高阶调优

    整理的实践调优列表: 设置 AlluxioRuntime 的属性 数据预加载 Master 高可用模式 开启 Fu...

  • 微信webview开发的那些坑

    安卓和ios不同的视频播放特性 安卓刷新无效,缓存 图片预加载

网友评论

    本文标题:flutter 列表数据缓存和预加载

    本文链接:https://www.haomeiwen.com/subject/pozxtktx.html