Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

can't clone a disposed image #343

Closed
826327700 opened this issue Apr 20, 2021 · 17 comments
Closed

can't clone a disposed image #343

826327700 opened this issue Apr 20, 2021 · 17 comments
Assignees
Labels
bug Something isn't working

Comments

@826327700
Copy link

使用场景:
图片列表页 点击图片=>图片放大预览页。

情况1:
如果图片列表页每一项图片都使用ExtendedImage.network来展示,点击跳转图片放大页面(传参是图片的url)后正常。

情况2:
如果图片列表页不使用使用ExtendedImage.network来展示,首次点击跳转图片放大页面正常,关闭后再次点击报错can't clone a disposed image。

情况3:
如果图片列表页每一项图片都使用ExtendedImage.network来展示,点击跳转图片放大页面(传参不是图片的原始url,比如加了url参数时间戳),则出现情况2的现象。

图片放大预览页基础代码:

ExtendedImageSlidePage(
        key: slidePagekey,
        child: GestureDetector(
            child: ExtendedImage.network(
                widget.url,
                enableSlideOutPage: true,
                mode: ExtendedImageMode.gesture,
                ///make hero better when slide out
                heroBuilderForSlidingPage: (Widget result) {
                    return Hero(
                        tag: widget.url,
                        child: result,
                        flightShuttleBuilder: (BuildContext flightContext,
                            Animation<double> animation,
                            HeroFlightDirection flightDirection,
                            BuildContext fromHeroContext,
                            BuildContext toHeroContext) {
                        final Hero hero = (flightDirection == HeroFlightDirection.pop
                            ? fromHeroContext.widget
                            : toHeroContext.widget) as Hero;
                        return hero.child;
                        },
                    );
                },
            ),
            onTap: () {
                slidePagekey.currentState.popPage();
                Navigator.pop(context);
            },
        ),
        slideAxis: SlideAxis.both,
        slideType: SlideType.onlyImage,
    )

大概猜测和图片缓存有关 但不知道怎么解

@zmtzawqlp
Copy link
Member

I have post issue on
flutter/flutter#77576

@826327700
Copy link
Author

https://github.com/fluttercandies/extended_image/blob/master/example/lib/pages/simple/slide_page_demo.dart
我就是完全的复制粘贴这个demo文件里的SlidePage,一个字没改过,把它当成一个单独的页面做为图片预览,在别的页面,点击图片就跳转到SlidePage,传参是图片url,如果图片url在打开SlidePage之前被extended_image缓存过,那就没任何问题,如果不使用extended_image缓存,直接传url打开SlidePage,首次没问题,第二次就报can't clone a disposed image这个错了。照这种情况,我应该怎么做?

@zmtzawqlp
Copy link
Member

https://github.com/fluttercandies/extended_image/blob/master/example/lib/pages/simple/slide_page_demo.dart
我就是完全的复制粘贴这个demo文件里的SlidePage,一个字没改过,把它当成一个单独的页面做为图片预览,在别的页面,点击图片就跳转到SlidePage,传参是图片url,如果图片url在打开SlidePage之前被extended_image缓存过,那就没任何问题,如果不使用extended_image缓存,直接传url打开SlidePage,首次没问题,第二次就报can't clone a disposed image这个错了。照这种情况,我应该怎么做?

为啥不都用 extended_image

@zmtzawqlp zmtzawqlp self-assigned this Apr 21, 2021
@zmtzawqlp zmtzawqlp added the bug Something isn't working label Apr 21, 2021
@826327700
Copy link
Author

https://github.com/fluttercandies/extended_image/blob/master/example/lib/pages/simple/slide_page_demo.dart
我就是完全的复制粘贴这个demo文件里的SlidePage,一个字没改过,把它当成一个单独的页面做为图片预览,在别的页面,点击图片就跳转到SlidePage,传参是图片url,如果图片url在打开SlidePage之前被extended_image缓存过,那就没任何问题,如果不使用extended_image缓存,直接传url打开SlidePage,首次没问题,第二次就报can't clone a disposed image这个错了。照这种情况,我应该怎么做?

为啥不都用 extended_image

经过了好久的排查,终于发现问题点在哪了
被点击图片是用hero包住的,tag设定有问题。
以下是我的代码:

