- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
最近遇到一个问题,我的flutter(2.2.0) app运行一段时间后老是闪退,后来debbuing发现文章详情页有很多图片时,多次关闭重新打开,内存一直在增加,为什么关闭文章详情页后图片没有处理?我尝试通过这种方式在退出文章详细信息页面之前清除图像缓存(这不应该使用任何图像缓存,不是一个好的做法):
if (PaintingBinding.instance != null && PaintingBinding.instance!.imageCache != null) {
PaintingBinding.instance!.imageCache!.clear();
PaintingBinding.instance!.imageCache!.clearLiveImages();
}
但现在日志显示此信息:
I/rth.dolphin.de( 9117): Explicit concurrent copying GC freed 4578(176KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 2412KB/4824KB, paused 574us total 8.073ms
I/rth.dolphin.de( 9117): Explicit concurrent copying GC freed 4578(176KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 2412KB/4824KB, paused 734us total 7.350ms
I/rth.dolphin.de( 9117): Explicit concurrent copying GC freed 4578(176KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 2412KB/4824KB, paused 612us total 8.267ms
I/rth.dolphin.de( 9117): Explicit concurrent copying GC freed 4578(176KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 2412KB/4824KB, paused 746us total 7.442m
这是处理这个问题的正确方法,看到使用这种方法清除图像缓存有副作用。我该怎么做才能解决它?顺便说一句,这是我捕捉到的图像变化趋势:
我尝试使用此代码片段来监控图像缓存:
var imageCache= PaintingBinding.instance!.imageCache;
print("dd:"+imageCache!.currentSizeBytes.toString());
print("size:" + imageCache.currentSize.toString());
发现文章详情页关闭再打开时图片缓存没有变化,但内存还在上升。这是显示图像的关键代码:
if (item.content != "")
Html(
data: item.content,
style: {
"body": Style(
fontSize: FontSize(19.0),
),
},
customImageRenders: defaultImageRenders,
onLinkTap: (String? url, RenderContext context, Map<String, String> attributes, dom.Element? element) {
CommonUtils.launchUrl(url);
}),
图像在 html 内容中,我正在使用 flutter html显示它。这是我的完整代码:
import 'package:cruise/src/common/article_action.dart';
import 'package:cruise/src/common/helpers.dart';
import 'package:cruise/src/common/net/rest/http_result.dart';
import 'package:cruise/src/common/repo.dart';
import 'package:cruise/src/common/utils/common_utils.dart';
import 'package:cruise/src/models/Channel.dart';
import 'package:cruise/src/models/Item.dart';
import 'package:cruise/src/models/api/fav_status.dart';
import 'package:cruise/src/models/api/upvote_status.dart';
import 'package:cruise/src/page/channel/channelpg_component/page.dart';
import 'package:cruise/src/page/home/components/articledetail_component/action.dart';
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_html/style.dart';
import 'package:flutter_icons/flutter_icons.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:html/dom.dart' as dom;
import 'state.dart';
Widget buildView(ArticleDetailState state, Dispatch dispatch, ViewService viewService) {
Item item = state.article;
BuildContext context = viewService.context;
Offset? _initialSwipeOffset;
Offset? _finalSwipeOffset;
void _onHorizontalDragStart(DragStartDetails details) {
_initialSwipeOffset = details.globalPosition;
}
void _onHorizontalDragUpdate(DragUpdateDetails details) {
_finalSwipeOffset = details.globalPosition;
}
void _onHorizontalDragEnd(DragEndDetails details) {
if (_initialSwipeOffset != null) {
final offsetDifference = _initialSwipeOffset!.dx - _finalSwipeOffset!.dx;
if (offsetDifference < 0) {
if (PaintingBinding.instance != null && PaintingBinding.instance!.imageCache != null) {
// https://mp.weixin.qq.com/s/yUm4UFggYLgDbj4_JCjEdg
// https://musicfe.dev/flutter/
PaintingBinding.instance!.imageCache!.clear();
PaintingBinding.instance!.imageCache!.clearLiveImages();
}
Navigator.pop(context);
}
}
}
void touchUpvote(String action, UpvoteStatus upvoteStatus) async {
HttpResult result = (await ArticleAction.upvote(articleId: item.id.toString(), action: action))!;
if (result.result == Result.error) {
Fluttertoast.showToast(
msg: "点赞失败",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
} else {
if (upvoteStatus.statusCode == "upvote") {
dispatch(ArticleDetailActionCreator.onVote(UpvoteStatus.UPVOTE));
}
if (upvoteStatus.statusCode == "unupvote" && item.upvoteCount > 0) {
dispatch(ArticleDetailActionCreator.onVote(UpvoteStatus.UNUPVOTE));
}
Fluttertoast.showToast(
msg: upvoteStatus.statusCode == "upvote" ? "点赞成功" : "取消点赞成功",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
}
}
void touchFav(String action, FavStatus favStatus) async {
HttpResult result = (await ArticleAction.fav(articleId: item.id.toString(), action: action))!;
if (result.result == Result.error) {
Fluttertoast.showToast(
msg: favStatus.statusCode == "fav" ? "添加收藏失败" : "取消收藏失败",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
} else {
if (favStatus.statusCode == "fav") {
dispatch(ArticleDetailActionCreator.onFav(FavStatus.FAV));
}
if (favStatus.statusCode == "unfav" && item.favCount > 0) {
dispatch(ArticleDetailActionCreator.onFav(FavStatus.UNFAV));
}
Fluttertoast.showToast(
msg: favStatus.statusCode == "fav" ? "添加收藏成功" : "取消收藏成功",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
}
}
void navToChannelDetail() async {
Channel channel = (await Repo.fetchChannelItem(int.parse(item.subSourceId)))!;
var data = {'name': "originalstories", "channel": channel};
Widget page = ChannelpgPage().buildPage(data);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => page),
);
}
/// 是否是编辑选择频道链接显示不同的颜色
TextStyle getDomainStyle(Item article) {
if (article.editorPick == 1) {
return new TextStyle(color: Color(0xFFFFA826), fontSize: 15);
} else {
return new TextStyle(color: Color(0xFF0A0A0A), fontSize: 15);
}
}
ImageSourceMatcher base64UriMatcher() =>
(attributes, element) => attributes["src"] != null && attributes["src"]!.startsWith("data:image") && attributes["src"]!.contains("base64,");
Widget loadingWidget() {
return Center(
child: Container(
height: 400.0,
width: 120.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
height: 50.0,
width: 50.0,
)
],
)),
);
}
final Map<ImageSourceMatcher, ImageRender> defaultImageRenders = {
base64UriMatcher(): base64ImageRender(),
assetUriMatcher(): assetImageRender(),
networkSourceMatcher(extension: "svg"): svgNetworkImageRender(),
networkSourceMatcher(): networkImageRender(height: 400, loadingWidget: loadingWidget),
};
SingleChildScrollView buildListView(Item item, BuildContext context) {
return SingleChildScrollView(
key: PageStorageKey("detail" + item.id),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
InkWell(
onTap: () => CommonUtils.launchUrl(item.link),
child: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Container(
child: Text(
item.title,
style: Theme.of(context).textTheme.headline5!.copyWith(
fontWeight: FontWeight.w600,
),
),
),
),
),
if (item.domain != "")
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: InkWell(
onTap: () async {
navToChannelDetail();
},
child: Text(
item.domain,
style: getDomainStyle(item),
)),
),
InkWell(
onTap: () {},
child: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: item.author,
style: Theme.of(context).textTheme.caption,
),
TextSpan(
text: " ${String.fromCharCode(8226)} ",
style: Theme.of(context).textTheme.caption,
),
TextSpan(
text: item.ago,
style: Theme.of(context).textTheme.caption,
),
],
),
),
),
if (item.content != "")
Html(
data: item.content,
style: {
"body": Style(
fontSize: FontSize(19.0),
),
},
customImageRenders: defaultImageRenders,
onLinkTap: (String? url, RenderContext context, Map<String, String> attributes, dom.Element? element) {
CommonUtils.launchUrl(url);
}),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Row(
children: [
if (item.isFav == 1)
IconButton(
icon: Icon(Icons.bookmark, color: Theme.of(context).primaryColor),
onPressed: () => touchFav("unfav", FavStatus.UNFAV),
),
if (item.isFav != 1)
IconButton(
icon: Icon(Icons.bookmark),
onPressed: () => touchFav("fav", FavStatus.FAV),
),
Padding(
padding: const EdgeInsets.only(left: 0.0),
child: Text(
"${item.favCount}",
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.caption!.copyWith(
color: Theme.of(context).primaryColor,
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 0.0),
child: Row(
children: [
if (item.isUpvote == 1)
IconButton(
icon: Icon(Icons.thumb_up, color: Theme.of(context).primaryColor),
onPressed: () => touchUpvote("unupvote", UpvoteStatus.UNUPVOTE),
),
if (item.isUpvote != 1)
IconButton(
icon: Icon(Icons.thumb_up),
onPressed: () => touchUpvote("upvote", UpvoteStatus.UPVOTE),
),
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
"${item.upvoteCount}",
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.caption!.copyWith(
color: Theme.of(context).primaryColor,
),
),
),
],
),
),
],
),
IconButton(
icon: Icon(
Feather.share_2,
),
onPressed: () => handleShare(id: item.id, title: item.title, postUrl: item.link),
),
],
),
],
));
}
var imageCache= PaintingBinding.instance!.imageCache;
print("dd:"+imageCache!.currentSizeBytes.toString());
print("size:" + imageCache.currentSize.toString());
return GestureDetector(
onHorizontalDragStart: _onHorizontalDragStart,
onHorizontalDragUpdate: _onHorizontalDragUpdate,
onHorizontalDragEnd: _onHorizontalDragEnd,
child: Container(
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height * 0.9,
),
color: Theme.of(context).scaffoldBackgroundColor,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: buildListView(item, context),
),
));
}
最新更新:清除图片缓存时仍然触发OOM,图片好像没有从内存中卸载,加载更多图片,内存增加更多(内存永远不会被GC)。
最佳答案
我遇到了同样的问题,将你的问题添加为书签等待答案,最近我找到了一个简单的方法来解决它。
基本思想是为每个要显示的图像使用 ImageProvider
并使用 precacheImage
函数将其放入内存,然后您可以轻松地将其从内存中删除在 ImageProvider
evict
函数
// any class that extend ImageProvider
ImageProvider().evict();
// example
MemoryImage(bytes).evict();
完整的工作代码作为答案会太长。但我已经向 pub.dev 发布了一个包,它可以做同样的事情并具有其他功能 disposable_cached_images .
您可以查看源代码以更好地理解实现。
编辑:
经过更多测试,事实证明在图像提供程序上调用 evict
有时不起作用,尤其是在 resolve
期间调用它时。所以我进行了更改,发现使用 ui.image
更好直接没有提供者图像。
ui.image.dispose()
效果很好。
关于flutter - 为什么 flutter image 在 widget 关闭时没有释放内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67667590/
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引起辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the he
我正在试验 jquery-ui 并查看和克隆一些示例。在一个示例(自动完成的组合框)中,我看到一个带有 ui-widget 类的 anchor (a) 元素,它与包含的 css 文件中的 .ui-wi
在我帮助维护的代码中,我发现了多个如下所示的代码示例: Description := IfThen(Assigned(Widget), Widget.Description, 'No Widget')
这个问题在这里已经有了答案: CSS Performance Question (8 个答案) 关闭 9 年前。 这是一个很常见的情况。假设我们有一些标记: 我们可以使用 2 个选项来
这个问题在这里已经有了答案: CSS: Class name selector- name starts with (2 个答案) 关闭 5 年前。
我有一个布局,其中Row小部件中有两个子部件,这两个都是灵活的小部件。左侧的小部件包含红色和蓝色的小部件,我希望根据右侧小部件的高度增加蓝色小部件的高度(这是一个灵活的小部件,其FLEX值为7)。。例
我有一个布局,其中Row小部件中有两个子部件,这两个都是灵活的小部件。左侧的小部件包含红色和蓝色的小部件,我希望根据右侧小部件的高度增加蓝色小部件的高度(这是一个灵活的小部件,其FLEX值为7)。。例
这是一个代码: import 'package:flutter/material.dart'; import 'package:flutterapp/ui/pages/notes_home.dart'
ListView、GridView、SingleChildScrollView 等小部件是可滚动的小部件,而Container、SizedBox, Column 不是。 有没有办法检查 Widget
假设我在 Python 中有这个简单的函数: def f(gender, name): if gender == 'male': return ranking_male(nam
我不想听起来像个糟糕的新手,但是小部件是独立的应用程序吗?例如,我正在为 Android 创建一个新闻阅读器应用程序,我想要一个主屏幕小部件。我是将小部件创建为 Hook 到其他应用程序的单独项目/应
如何告诉 Tk 小部件告诉我它的子级是什么(或谁,视情况而定)?有这个命令吗? 例如,给定一个带有标签、按钮和其他装饰的 Canvas 小部件 .cvs ...如何查询 Canvas ? 最佳答案 w
我为 Android 开发了一个 APP + 主屏幕小部件。现在我更新了应用程序(增加了版本代码/名称)但是当我安装时,它不会自动替换屏幕上现有的小部件。它只是说“问题加载小部件”。 有什么想法吗??
我是小部件开发的新手..我设法构建了一个无法调整大小的小部件..我希望它像 - 用户设法根据自己调整大小......有人可以告诉我如何制作它可能吗? 谢谢 friend 。 最佳答案 AppWidge
我有一个小部件列表: List widgetList = List(); widgetList.add(WidgetA()); widgetList.add(WidgetB()); widgetLis
对不起,我的英语不好! 我开发了一个今日小部件。我需要从小部件启动 map 应用程序(例如),并且在设备锁定时不工作。在这种情况下如何检测锁定的设备并启动解锁屏幕? 问候, 最佳答案 而不是使用 UI
我正在尝试制作我的小部件的免费版本,我想向小部件添加广告,这可能吗?如果是这样怎么办? 最佳答案 小部件应用程序中的 View 仅限于: 模拟时钟 按钮 天文台 图像按钮 ImageView 进度条
我在 Android Studio 中创建了一个新项目,然后添加了一个新的小部件并在我的 Nexus 5 5.1 上运行该应用程序,它可以工作,但是当尝试将小部件添加到主屏幕时,它没有出现在小部件列表
我有一个字符串 'currentMessage' 和一个用于显示它的标签。我有一个 Toplevel 小部件,其中包含一个文本小部件,为“currentMessage”提供新值: from tkint
我尝试使用这个 link 解决我的问题 update - I figured out that there is something wrong with the setter of the pend
我是一名优秀的程序员,十分优秀!