1、环境版本
Flutter版本:3.16.9
开发IDE:android studio 2023.1.1 Patch2
受影响环境:WEB (Android、IOS、Windows等都不受影响)
IDE输出的错误:
The following ProgressEvent$ object was thrown resolving an image codec:
[object ProgressEvent]
When the exception was thrown, this was the stack:
Image provider: NetworkImage("https://randomuser.me/api/portraits/med/men/62.jpg", scale: 1.0)
Image key: NetworkImage("https://randomuser.me/api/portraits/med/men/62.jpg", scale: 1.0)
2、Flutter关于图片跨域问题的描述
2.1、内存、asset 和同源网络图片
如果图片在应用内存中有编码后的字节信息、或者以 asset 的方式提供、或者和应用存储在同一服务器上(也就是同源),则不需要做额外工作。图片既可以在 HTML 也可以在 CanvasKit 模式下,使用 Image.memory、 Image.asset 和 Image.network 来展示。
2.2、跨域图片
通常,可以配置内容分发网络 (CDN) 来自定义哪些域名可以访问你的内容。例如:Firebase 站点托管允许在 firebase.json 文件中, 指定一个自定义的 Access-Control-Allow-Origin 头。
如果无法从你的应用层面去配置图片服务器的 CORS,你依然可以通过另一个服务器代理请求,从而加载图片。这要求中转服务器对图片加载有充分的访问权。
此方法适用于源图片服务器公开提供了图片,却没有正确配置 CORS 头的情况。
例子:使用 CloudFlare Workers。使用 Firebase Functions。
以上这些官方文档提供解决跨域问题的方式,其实在实际业务环境中并不是太适合。
2.3、使用
来解决
Flutter 支持在应用中使用 HtmlElementView 嵌入 HTML。通过它可以创建一个 元素来渲染另一个域名的图片。但是,一定要记住,此方法也附带了「Web 中的 Flutter 渲染器」一节中提到的限制。就目前而言,在 CanvasKit 渲染器中过多地使用 HTML 元素,可能较为影响性能。如果图片和非图片内容交替出现, Flutter 需要在
元素之间创建额外的 WebGL 上下文。如果你的应用需要一次性在同一屏幕中展示大量的图片,请考虑使用 HTML 渲染器替代 CanvasKit。
2.3.1、
Widget组件封装
参考网上的解决方案,先写个使用的Widget的封装
import 'dart:html'; import 'dart:ui' as ui; import 'package:flutter/cupertino.dart'; class WebImage extends StatelessWidget{ String url; double width; double height; WebImage(this.url, this.width, this.height); @override Widget build(BuildContext context) { String _divId = "web_image_" + DateTime.now().toIso8601String(); // ignore: undefined_prefixed_name ui.platformViewRegistry.registerViewFactory( _divId, (int viewId) => ImageElement(src: url), ); return SizedBox( width: width, height: height, child: HtmlElementView(key: UniqueKey(), viewType: _divId,), ); } }
使用WebImage组件:
WebImage("图片网址", 50, 50),
如果要显示圆形的头像图片可以这样写:
ClipOval( child: WebImage("网络图片地址", 50, 50) ),
效果:
2.3.2、使用后带来性能问题
上面方式有一个很严重的问题,如果一个页面中图片特别多,比如列表,那么使用这种方式的话在pc上运行会特别卡,甚至卡死(所以少量使用时就不需要再望下看了)。会出现大量如下信息:
Flutter: restoring WebGL context.
Flutter: restoring WebGL context.
Flutter: restoring WebGL context.
...
官方文档(https://flutter.dev/docs/development/platform-integration/web-images )中已经提到了 :
As of today, using too many HTML elements with the CanvasKit renderer may hurt performance. If images interleave non-image content Flutter needs to create extra WebGL contexts between theelements. If your application needs to display a lot of images on the same screen all at once, consider using the HTML renderer instead of CanvasKit.
如果在一个页面有很多图片,则使用HTML renderer来代替CanvasKit。
那么什么是HTML renderer,什么是CanvasKit,如何使用这两个?
根据https://flutter.cn/docs/development/tools/web-renderers 官方文档,flutter对于web的渲染是有两种模式,即html和Canvaskit。
Canvaskit将 Skia 编译成 WebAssembly 格式,并使用 WebGL 渲染。应用在移动和桌面端保持一致,有更好的性能,以及降低不同浏览器渲染效果不一致的风险。但是应用的大小会增加大约 2MB。
默认情况下flutter自动选择渲染器。移动端浏览器选择 HTML,桌面端浏览器选择 CanvasKit。
但是我们如果想使用HTML renderer,就必须强制设置一下,而这个设置并不是在代码中,而是在启动参数中,如下
flutter run -d chrome --web-renderer html (或canvaskit)//运行命令
flutter build web --web-renderer html (或canvaskit) //编译打包
我们通过在终端执行flutter run -d chrome --web-renderer html 来运行我们的应用,就会发现即使页面中有很多图片,也不会出现明显卡顿卡死的现象了。
如果使用Android studio,则需要对运行进行配置,如图:
在配置中的Additional run args一栏中添加--web-renderer html即可,再运行就会以HTML renderer的方式来运行。
最后编译打包的时候也要加上--web-renderer html才可以。
但配置后会了出现一些影响:
改成html render后发现所有文字无法选择了,导致无法进行复制等行为。运行后通过开发者工具查看页面节点信息,可以看到整个body都被设置成了user-select: none; touch-action: none,这样就导致整个页面上的文本都无法选择。这个是flutter框架的行为,目前在flutter项目中还没有发现可以取消这个配置的api。
3、使用总结
- 少量使用直接复制代码用上即可
- 加载大量外部图片优先考虑使用代理
- 假如你有图片服务端的控制权限,那直接配置服务端的跨域设置