final List urls=[
        'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/19/bFLhDueTIPXnZYWJ.png',
        'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/21/1080/1440/QyVGVffBICoOQgcH.jpg',
        'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/20/VDGDYOsNirJMFuFf.png',
    ];

@override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text("测试"),
            ),
            body: GridView.count(
                crossAxisCount: 3,
                children: urls.map((url) => GestureDetector(
                    onTap: (){
                        Navigator.push(
                            context,
                            TransparentRoute(builder: (_) => SlidePage(url:url)),
                        );
                    },
                    child: Hero(
                       ///tag:url,  这里的tag,如果和下面的network的url保持一致,就不会有问题了。一开始我一直以为和传参的url保持一致。
                        tag:url+'?x-oss-process=image/resize,w_100',
                        child: ExtendedImage.network(
                            url+'?x-oss-process=image/resize,w_100',
                            fit: BoxFit.cover,
                        ),
                    )
                    ,
                )).toList()
            ),
        );
    }

以上的使用场景是:图片列表里用url+参数,实现图片压缩,等点击预览后,传参url不带参数,表示预览原图。问题点在于,hero的tag应该要写什么?

@zmtzawqlp
Copy link
Member

我写了一个demo,但是还是没有重现问题, https://gist.github.com/zmtzawqlp/5dbbf021ca0b3f0f0c9c35f20a1eb4e9,希望有谁修改demo ,重现问题

@zmtzawqlp
Copy link
Member

https://github.com/fluttercandies/extended_image/blob/master/example/lib/pages/simple/slide_page_demo.dart
我就是完全的复制粘贴这个demo文件里的SlidePage,一个字没改过,把它当成一个单独的页面做为图片预览,在别的页面,点击图片就跳转到SlidePage,传参是图片url,如果图片url在打开SlidePage之前被extended_image缓存过,那就没任何问题,如果不使用extended_image缓存,直接传url打开SlidePage,首次没问题,第二次就报can't clone a disposed image这个错了。照这种情况,我应该怎么做?

为啥不都用 extended_image

经过了好久的排查,终于发现问题点在哪了
被点击图片是用hero包住的,tag设定有问题。
以下是我的代码:

final List urls=[
        'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/19/bFLhDueTIPXnZYWJ.png',
        'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/21/1080/1440/QyVGVffBICoOQgcH.jpg',
        'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/20/VDGDYOsNirJMFuFf.png',
    ];

@override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text("测试"),
            ),
            body: GridView.count(
                crossAxisCount: 3,
                children: urls.map((url) => GestureDetector(
                    onTap: (){
                        Navigator.push(
                            context,
                            TransparentRoute(builder: (_) => SlidePage(url:url)),
                        );
                    },
                    child: Hero(
                       ///tag:url,  这里的tag,如果和下面的network的url保持一致,就不会有问题了。一开始我一直以为和传参的url保持一致。
                        tag:url+'?x-oss-process=image/resize,w_100',
                        child: ExtendedImage.network(
                            url+'?x-oss-process=image/resize,w_100',
                            fit: BoxFit.cover,
                        ),
                    )
                    ,
                )).toList()
            ),
        );
    }

以上的使用场景是:图片列表里用url+参数,实现图片压缩,等点击预览后,传参url不带参数,表示预览原图。问题点在于,hero的tag应该要写什么?

tag 必须是一样的才会生效吧。

@826327700
Copy link
Author

https://github.com/fluttercandies/extended_image/blob/master/example/lib/pages/simple/slide_page_demo.dart
我就是完全的复制粘贴这个demo文件里的SlidePage,一个字没改过,把它当成一个单独的页面做为图片预览,在别的页面,点击图片就跳转到SlidePage,传参是图片url,如果图片url在打开SlidePage之前被extended_image缓存过,那就没任何问题,如果不使用extended_image缓存,直接传url打开SlidePage,首次没问题,第二次就报can't clone a disposed image这个错了。照这种情况,我应该怎么做?

为啥不都用 extended_image

经过了好久的排查,终于发现问题点在哪了
被点击图片是用hero包住的,tag设定有问题。
以下是我的代码:

final List urls=[
        'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/19/bFLhDueTIPXnZYWJ.png',
        'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/21/1080/1440/QyVGVffBICoOQgcH.jpg',
        'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/20/VDGDYOsNirJMFuFf.png',
    ];

