处理图像/视频和导航器的内存使用情况
Handle memory usage with images / videos and Navigators flutter
对不起大 post 提前...
我的应用程序是一个与 Instagram 非常相似的社交网络。
模式 :
- 用户个人资料(包含缩略图列表,未播放视频)
- Select照片(打开页面
Navigator.push()
有照片/播放视频
- Select 另一个用户个人资料(例如在视频的评论中)
- 再次查看缩略图列表,然后 post 带有照片的列表 - 播放视频等等...
这就像一个无限的“配置文件 - 提要”循环。有了这样的逻辑,在我 运行 进入 OutOfMemory
错误之前,我用 Navigator.push() 达到了或多或少的 30 页。
Flutter 工具只显示 lost connection to device
,但我使用 Navigator
的次数越多,应用程序变得越慢,最终崩溃,所以我 99% 确定这是由于内存使用造成的。
由于 post 列表中的滚动,这种情况在 100% 的时间或多或少会发生 1 页差异。
如果我不在图片/视频列表中滚动太多,内存使用量每页或多或少会增加20MB。
我已经计划缩小我的图像,但这充其量只是延迟了问题。
问题 :
- 这种“无限页面”是否有可能永远不会 运行 进入
OutOfMemory
异常?
- 我知道
StateFul Widgets
中有一个deactivate()
方法可以在调用Navigator.push
之后使用(dispose()
没有被调用,因为我们没有删除树上的任何东西),也许应该在那里做一些工作?
- 我应该做些什么来处理
Navigator
堆栈吗?我不想 pop()
旧页面,因为我需要返回到第一个页面打开
在这种情况和这种逻辑下,这意味着如果我可能在 Instagram 中浏览 100 页,这也会在 100% 时崩溃。
我不确定是否有人会走那么远,也许这就是他们所指望的...如果没有办法阻止 OutOfMemory
,唯一的解决方案可能是将其延迟到用户例如至少看了 100 页...
WORKAROUND 我在理论上找到了但不确定在代码中是否可行 :
到目前为止我想到的唯一解决方案是允许用户 push()
一定数量的页面,并在 Navigator
堆栈中最多保留 20 页,但不删除第一个页。
所以内存中总会有20页。
如果这是唯一的解决方案,有人可以提供有关如何处理 Navigator
堆栈中特定页面的示例吗?
此外,我注意到当您返回 Navigator.pop()
时,内存并没有减少,为什么?
编辑 :
为了显示照片/视频,我基本上使用了 2 packages
:
- flutter_staggered_grid_view :不是官方包,但是拉取请求通过禁用
automaticKeepAlives
. 使其可用
- scrollable_positioned_list :也适用于构建器和
automaticKeepAlives: false
.
我还在应用程序的一部分中使用了 Slivers,但再次使用了前面提到的包中的 SliverStaggeredGrid.countBuilder
。
我认为您已经正确指出,根本问题在于推送大量页面的“无限”性质,同时将所有旧页面保留在内存中。
您尚未发布任何源代码,但我认为此时它不相关。而且从问题描述上看,你的能力很强,所以我们大概可以从高层次上讨论这个问题。正如一些评论指出的那样,也许每个页面都有优化的空间,但即便如此,它也只是延迟问题。简单来说,即使每个页面只是一个Text widget,它最终也会运行陷入OOM问题。
解决此问题的一种方法是构建您自己的路由堆栈。在最近的 Flutter 更新中,这变得容易多了,他们引入了所谓的“Navigator 2.0”,您可以从 official docs.
了解更多信息
本质上,您可以拥有自己的“堆栈”(数据结构)来跟踪用户单击的内容,以便“后退按钮”正常运行。但是在每次跳转时,您不是推送新页面,而是字面上“替换”当前页面。同样,当用户返回时,您不会“弹出”导航器,因为它下面什么也没有。相反,您查找自己的“历史堆栈”并重新加载上一页的内容。这样,您可以进一步延迟问题(直到您的堆栈溢出,就像本网站的名称一样)。
一旦完成,也许您可以做进一步的优化,就是让一些最近的页面保持活动状态,而不是立即销毁它们。例如,也许您可以让最近的 3-5 个页面保持活动状态,以便用户可以更快地返回到它们(无需加载)。无论如何,这个可选的优化可能会覆盖 95% 的用例。
编辑:
既然你在评论中提到你想要一个 Navigator 2.0 的例子,我拿出我的 IDE 并为你做了一个快速演示。
如您所见,用户可以打开几乎无限量的新页面,并且可以返回到所有这些页面;然而,在任何给定时间,内存中只有 3 个页面,其他页面将被完全销毁并在需要时重新创建。
我在源代码中写了一些简短的注释来帮助您,但是您始终可以从官方文档中学习 Navigator 2.0。
完整源代码:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// This list holds the real "stack": anything user has ever clicked.
// But it only holds minimal info - just the page title.
final List<String> _titles = [
'Home Page',
];
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Navigator(
// The `pages` property here is the most important thing in this demo.
// It literally represents the stack of pages on the screen.
// If we make each item in `_titles` into a page, it'll build them all.
pages: _getPages()
.map((title) => MaterialPage(
key: ValueKey(title),
child: _buildPage(title),
))
.toList(),
onPopPage: (route, result) {
// When user pops a page, we remove it from our list as well.
setState(() => _titles.removeLast());
return route.didPop(result);
},
),
);
}
List<String> _getPages() {
print("Current list: $_titles");
// Uncomment the following line, to skip "optimization" altogether.
// return _titles;
// Optimization: only build the top-most 3 pages.
// First, don't optimize if there are only 3 or fewer pages.
if (_titles.length <= 3) return _titles;
// Otherwise, we only return the last 3 items, so the navigator
// will only build those 3 pages, omitting anything beneath them.
final last3items = [
_titles[_titles.length - 3],
_titles[_titles.length - 2],
_titles[_titles.length - 1],
];
print("Optimizing, only build the top 3 pages: $last3items");
return last3items;
}
Widget _buildPage(title) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: GridView.count(
crossAxisCount: 4,
children: [
for (int i = 1; i <= 10; i++)
Padding(
padding: EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () {
final time = DateTime.now().millisecondsSinceEpoch;
setState(() {
// When user clicks on a button, we add it to our list
_titles.add('Page $i: $time');
});
},
child: Text('Open Page $i'),
),
),
],
),
);
}
}
对不起大 post 提前...
我的应用程序是一个与 Instagram 非常相似的社交网络。
模式 :
- 用户个人资料(包含缩略图列表,未播放视频)
- Select照片(打开页面
Navigator.push()
有照片/播放视频 - Select 另一个用户个人资料(例如在视频的评论中)
- 再次查看缩略图列表,然后 post 带有照片的列表 - 播放视频等等...
这就像一个无限的“配置文件 - 提要”循环。有了这样的逻辑,在我 运行 进入 OutOfMemory
错误之前,我用 Navigator.push() 达到了或多或少的 30 页。
Flutter 工具只显示 lost connection to device
,但我使用 Navigator
的次数越多,应用程序变得越慢,最终崩溃,所以我 99% 确定这是由于内存使用造成的。
由于 post 列表中的滚动,这种情况在 100% 的时间或多或少会发生 1 页差异。
如果我不在图片/视频列表中滚动太多,内存使用量每页或多或少会增加20MB。
我已经计划缩小我的图像,但这充其量只是延迟了问题。
问题 :
- 这种“无限页面”是否有可能永远不会 运行 进入
OutOfMemory
异常? - 我知道
StateFul Widgets
中有一个deactivate()
方法可以在调用Navigator.push
之后使用(dispose()
没有被调用,因为我们没有删除树上的任何东西),也许应该在那里做一些工作? - 我应该做些什么来处理
Navigator
堆栈吗?我不想pop()
旧页面,因为我需要返回到第一个页面打开
在这种情况和这种逻辑下,这意味着如果我可能在 Instagram 中浏览 100 页,这也会在 100% 时崩溃。
我不确定是否有人会走那么远,也许这就是他们所指望的...如果没有办法阻止 OutOfMemory
,唯一的解决方案可能是将其延迟到用户例如至少看了 100 页...
WORKAROUND 我在理论上找到了但不确定在代码中是否可行 :
到目前为止我想到的唯一解决方案是允许用户 push()
一定数量的页面,并在 Navigator
堆栈中最多保留 20 页,但不删除第一个页。
所以内存中总会有20页。
如果这是唯一的解决方案,有人可以提供有关如何处理 Navigator
堆栈中特定页面的示例吗?
此外,我注意到当您返回 Navigator.pop()
时,内存并没有减少,为什么?
编辑 :
为了显示照片/视频,我基本上使用了 2 packages
:
- flutter_staggered_grid_view :不是官方包,但是拉取请求通过禁用
automaticKeepAlives
. 使其可用
- scrollable_positioned_list :也适用于构建器和
automaticKeepAlives: false
.
我还在应用程序的一部分中使用了 Slivers,但再次使用了前面提到的包中的 SliverStaggeredGrid.countBuilder
。
我认为您已经正确指出,根本问题在于推送大量页面的“无限”性质,同时将所有旧页面保留在内存中。
您尚未发布任何源代码,但我认为此时它不相关。而且从问题描述上看,你的能力很强,所以我们大概可以从高层次上讨论这个问题。正如一些评论指出的那样,也许每个页面都有优化的空间,但即便如此,它也只是延迟问题。简单来说,即使每个页面只是一个Text widget,它最终也会运行陷入OOM问题。
解决此问题的一种方法是构建您自己的路由堆栈。在最近的 Flutter 更新中,这变得容易多了,他们引入了所谓的“Navigator 2.0”,您可以从 official docs.
了解更多信息本质上,您可以拥有自己的“堆栈”(数据结构)来跟踪用户单击的内容,以便“后退按钮”正常运行。但是在每次跳转时,您不是推送新页面,而是字面上“替换”当前页面。同样,当用户返回时,您不会“弹出”导航器,因为它下面什么也没有。相反,您查找自己的“历史堆栈”并重新加载上一页的内容。这样,您可以进一步延迟问题(直到您的堆栈溢出,就像本网站的名称一样)。
一旦完成,也许您可以做进一步的优化,就是让一些最近的页面保持活动状态,而不是立即销毁它们。例如,也许您可以让最近的 3-5 个页面保持活动状态,以便用户可以更快地返回到它们(无需加载)。无论如何,这个可选的优化可能会覆盖 95% 的用例。
编辑:
既然你在评论中提到你想要一个 Navigator 2.0 的例子,我拿出我的 IDE 并为你做了一个快速演示。
如您所见,用户可以打开几乎无限量的新页面,并且可以返回到所有这些页面;然而,在任何给定时间,内存中只有 3 个页面,其他页面将被完全销毁并在需要时重新创建。
我在源代码中写了一些简短的注释来帮助您,但是您始终可以从官方文档中学习 Navigator 2.0。
完整源代码:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// This list holds the real "stack": anything user has ever clicked.
// But it only holds minimal info - just the page title.
final List<String> _titles = [
'Home Page',
];
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Navigator(
// The `pages` property here is the most important thing in this demo.
// It literally represents the stack of pages on the screen.
// If we make each item in `_titles` into a page, it'll build them all.
pages: _getPages()
.map((title) => MaterialPage(
key: ValueKey(title),
child: _buildPage(title),
))
.toList(),
onPopPage: (route, result) {
// When user pops a page, we remove it from our list as well.
setState(() => _titles.removeLast());
return route.didPop(result);
},
),
);
}
List<String> _getPages() {
print("Current list: $_titles");
// Uncomment the following line, to skip "optimization" altogether.
// return _titles;
// Optimization: only build the top-most 3 pages.
// First, don't optimize if there are only 3 or fewer pages.
if (_titles.length <= 3) return _titles;
// Otherwise, we only return the last 3 items, so the navigator
// will only build those 3 pages, omitting anything beneath them.
final last3items = [
_titles[_titles.length - 3],
_titles[_titles.length - 2],
_titles[_titles.length - 1],
];
print("Optimizing, only build the top 3 pages: $last3items");
return last3items;
}
Widget _buildPage(title) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: GridView.count(
crossAxisCount: 4,
children: [
for (int i = 1; i <= 10; i++)
Padding(
padding: EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () {
final time = DateTime.now().millisecondsSinceEpoch;
setState(() {
// When user clicks on a button, we add it to our list
_titles.add('Page $i: $time');
});
},
child: Text('Open Page $i'),
),
),
],
),
);
}
}