美文网首页
Flutter Image加载图片流程分析

Flutter Image加载图片流程分析

作者: roam_k | 来源:发表于2020-12-04 10:00 被阅读0次

    从最基础的Image加载网络图片方法开始:

    Image(image: NetworkImage(url));
    

    查看Image源码可以看到,Image是一个StatefulWidget。设置了一个必选参数image,类型为ImageProvider。

     const Image({
        Key key,
        @required this.image,
        this.frameBuilder,
        this.loadingBuilder,
        this.errorBuilder,
       ......
      }) 
    

    ImageProvider是一个抽象类,内部定义图片数据的获取和加载方法。主要有下面几个方法:

     ImageStream resolve(ImageConfiguration configuration){
            //返回的是一个图片stream数据流
            ...
     }
     
     Future<bool> evict({ ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty }){
     //缓存移除方法
     ...
     }
     
     //获取缓存资源key
     Future<T> obtainKey(ImageConfiguration configuration);
     
     //load数据方法
     ImageStreamCompleter load(T key, DecoderCallback decode);
    

    我们的例子中使用的是NetworkImage作为ImageProvider,找到NetworkImage的源码位置。看一看load方法时如何实现的:

        @override
      ImageStreamCompleter load(image_provider.NetworkImage key, image_provider.DecoderCallback decode) {
            //chunkEvents用于传递数据的加载进度
        final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
    
        return MultiFrameImageStreamCompleter(
          codec: _loadAsync(key as NetworkImage, chunkEvents, decode),
          chunkEvents: chunkEvents.stream,
          scale: key.scale,
          debugLabel: key.url,
          informationCollector: () {
            return <DiagnosticsNode>[
              DiagnosticsProperty<image_provider.ImageProvider>('Image provider', this),
              DiagnosticsProperty<image_provider.NetworkImage>('Image key', key),
            ];
          },
        );
      }
    

    可以看到,load方法最终返回的是一个ImageStreamCompleter的实现类。我们先看看他的构造方法,特别关注一下codec参数。

    MultiFrameImageStreamCompleter({
        required Future<ui.Codec> codec,
        required double scale,
        String? debugLabel,
        Stream<ImageChunkEvent>? chunkEvents,
        InformationCollector? informationCollector,
      }) 
    

    codec参数是一个Future<ui.Codec>的必选参数,Codec 是处理图片编解码的类的一个handler,查看其内部方法,可以发现均是native方法。官方注释提示说:此类为engine创建,请勿手动实例化。也就是说图片的编解码逻辑不是在Dart 代码部分实现,而是在flutter engine中实现的。

    /// A handle to an image codec.
    ///
    /// This class is created by the engine, and should not be instantiated
    /// or extended directly.
    ///
    /// To obtain an instance of the [Codec] interface, see
    /// [instantiateImageCodec].
    @pragma('vm:entry-point')
    class Codec extends NativeFieldWrapperClass2 {
      @pragma('vm:entry-point')
      Codec._();
    
      int get frameCount native 'Codec_frameCount';
    
      int get repetitionCount native 'Codec_repetitionCount';
    
      Future<FrameInfo> getNextFrame() {
        return _futurize(_getNextFrame);
      }
    
      String _getNextFrame(_Callback<FrameInfo> callback) native 'Codec_getNextFrame';
    
      void dispose() native 'Codec_dispose';
    }
    

    再回到NetworkImage.load方法,MultiFrameImageStreamCompleter创建时传递的codec参数由_loadAsync方法提供

     Future<ui.Codec> _loadAsync(
        NetworkImage key,
        StreamController<ImageChunkEvent> chunkEvents,
        image_provider.DecoderCallback decode,
      ) async {
        try {
                assert(key == this);
          final Uri resolved = Uri.base.resolve(key.url);
    
                //通过HttpClient请求网络数据
          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 image_provider.NetworkImageLoadException(statusCode: response.statusCode, uri: resolved);
          }
                //发送数据加载进度事件
          final Uint8List bytes = await consolidateHttpClientResponseBytes(
            response,
            onBytesReceived: (int cumulative, int? total) {
              chunkEvents.add(ImageChunkEvent(
                cumulativeBytesLoaded: cumulative,
                expectedTotalBytes: total,
              ));
            },
          );
          if (bytes.lengthInBytes == 0)
            throw Exception('NetworkImage is an empty file: $resolved');
    
                //返回解码后的数据
          return decode(bytes);
        } catch (e) {
        
          scheduleMicrotask(() {
            PaintingBinding.instance!.imageCache!.evict(key);
          });
          rethrow;
        } finally {
          chunkEvents.close();
        }
      }
    

    现在再来看MultiFrameImageStreamCompleter。内部已经有了数据源和解码器,就看如何为image提供数据了。

     void _handleCodecReady(ui.Codec codec) {
        _codec = codec;
        assert(_codec != null);
    
        if (hasListeners) {
        //这里了进入解码方法
          _decodeNextFrameAndSchedule();
        }
      }
      
      
      Future<void> _decodeNextFrameAndSchedule() async {
        try {
          _nextFrame = await _codec!.getNextFrame();
        } catch (exception, stack) {
          reportError(
            context: ErrorDescription('resolving an image frame'),
            exception: exception,
            stack: stack,
            informationCollector: _informationCollector,
            silent: true,
          );
          return;
        }
        if (_codec!.frameCount == 1) {
          //找到了发送图片帧方法
          _emitFrame(ImageInfo(image: _nextFrame!.image, scale: _scale, debugLabel: debugLabel));
          return;
        }
        _scheduleAppFrame();
      }
    

    当图片资源只有一帧的时候,会直接调用_emitFrame发送封装好的ImageInfo数据。

     void _emitFrame(ImageInfo imageInfo) {
        setImage(imageInfo);
        _framesEmitted += 1;
      }
      
       @protected
      void setImage(ImageInfo image) {
        _currentImage = image;
        if (_listeners.isEmpty)
          return;
        // Make a copy to allow for concurrent modification.
        final List<ImageStreamListener> localListeners =
            List<ImageStreamListener>.from(_listeners);
        for (final ImageStreamListener listener in localListeners) {
          try {
            listener.onImage(image, false);
          } catch (exception, stack) {
            reportError(
              context: ErrorDescription('by an image listener'),
              exception: exception,
              stack: stack,
            );
          }
        }
      }
    

    这里可以看到,setImage内部会通过listener监听将数据回传。那listener是什么时候设置的呢?
    之前有说到,image是一个StatefulWidget。查看内部的_ImageState的didChangeDependencies方法

    @override
      void didChangeDependencies() {
        _updateInvertColors();
        _resolveImage();
    
        if (TickerMode.of(context))
            //设置数据流监听
          _listenToStream();
        else
          _stopListeningToStream();
    
        super.didChangeDependencies();
      }
      
       void _listenToStream() {
        if (_isListeningToStream)
          return;
          //这里便又回到了image_stream的listener设置
        _imageStream.addListener(_getListener());
        _isListeningToStream = true;
      }
    

    最终会在监听中调用setDate方法更新图片信息,完成一次图片的加载。

    //帧返回处理
    void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
        setState(() {
          _imageInfo = imageInfo;
          _loadingProgress = null;
          _frameNumber = _frameNumber == null ? 0 : _frameNumber + 1;
          _wasSynchronouslyLoaded |= synchronousCall;
        });
      }
    
        //进度返回处理
      void _handleImageChunk(ImageChunkEvent event) {
        assert(widget.loadingBuilder != null);
        setState(() {
          _loadingProgress = event;
        });
      }
    

    相关文章

      网友评论

          本文标题:Flutter Image加载图片流程分析

          本文链接:https://www.haomeiwen.com/subject/ngubwktx.html