@override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text("测试"),
            ),
            body: GridView.count(
                crossAxisCount: 3,
                children: urls.map((url) => GestureDetector(
                    onTap: (){
                        Navigator.push(
                            context,
                            TransparentRoute(builder: (_) => SlidePage(url:url)),
                        );
                    },
                    child: Hero(
                       ///tag:url,  这里的tag,如果和下面的network的url保持一致,就不会有问题了。一开始我一直以为和传参的url保持一致。
                        tag:url+'?x-oss-process=image/resize,w_100',
                        child: ExtendedImage.network(
                            url+'?x-oss-process=image/resize,w_100',
                            fit: BoxFit.cover,
                        ),
                    )
                    ,
                )).toList()
            ),
        );
    }

以上的使用场景是:图片列表里用url+参数,实现图片压缩,等点击预览后,传参url不带参数,表示预览原图。问题点在于,hero的tag应该要写什么?

进一步查看了slide_page_demo.dart的代码,发现hero的tag是实现动画的关键,那么要求点击图片的hero的tag必须和预览页的tag一致,则又回到了问题之初。

@zmtzawqlp
Copy link
Member

我更新了 extended image 的demo,现在photo_view_demo.dart 也不会报错了

@826327700
Copy link
Author

我写了一个demo,但是还是没有重现问题, https://gist.github.com/zmtzawqlp/5dbbf021ca0b3f0f0c9c35f20a1eb4e9,希望有谁修改demo ,重现问题

你复制粘贴以下内容,直接运行应该可以重现:

import 'dart:ui';

import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';

class TransparentRoute extends PageRoute<void> {
  TransparentRoute({
    @required this.builder,
    RouteSettings settings,
  })  : assert(builder != null),
        super(settings: settings, fullscreenDialog: false);
 
  final WidgetBuilder builder;
 
  @override
  bool get opaque => false;
 
  @override
  Color get barrierColor => null;
 
  @override
  String get barrierLabel => null;
 
  @override
  bool get maintainState => true;
 
  @override
  Duration get transitionDuration => Duration(milliseconds: 350);
 
  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation) {
    final result = builder(context);
    return FadeTransition(
      opacity: Tween<double>(begin: 0, end: 1).animate(animation),
      child: Semantics(
        scopesRoute: true,
        explicitChildNodes: true,
        child: result,
      ),
    );
  }
}


class SlidePageDemo extends StatelessWidget {
     SlidePageDemo({Key key}) : super(key: key);

    final List urls=[
        'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/19/bFLhDueTIPXnZYWJ.png',
        'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/21/1080/1440/QyVGVffBICoOQgcH.jpg',
        'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/20/VDGDYOsNirJMFuFf.png',
    ];

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text("测试"),
            ),
            body: GridView.count(
                crossAxisCount: 3,
                children: urls.map((url) => GestureDetector(
                    onTap: (){
                        Navigator.push(
                            context,
                            TransparentRoute(builder: (_) => SlidePage(url:url)),
                        );
                    },
                    child: Hero(
                        tag:url,
                        child: ExtendedImage.network(
                            url+'?x-oss-process=image/resize,w_100',
                            fit: BoxFit.cover,
                        ),
                    )
                    ,
                )).toList()
            ),
        );
    }
}

class SlidePage extends StatefulWidget {
    const SlidePage({this.url});
    final String url;
    @override
    _SlidePageState createState() => _SlidePageState();
}

class _SlidePageState extends State<SlidePage> {
    GlobalKey<ExtendedImageSlidePageState> slidePagekey =
        GlobalKey<ExtendedImageSlidePageState>();



    @override
    Widget build(BuildContext context) {
        return Material(
            color: Colors.transparent,
            child:Theme(
                data: ThemeData(dialogBackgroundColor: Colors.black),
                child: Builder(
                     builder: (context) {
                        return ExtendedImageSlidePage(
                            key: slidePagekey,
                            child: GestureDetector(
                                child: ExtendedImage.network(
                                    widget.url,
                                    enableSlideOutPage: true,
                                    mode: ExtendedImageMode.gesture,
                                    ///make hero better when slide out
                                    heroBuilderForSlidingPage: (Widget result) {
                                        return Hero(
                                            tag: widget.url,
                                            child: result,
                                            flightShuttleBuilder: (BuildContext flightContext,
                                                Animation<double> animation,
                                                HeroFlightDirection flightDirection,
                                                BuildContext fromHeroContext,
                                                BuildContext toHeroContext) {
                                            final Hero hero = (flightDirection == HeroFlightDirection.pop
                                                ? fromHeroContext.widget
                                                : toHeroContext.widget) as Hero;
                                            return hero.child;
                                            },
                                        );
                                    },
                                ),
                                onTap: () {
                                    slidePagekey.currentState.popPage();
                                    Navigator.pop(context);
                                },
                            ),
                            slideAxis: SlideAxis.both,
                            slideType: SlideType.onlyImage,
                        );
                     }
                )
            )
        );
    }
}

