解决Flutter在WEB中加载图片的跨域问题

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: ***workImage("https://randomuser.me/api/portraits/med/men/62.jpg", scale: 1.0)
Image key: ***workImage("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.***work 来展示。

2.2、跨域图片

        通常,可以配置内容分发网络 (CDN) 来自定义哪些域名可以访问你的内容。例如:Firebase 站点托管允许在 firebase.json 文件中, 指定一个自定义的 A***ess-Control-Allow-Origin 头。

        如果无法从你的应用层面去配置图片服务器的 CORS,你依然可以通过另一个服务器代理请求,从而加载图片。这要求中转服务器对图片加载有充分的访问权。

        此方法适用于源图片服务器公开提供了图片,却没有正确配置 CORS 头的情况。

        例子:使用 CloudFlare Workers。使用 Firebase Functions。

        以上这些官方文档提供解决跨域问题的方式,其实在实际业务环境中并不是太适合。

2.3、使用 <img>来解决

        Flutter 支持在应用中使用 HtmlElementView 嵌入 HTML。通过它可以创建一个 <img> 元素来渲染另一个域名的图片。但是,一定要记住,此方法也附带了「Web 中的 Flutter 渲染器」一节中提到的限制。就目前而言,在 CanvasKit 渲染器中过多地使用 HTML 元素,可能较为影响性能。如果图片和非图片内容交替出现, Flutter 需要在 <img> 元素之间创建额外的 WebGL 上下文。如果你的应用需要一次性在同一屏幕中展示大量的图片,请考虑使用 HTML 渲染器替代 CanvasKit。

2.3.1、<img>Widget组件封装

        参考网上的解决方案,先写个使用<img>的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.***/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、使用总结

  • 少量使用直接复制代码用上即可
  • 加载大量外部图片优先考虑使用代理
  • 假如你有图片服务端的控制权限,那直接配置服务端的跨域设置
转载请说明出处内容投诉
CSS教程_站长资源网 » 解决Flutter在WEB中加载图片的跨域问题

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买