Flutter加载图片证书校验失败问题
I/flutter: The following HandshakeException was thrown resolving an image codec: Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: self signed certificate(handshake.cc:352)) 08-05 16:27:56.673 13676-13696/com.example.flutter_module.host I/flutter: When the exception was thrown, this was the stack: 08-05 16:27:56.688 13676-13696/com.example.flutter_module.host I/flutter: #0 NetworkImage._loadAsync (package:flutter/src/painting/image_provider.dart:490:39)
flutter加载图片出错,未加载出来。看错误是证书校验问题,由于使用的是不被信任的证书,导致使用flutter加载图片加载不出来。 为了有默认占位图,使用的是FadeInImage。
new FadeInImage.assetNetwork( placeholder: 'assets/images/pic_hzw.jpeg', image: 'https://xxxx/xxxx.jpg', fit: BoxFit.fill, ),
找了一圈,没看到合适解决方法。因为有些项目确实会存在这种证书问题,虽然有点非主流,但实际情况如此,只能从图片加载入手进行解决。看下FadeInImage源码,图片的加载过程。
FadeInImage.assetNetwork({ Key key, @required String placeholder, @required String image, AssetBundle bundle, double placeholderScale, double imageScale = 1.0, this.fadeOutDuration = const Duration(milliseconds: 300), this.fadeOutCurve = Curves.easeOut, this.fadeInDuration = const Duration(milliseconds: 700), this.fadeInCurve = Curves.easeIn, this.width, this.height, this.fit, this.alignment = Alignment.center, this.repeat = ImageRepeat.noRepeat, this.matchTextDirection = false, }) : assert(placeholder != null), assert(image != null), placeholder = placeholderScale != null ? ExactAssetImage(placeholder, bundle: bundle, scale: placeholderScale) : AssetImage(placeholder, bundle: bundle), assert(imageScale != null), assert(fadeOutDuration != null), assert(fadeOutCurve != null), assert(fadeInDuration != null), assert(fadeInCurve != null), assert(alignment != null), assert(repeat != null), assert(matchTextDirection != null), image = NetworkImage(image, scale: imageScale), super(key: key);
可以看到图片url地址最终传给了NetworkImage中。再看NetworkImage中,代码不多,涉及图片的http请求就是如下一些代码。
static final HttpClient _httpClient = HttpClient(); Future<ui.Codec> _loadAsync(NetworkImage key) async { assert(key == this); final Uri resolved = Uri.base.resolve(key.url); final HttpClientRequest request = await _httpClient.getUrl(resolved); headers?.forEach((String name, String value) { request.headers.add(name, value); }); final HttpClientResponse response = await request.close(); if (response.statusCode != HttpStatus.ok) throw Exception('HTTP request failed, statusCode: ${response?.statusCode}, $resolved'); final Uint8List bytes = await consolidateHttpClientResponseBytes(response); if (bytes.lengthInBytes == 0) throw Exception('NetworkImage is an empty file: $resolved'); return PaintingBinding.instance.instantiateImageCodec(bytes); }
NetworkImage加载图片使用的是HttpClient,参考《Flutter实战》book.flutterchina.club/chapter10/h… 中关于HttpClient的证书校验的处理,根据实际使用场景对证书校验进行处理,这里直接让其通过校验。 修改_loadAsync方法,使证书校验通过。
Future<ui.Codec> _loadAsync(NetworkImageWithoutAuth key) async { assert(key == this); //解决证书校验通不过的问题 _httpClient.badCertificateCallback = (X509Certificate cert,String host,int port){ return true; }; final Uri resolved = Uri.base.resolve(key.url); final HttpClientRequest request = await _httpClient.getUrl(resolved); ··· }
所以,方案就是新写2个类:FadeInImageWithoutAuth 和 NetworkImageWithoutAuth。FadeInImageWithoutAuth中将原先的加载网络图片的NetworkImage替换为NetworkImageWithoutAuth,NetworkImageWithoutAuth在使用HttpClient加载图片时,使不安全证书校验通过。
fade_in_image_without_auth.dart 直接拷贝fade_in_iamge.dart,注意导一下包。
import 'package:flutter/material.dart'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; //import 'basic.dart'; //import 'framework.dart'; //import 'image.dart'; //import 'ticker_provider.dart'; import 'package:flutter/src/widgets/basic.dart'; import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter/src/widgets/image.dart'; import 'package:flutter/src/widgets/ticker_provider.dart'; import 'image_provider_without_auth.dart'; // Examples can assume: // Uint8List bytes; /// An image that shows a [placeholder] image while the target [image] is /// loading, then fades in the new image when it loads. /// /// Use this class to display long-loading images, such as [new NetworkImage], /// so that the image appears on screen with a graceful animation rather than /// abruptly pops onto the screen. /// /// If the [image] emits an [ImageInfo] synchronously, such as when the image /// has been loaded and cached, the [image] is displayed immediately and the /// [placeholder] is never displayed. /// /// [fadeOutDuration] and [fadeOutCurve] control the fade-out animation of the /// placeholder. /// /// [fadeInDuration] and [fadeInCurve] control the fade-in animation of the /// target [image]. /// /// Prefer a [placeholder] that's already cached so that it is displayed in one /// frame. This prevents it from popping onto the screen. /// /// When [image] changes it is resolved to a new [ImageStream]. If the new /// [ImageStream.key] is different this widget subscribes to the new stream and /// replaces the displayed image with images emitted by the new stream. /// /// When [placeholder] changes and the [image] has not yet emitted an /// [ImageInfo], then [placeholder] is resolved to a new [ImageStream]. If the /// new [ImageStream.key] is different this widget subscribes to the new stream /// and replaces the displayed image to images emitted by the new stream. /// /// When either [placeholder] or [image] changes, this widget continues showing /// the previously loaded image (if any) until the new image provider provides a /// different image. This is known as "gapless playback" (see also /// [Image.gaplessPlayback]). /// /// {@tool sample} /// /// ```dart /// FadeInImage( /// // here `bytes` is a Uint8List containing the bytes for the in-memory image /// placeholder: MemoryImage(bytes), /// image: NetworkImage('https://backend.example.com/image.png'), /// ) /// ``` /// {@end-tool} class FadeInImageWithoutAuth extends StatefulWidget { /// Creates a widget that displays a [placeholder] while an [image] is loading /// then cross-fades to display the [image]. /// /// The [placeholder], [image], [fadeOutDuration], [fadeOutCurve], /// [fadeInDuration], [fadeInCurve], [alignment], [repeat], and /// [matchTextDirection] arguments must not be null. const FadeInImageWithoutAuth({ Key key, @required this.placeholder, @required this.image, this.fadeOutDuration = const Duration(milliseconds: 300), this.fadeOutCurve = Curves.easeOut, this.fadeInDuration = const Duration(milliseconds: 700), this.fadeInCurve = Curves.easeIn, this.width, this.height, this.fit, this.alignment = Alignment.center, this.repeat = ImageRepeat.noRepeat, this.matchTextDirection = false, }) : assert(placeholder != null), assert(image != null), assert(fadeOutDuration != null), assert(fadeOutCurve != null), assert(fadeInDuration != null), assert(fadeInCurve != null), assert(alignment != null), assert(repeat != null), assert(matchTextDirection != null), super(key: key); /// Creates a widget that uses a placeholder image stored in memory while /// loading the final image from the network. /// /// [placeholder] contains the bytes of the in-memory image. /// /// [image] is the URL of the final image. /// /// [placeholderScale] and [imageScale] are passed to their respective /// [ImageProvider]s (see also [ImageInfo.scale]). /// /// The [placeholder], [image], [placeholderScale], [imageScale], /// [fadeOutDuration], [fadeOutCurve], [fadeInDuration], [fadeInCurve], /// [alignment], [repeat], and [matchTextDirection] arguments must not be /// null. /// /// See also: /// /// * [new Image.memory], which has more details about loading images from /// memory. /// * [new Image.network], which has more details about loading images from /// the network. FadeInImageWithoutAuth.memoryNetwork({ Key key, @required Uint8List placeholder, @required String image, double placeholderScale = 1.0, double imageScale = 1.0, this.fadeOutDuration = const Duration(milliseconds: 300), this.fadeOutCurve = Curves.easeOut, this.fadeInDuration = const Duration(milliseconds: 700), this.fadeInCurve = Curves.easeIn, this.width, this.height, this.fit, this.alignment = Alignment.center, this.repeat = ImageRepeat.noRepeat, this.matchTextDirection = false, }) : assert(placeholder != null), assert(image != null), assert(placeholderScale != null), assert(imageScale != null), assert(fadeOutDuration != null), assert(fadeOutCurve != null), assert(fadeInDuration != null), assert(fadeInCurve != null), assert(alignment != null), assert(repeat != null), assert(matchTextDirection != null), placeholder = MemoryImage(placeholder, scale: placeholderScale), image = NetworkImage(image, scale: imageScale), super(key: key); /// Creates a widget that uses a placeholder image stored in an asset bundle /// while loading the final image from the network. /// /// [placeholder] is the key of the image in the asset bundle. /// /// [image] is the URL of the final image. /// /// [placeholderScale] and [imageScale] are passed to their respective /// [ImageProvider]s (see also [ImageInfo.scale]). /// /// If [placeholderScale] is omitted or is null, the pixel-density-aware asset /// resolution will be attempted for the [placeholder] image. Otherwise, the /// exact asset specified will be used. /// /// The [placeholder], [image], [imageScale], [fadeOutDuration], /// [fadeOutCurve], [fadeInDuration], [fadeInCurve], [alignment], [repeat], /// and [matchTextDirection] arguments must not be null. /// /// See also: /// /// * [new Image.asset], which has more details about loading images from /// asset bundles. /// * [new Image.network], which has more details about loading images from /// the network. FadeInImageWithoutAuth.assetNetwork({ Key key, @required String placeholder, @required String image, AssetBundle bundle, double placeholderScale, double imageScale = 1.0, this.fadeOutDuration = const Duration(milliseconds: 300), this.fadeOutCurve = Curves.easeOut, this.fadeInDuration = const Duration(milliseconds: 700), this.fadeInCurve = Curves.easeIn, this.width, this.height, this.fit, this.alignment = Alignment.center, this.repeat = ImageRepeat.noRepeat, this.matchTextDirection = false, }) : assert(placeholder != null), assert(image != null), placeholder = placeholderScale != null ? ExactAssetImage(placeholder, bundle: bundle, scale: placeholderScale) : AssetImage(placeholder, bundle: bundle), assert(imageScale != null), assert(fadeOutDuration != null), assert(fadeOutCurve != null), assert(fadeInDuration != null), assert(fadeInCurve != null), assert(alignment != null), assert(repeat != null), assert(matchTextDirection != null), image = NetworkImageWithoutAuth(image, scale: imageScale), super(key: key); /// Image displayed while the target [image] is loading. final ImageProvider placeholder; /// The target image that is displayed. final ImageProvider image; /// The duration of the fade-out animation for the [placeholder]. final Duration fadeOutDuration; /// The curve of the fade-out animation for the [placeholder]. final Curve fadeOutCurve; /// The duration of the fade-in animation for the [image]. final Duration fadeInDuration; /// The curve of the fade-in animation for the [image]. final Curve fadeInCurve; /// If non-null, require the image to have this width. /// /// If null, the image will pick a size that best preserves its intrinsic /// aspect ratio. This may result in a sudden change if the size of the /// placeholder image does not match that of the target image. The size is /// also affected by the scale factor. final double width; /// If non-null, require the image to have this height. /// /// If null, the image will pick a size that best preserves its intrinsic /// aspect ratio. This may result in a sudden change if the size of the /// placeholder image does not match that of the target image. The size is /// also affected by the scale factor. final double height; /// How to inscribe the image into the space allocated during layout. /// /// The default varies based on the other fields. See the discussion at /// [paintImage]. final BoxFit fit; /// How to align the image within its bounds. /// /// The alignment aligns the given position in the image to the given position /// in the layout bounds. For example, an [Alignment] alignment of (-1.0, /// -1.0) aligns the image to the top-left corner of its layout bounds, while an /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the /// image with the bottom right corner of its layout bounds. Similarly, an /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the /// middle of the bottom edge of its layout bounds. /// /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a /// [AlignmentDirectional]), then an ambient [Directionality] widget /// must be in scope. /// /// Defaults to [Alignment.center]. /// /// See also: /// /// * [Alignment], a class with convenient constants typically used to /// specify an [AlignmentGeometry]. /// * [AlignmentDirectional], like [Alignment] for specifying alignments /// relative to text direction. final AlignmentGeometry alignment; /// How to paint any portions of the layout bounds not covered by the image. final ImageRepeat repeat; /// Whether to paint the image in the direction of the [TextDirection]. /// /// If this is true, then in [TextDirection.ltr] contexts, the image will be /// drawn with its origin in the top left (the "normal" painting direction for /// images); and in [TextDirection.rtl] contexts, the image will be drawn with /// a scaling factor of -1 in the horizontal direction so that the origin is /// in the top right. /// /// This is occasionally used with images in right-to-left environments, for /// images that were designed for left-to-right locales. Be careful, when /// using this, to not flip images with integral shadows, text, or other /// effects that will look incorrect when flipped. /// /// If this is true, there must be an ambient [Directionality] widget in /// scope. final bool matchTextDirection; @override State<StatefulWidget> createState() => _FadeInImageState(); } /// The phases a [FadeInImage] goes through. @visibleForTesting enum FadeInImagePhase { /// The initial state. /// /// We do not yet know whether the target image is ready and therefore no /// animation is necessary, or whether we need to use the placeholder and /// wait for the image to load. start, /// Waiting for the target image to load. waiting, /// Fading out previous image. fadeOut, /// Fading in new image. fadeIn, /// Fade-in complete. completed, } typedef _ImageProviderResolverListener = void Function(); class _ImageProviderResolver { _ImageProviderResolver({ @required this.state, @required this.listener, }); final _FadeInImageState state; final _ImageProviderResolverListener listener; FadeInImageWithoutAuth get widget => state.widget; ImageStream _imageStream; ImageInfo _imageInfo; void resolve(ImageProvider provider) { final ImageStream oldImageStream = _imageStream; _imageStream = provider.resolve(createLocalImageConfiguration( state.context, size: widget.width != null && widget.height != null ? Size(widget.width, widget.height) : null )); assert(_imageStream != null); if (_imageStream.key != oldImageStream?.key) { oldImageStream?.removeListener(_handleImageChanged); _imageStream.addListener(_handleImageChanged); } } void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) { _imageInfo = imageInfo; listener(); } void stopListening() { _imageStream?.removeListener(_handleImageChanged); } } class _FadeInImageState extends State<FadeInImageWithoutAuth> with TickerProviderStateMixin { _ImageProviderResolver _imageResolver; _ImageProviderResolver _placeholderResolver; AnimationController _controller; Animation<double> _animation; FadeInImagePhase _phase = FadeInImagePhase.start; FadeInImagePhase get phase => _phase; @override void initState() { _imageResolver = _ImageProviderResolver(state: this, listener: _updatePhase); _placeholderResolver = _ImageProviderResolver(state: this, listener: () { setState(() { // Trigger rebuild to display the placeholder image }); }); _controller = AnimationController( value: 1.0, vsync: this, ); _controller.addListener(() { setState(() { // Trigger rebuild to update opacity value. }); }); _controller.addStatusListener((AnimationStatus status) { _updatePhase(); }); super.initState(); } @override void didChangeDependencies() { _resolveImage(); super.didChangeDependencies(); } @override void didUpdateWidget(FadeInImageWithoutAuth oldWidget) { super.didUpdateWidget(oldWidget); if (widget.image != oldWidget.image || widget.placeholder != oldWidget.placeholder) _resolveImage(); } @override void reassemble() { _resolveImage(); // in case the image cache was flushed super.reassemble(); } void _resolveImage() { _imageResolver.resolve(widget.image); // No need to resolve the placeholder if we are past the placeholder stage. if (_isShowingPlaceholder) _placeholderResolver.resolve(widget.placeholder); if (_phase == FadeInImagePhase.start) _updatePhase(); } void _updatePhase() { setState(() { switch (_phase) { case FadeInImagePhase.start: if (_imageResolver._imageInfo != null) _phase = FadeInImagePhase.completed; else _phase = FadeInImagePhase.waiting; break; case FadeInImagePhase.waiting: if (_imageResolver._imageInfo != null) { // Received image data. Begin placeholder fade-out. _controller.duration = widget.fadeOutDuration; _animation = CurvedAnimation( parent: _controller, curve: widget.fadeOutCurve, ); _phase = FadeInImagePhase.fadeOut; _controller.reverse(from: 1.0); } break; case FadeInImagePhase.fadeOut: if (_controller.status == AnimationStatus.dismissed) { // Done fading out placeholder. Begin target image fade-in. _controller.duration = widget.fadeInDuration; _animation = CurvedAnimation( parent: _controller, curve: widget.fadeInCurve, ); _phase = FadeInImagePhase.fadeIn; _placeholderResolver.stopListening(); _controller.forward(from: 0.0); } break; case FadeInImagePhase.fadeIn: if (_controller.status == AnimationStatus.completed) { // Done finding in new image. _phase = FadeInImagePhase.completed; } break; case FadeInImagePhase.completed: // Nothing to do. break; } }); } @override void dispose() { _imageResolver.stopListening(); _placeholderResolver.stopListening(); _controller.dispose(); super.dispose(); } bool get _isShowingPlaceholder { assert(_phase != null); switch (_phase) { case FadeInImagePhase.start: case FadeInImagePhase.waiting: case FadeInImagePhase.fadeOut: return true; case FadeInImagePhase.fadeIn: case FadeInImagePhase.completed: return false; } return null; } ImageInfo get _imageInfo { return _isShowingPlaceholder ? _placeholderResolver._imageInfo : _imageResolver._imageInfo; } @override Widget build(BuildContext context) { assert(_phase != FadeInImagePhase.start); final ImageInfo imageInfo = _imageInfo; return RawImage( image: imageInfo?.image, width: widget.width, height: widget.height, scale: imageInfo?.scale ?? 1.0, color: Color.fromRGBO(255, 255, 255, _animation?.value ?? 1.0), colorBlendMode: BlendMode.modulate, fit: widget.fit, alignment: widget.alignment, repeat: widget.repeat, matchTextDirection: widget.matchTextDirection, ); } @override void debugFillProperties(DiagnosticPropertiesBuilder description) { super.debugFillProperties(description); description.add(EnumProperty<FadeInImagePhase>('phase', _phase)); description.add(DiagnosticsProperty<ImageInfo>('pixels', _imageInfo)); description.add(DiagnosticsProperty<ImageStream>('image stream', _imageResolver._imageStream)); description.add(DiagnosticsProperty<ImageStream>('placeholder stream', _placeholderResolver._imageStream)); } }
image_provider_without_auth.dart 拷贝image_provider.dart中NetworkImage相关部分的代码。
import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'dart:ui' as ui show Codec; import 'dart:ui' show Size, Locale, TextDirection, hashValues; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/src/painting/image_cache.dart'; import 'package:flutter/src/painting/image_stream.dart'; import 'package:flutter/src/painting/image_provider.dart'; import 'package:flutter/src/painting/binding.dart'; class NetworkImageWithoutAuth extends ImageProvider<NetworkImageWithoutAuth> { /// Creates an object that fetches the image at the given URL. /// /// The arguments must not be null. const NetworkImageWithoutAuth(this.url, { this.scale = 1.0 , this.headers }) : assert(url != null), assert(scale != null); /// The URL from which the image will be fetched. final String url; /// The scale to place in the [ImageInfo] object of the image. final double scale; /// The HTTP headers that will be used with [HttpClient.get] to fetch image from network. final Map<String, String> headers; @override Future<NetworkImageWithoutAuth> obtainKey(ImageConfiguration configuration) { return SynchronousFuture<NetworkImageWithoutAuth>(this); } @override ImageStreamCompleter load(NetworkImageWithoutAuth key) { return MultiFrameImageStreamCompleter( codec: _loadAsync(key), scale: key.scale, informationCollector: (StringBuffer information) { information.writeln('Image provider: $this'); information.write('Image key: $key'); } ); } static final HttpClient _httpClient = HttpClient(); Future<ui.Codec> _loadAsync(NetworkImageWithoutAuth key) async { assert(key == this); //解决不安全证书校验通不过的问题 _httpClient.badCertificateCallback = (X509Certificate cert,String host,int port){ return true; }; final Uri resolved = Uri.base.resolve(key.url); final HttpClientRequest request = await _httpClient.getUrl(resolved); headers?.forEach((String name, String value) { request.headers.add(name, value); }); final HttpClientResponse response = await request.close(); if (response.statusCode != HttpStatus.ok) throw Exception('HTTP request failed, statusCode: ${response?.statusCode}, $resolved'); final Uint8List bytes = await consolidateHttpClientResponseBytes(response); if (bytes.lengthInBytes == 0) throw Exception('NetworkImage is an empty file: $resolved'); return PaintingBinding.instance.instantiateImageCodec(bytes); } @override bool operator ==(dynamic other) { if (other.runtimeType != runtimeType) return false; final NetworkImageWithoutAuth typedOther = other; return url == typedOther.url && scale == typedOther.scale; } @override int get hashCode => hashValues(url, scale); @override String toString() => '$runtimeType("$url", scale: $scale)'; }
使用:
import 'package:flutter/material.dart'; import 'package:flutter_module/imageload/fade_in_image_without_auth.dart'; ··· new FadeInImageWithoutAuth.assetNetwork( placeholder: 'assets/images/pic_hzw.jpeg', image: 'https://xxxx/xxxx.jpg', fit: BoxFit.fill,) ···
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/16186.html