@826327700
Copy link
Author

SlidePageDemo

入口是SlidePageDemo,点击某一张图片,正常打开预览,关闭预览页,再次点击刚才那张图片,此时报错一闪而过。最终能正常预览,但是动画过程会看到红色屏幕(release版本这是灰色屏幕)

@zmtzawqlp
Copy link
Member

zmtzawqlp commented Apr 21, 2021

fix with remove heroBuilderForSlidingPage,但是这样,hero 太丑了,你们先这样修改吧,我空了再研究下

import 'dart:ui';

import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';

class TransparentRoute extends PageRoute<void> {
  TransparentRoute({
    required this.builder,
    RouteSettings? settings,
  })  : assert(builder != null),
        super(settings: settings, fullscreenDialog: false);

  final WidgetBuilder builder;

  @override
  bool get opaque => false;

  @override
  Color? get barrierColor => null;

  @override
  String? get barrierLabel => null;

  @override
  bool get maintainState => true;

  @override
  Duration get transitionDuration => Duration(milliseconds: 350);

  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation) {
    final result = builder(context);
    return FadeTransition(
      opacity: Tween<double>(begin: 0, end: 1).animate(animation),
      child: Semantics(
        scopesRoute: true,
        explicitChildNodes: true,
        child: result,
      ),
    );
  }
}

class SlidePageDemo extends StatelessWidget {
  SlidePageDemo({Key? key}) : super(key: key);

  final List urls = [
    'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/19/bFLhDueTIPXnZYWJ.png',
    'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/21/1080/1440/QyVGVffBICoOQgcH.jpg',
    'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/20/VDGDYOsNirJMFuFf.png',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("测试"),
      ),
      body: GridView.count(
          crossAxisCount: 3,
          children: urls
              .map((url) => GestureDetector(
                    onTap: () {
                      Navigator.push(
                        context,
                        TransparentRoute(builder: (_) => SlidePage(url: url)),
                      );
                    },
                    child: Hero(
                      tag: url,
                      child: ExtendedImage.network(
                        url + '?x-oss-process=image/resize,w_100',
                        fit: BoxFit.cover,
                      ),
                    ),
                  ))
              .toList()),
    );
  }
}

class SlidePage extends StatefulWidget {
  const SlidePage({required this.url});
  final String url;
  @override
  _SlidePageState createState() => _SlidePageState();
}

class _SlidePageState extends State<SlidePage> {
  GlobalKey<ExtendedImageSlidePageState> slidePagekey =
      GlobalKey<ExtendedImageSlidePageState>();
  GlobalKey key = GlobalKey();
  @override
  Widget build(BuildContext context) {
    return Material(
        color: Colors.transparent,
        child: Theme(
            data: ThemeData(dialogBackgroundColor: Colors.black),
            child: ExtendedImageSlidePage(
              key: slidePagekey,
              child: GestureDetector(
                child: Hero(
                  tag: widget.url,
                  child: ExtendedImage.network(
                    widget.url,
                    enableSlideOutPage: true,
                    mode: ExtendedImageMode.gesture,
                  ),
                  flightShuttleBuilder: (BuildContext flightContext,
                      Animation<double> animation,
                      HeroFlightDirection flightDirection,
                      BuildContext fromHeroContext,
                      BuildContext toHeroContext) {
                    final Hero hero =
                        (flightDirection == HeroFlightDirection.pop
                            ? fromHeroContext.widget
                            : toHeroContext.widget) as Hero;
                    return hero.child;
                  },
                ),
                onTap: () {
                  slidePagekey.currentState?.popPage();
                  Navigator.pop(context);
                },
              ),
              slideAxis: SlideAxis.both,
              slideType: SlideType.onlyImage,
            )));
  }
}

