Open
Description
先来看一个测试用例
/// Rebuild the widget after 'refresh' or 'loadmore' is completed, check
/// their callbaks can get a correct timestamp(defined in 'builder').
/// If get a wrong timestamp, consider memory leak.
testWidgets("test memory leak", (tester) async {
final controller = RefreshController();
int currentTimestamp = 0;
await tester.pumpWidget(MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: Builder(
key: Key('builder'),
builder: (context) {
int timestamp = DateTime.now().millisecondsSinceEpoch;
currentTimestamp = timestamp;
print('$timestamp ---- build');
return SmartRefresher(
controller: controller,
enablePullUp: true,
onLoading: () {
controller.loadComplete();
print('$timestamp --- onLoading');
expect(timestamp, currentTimestamp);
tester
.firstElement(find.byKey(Key('builder')))
.markNeedsBuild();
},
onRefresh: () {
controller.refreshCompleted();
print('$timestamp --- onRefresh');
expect(timestamp, currentTimestamp);
tester
.firstElement(find.byKey(Key('builder')))
.markNeedsBuild();
},
child: ListView(
children: List<Widget>.generate(20, (index) {
return Container(height: 50, child: Text('Item: $index'));
}),
),
);
}),
),
));
// test pull refresh
await tester.drag(find.byType(Scrollable), const Offset(0, 100.0),
touchSlopY: 0.0);
await tester.pumpAndSettle();
await tester.drag(find.byType(Scrollable), const Offset(0, 100.0),
touchSlopY: 0.0);
await tester.pumpAndSettle();
// test load more
controller.position!.jumpTo(controller.position!.maxScrollExtent - 30.0);
await tester.drag(find.byType(Scrollable), const Offset(0, -30.0));
await tester.pumpAndSettle();
await tester.drag(find.byType(Scrollable), const Offset(0, -30.0));
await tester.pumpAndSettle();
});
1628278954679 ---- build
1628278954679 --- onRefresh
1628278955294 ---- build
1628278955294 --- onRefresh
1628278955405 ---- build
1628278955405 --- onLoading
1628278955452 ---- build
1628278955405 --- onLoading
══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════
The following TestFailure object was thrown while dispatching notifications for
RefreshNotifier<LoadStatus>:
Expected: <1628278955452>
Actual: <1628278955405>
在加载更多的onLoading回调中,打印的时间戳是永远是首次回调onLoading得到的那个,初步考虑是内存泄漏了,onLoading持有了应该要被销毁的Builder。通过devtools检查,发现确实存在两个SmartRefresher实例,gc无法回收。
初步判断问题可能出在这里,因为我没有设置header和footer,使用的是默认的:
class SmartRefresherState extends State<SmartRefresher> {
final RefreshIndicator defaultHeader =
defaultTargetPlatform == TargetPlatform.iOS
? ClassicHeader()
: MaterialClassicHeader();
final LoadIndicator defaultFooter = ClassicFooter();
}
这里header实例和footer实例保存在了State中,所以父节点重建时,这两个widget不会被重建,而且他们应该还持有了SmartRefresher对象但是没有被正确释放。
Metadata
Metadata
Assignees
Labels
No labels