@826327700
Copy link
Author

fix with remove heroBuilderForSlidingPage,但是这样,hero 太丑了,你们先这样修改吧,我空了再研究下

import 'dart:ui';

import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';

class TransparentRoute extends PageRoute<void> {
  TransparentRoute({
    required this.builder,
    RouteSettings? settings,
  })  : assert(builder != null),
        super(settings: settings, fullscreenDialog: false);

  final WidgetBuilder builder;

  @override
  bool get opaque => false;

  @override
  Color? get barrierColor => null;

  @override
  String? get barrierLabel => null;

  @override
  bool get maintainState => true;

  @override
  Duration get transitionDuration => Duration(milliseconds: 350);

  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation) {
    final result = builder(context);
    return FadeTransition(
      opacity: Tween<double>(begin: 0, end: 1).animate(animation),
      child: Semantics(
        scopesRoute: true,
        explicitChildNodes: true,
        child: result,
      ),
    );
  }
}

class SlidePageDemo extends StatelessWidget {
  SlidePageDemo({Key? key}) : super(key: key);

  final List urls = [
    'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/19/bFLhDueTIPXnZYWJ.png',
    'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/21/1080/1440/QyVGVffBICoOQgcH.jpg',
    'https://cfdz-sz-test.oss-cn-shenzhen.aliyuncs.com/store/org-photo/2021/4/20/VDGDYOsNirJMFuFf.png',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("测试"),
      ),
      body: GridView.count(
          crossAxisCount: 3,
          children: urls
              .map((url) => GestureDetector(
                    onTap: () {
                      Navigator.push(
                        context,
                        TransparentRoute(builder: (_) => SlidePage(url: url)),
                      );
                    },
                    child: Hero(
                      tag: url,
                      child: ExtendedImage.network(
                        url + '?x-oss-process=image/resize,w_100',
                        fit: BoxFit.cover,
                      ),
                    ),
                  ))
              .toList()),
    );
  }
}

class SlidePage extends StatefulWidget {
  const SlidePage({required this.url});
  final String url;
  @override
  _SlidePageState createState() => _SlidePageState();
}

class _SlidePageState extends State<SlidePage> {
  GlobalKey<ExtendedImageSlidePageState> slidePagekey =
      GlobalKey<ExtendedImageSlidePageState>();
  GlobalKey key = GlobalKey();
  @override
  Widget build(BuildContext context) {
    return Material(
        color: Colors.transparent,
        child: Theme(
            data: ThemeData(dialogBackgroundColor: Colors.black),
            child: ExtendedImageSlidePage(
              key: slidePagekey,
              child: GestureDetector(
                child: Hero(
                  tag: widget.url,
                  child: ExtendedImage.network(
                    widget.url,
                    enableSlideOutPage: true,
                    mode: ExtendedImageMode.gesture,
                  ),
                  flightShuttleBuilder: (BuildContext flightContext,
                      Animation<double> animation,
                      HeroFlightDirection flightDirection,
                      BuildContext fromHeroContext,
                      BuildContext toHeroContext) {
                    final Hero hero =
                        (flightDirection == HeroFlightDirection.pop
                            ? fromHeroContext.widget
                            : toHeroContext.widget) as Hero;
                    return hero.child;
                  },
                ),
                onTap: () {
                  slidePagekey.currentState?.popPage();
                  Navigator.pop(context);
                },
              ),
              slideAxis: SlideAxis.both,
              slideType: SlideType.onlyImage,
            )));
  }
}

感谢大佬,回复真及时,赞!目前这样,除了hero过渡有点别扭以外,功能上完全正常了!

@zhao6810
Copy link

zhao6810 commented May 3, 2021

同样问题,感谢指导。

@zmtzawqlp
Copy link
Member

I have updated the demo make hero better when slide out, please check it.

class HeroWidget extends StatefulWidget {

@AlexV525
Copy link
Member

AlexV525 commented May 8, 2021

An alternative solution in nullable by default version.

非空安全的 Hero 处理方案。

https://github.com/openjmu/OpenJMU/blob/8009c043e1aee2d7d872d1af48f56c82f15e080c/lib/widgets/image/image_hero.dart

@zmtzawqlp
Copy link
Member

看起来,修复了,估计要下个版本升级了 flutter sdk
flutter/flutter#77576

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants