Flutter 圖片卡頓 https 卡頓 ui卡頓

背景

由于flutter是單線程,所以即便是網(wǎng)絡(luò)請(qǐng)求也是占有ui線程的拗慨,平時(shí)正常網(wǎng)絡(luò)是沒有問題的。

但是在ios上,https請(qǐng)求會(huì)檢測(cè)OCSP晤锹,也就是證書是否吊銷,這會(huì)耗費(fèi)大量時(shí)間收厨。

當(dāng)然這個(gè)檢測(cè)跟證書是有關(guān)系的缴啡,如果證書的檢查地址在國(guó)內(nèi)就沒問題,在國(guó)外就卡死了骂际。

所以不僅僅是圖片會(huì)這樣疗琉,所有的http請(qǐng)求都會(huì)這樣。

如何解決

一種辦法就是換證書歉铝,另外一種就是使用isolate將http請(qǐng)求放入單獨(dú)的線程中執(zhí)行盈简。

首先就是要導(dǎo)入包:

isolate: ^2.0.3

然后對(duì)http請(qǐng)求和圖片重寫發(fā)起請(qǐng)求的地方

普通接口請(qǐng)求的解決方案代碼

dart文件名:api.dart

final Future<LoadBalancer> loadBalancer =
    LoadBalancer.create(2, IsolateRunner.spawn);

class Api {
  static int SERVER_ERROR = -1;
  static Dio http = init();

  static Dio init() {
    Dio http = Dio(BaseOptions(
        connectTimeout: 15000, receiveTimeout: 15000, sendTimeout: 15000));
    http.interceptors.add(LogInterceptor(
        responseBody: false,
        requestBody: false,
        requestHeader: false,
        responseHeader: false));
    return http;
  }

  static Map<String, dynamic> executeResponse(Response response) {
    if (response.statusCode >= 200 && response.statusCode < 300) {
      Map<String, dynamic> decodeMap = json.decode(response.data);
      if (decodeMap["code"] == 1) {
        return decodeMap;
      }
      if (decodeMap["code"] == 99) {
        decodeMap["msg"] = "登錄失效,請(qǐng)重新登錄";
        navigateTo(Login());
        showToast("登錄失效太示,請(qǐng)重新登錄");
        return decodeMap;
      }
      if (ObjectUtil.isEmptyString(decodeMap["msg"])) {
        decodeMap["msg"] = "服務(wù)異常";
      }
      return decodeMap;
    }
  }

  static Map<String, dynamic> executeException(e) {
    print(e);
    Map<String, dynamic> errorResponse = Map();
    errorResponse.putIfAbsent("code", () => SERVER_ERROR);
    errorResponse.putIfAbsent("msg", () => "網(wǎng)絡(luò)異常");
    return errorResponse;
  }

  //版本檢測(cè)
  static Future<Map<String, dynamic>> version(String type) {
    Map<String, dynamic> param = new Map();
    param.putIfAbsent("type", () => type);
    return getRequest("/app/version", param);
  }

  //get請(qǐng)求柠贤,這里跟post請(qǐng)求可以一起改一下,寫demo比較懶了
  static Future<Map<String, dynamic>> getRequest(
      String url, Map<String, dynamic> params) async {
    bool externalApi = true;
    if (!url.startsWith("http")) {
      url = BuildParam.api + "/api.php" + url;
      externalApi = false;
    }
    final ReceivePort receivePort = ReceivePort();
    final LoadBalancer lb = await loadBalancer;
    // 開啟一個(gè)線程
    await lb.run<dynamic, SendPort>(dataLoader, receivePort.sendPort);
    final SendPort sendPort = await receivePort.first;
    final ReceivePort resultPort = ReceivePort();
    sendPort.send([url, resultPort.sendPort, "get", params, externalApi]);
    Map<String, dynamic> responseMap = await resultPort.first;
    BotToast.closeAllLoading();
    if (externalApi) {
      return Future.value(responseMap);
    }
    if (responseMap["code"] != 1) {
      return Future.error(responseMap);
    }
    return Future.value(responseMap);
  }

  //post請(qǐng)求类缤,這里跟get請(qǐng)求可以一起改一下臼勉,寫demo比較懶了
  static Future<Map<String, dynamic>> postRequest(
      String url, Map<String, dynamic> params) async {
    bool externalApi = true;
    if (!url.startsWith("http")) {
      url = BuildParam.api + "/api.php" + url;
      externalApi = false;
    }
    final ReceivePort receivePort = ReceivePort();
    final LoadBalancer lb = await loadBalancer;
    // 開啟一個(gè)線程
    await lb.run<dynamic, SendPort>(dataLoader, receivePort.sendPort);
    final SendPort sendPort = await receivePort.first;
    final ReceivePort resultPort = ReceivePort();
    sendPort.send([url, resultPort.sendPort, "post", params, externalApi]);
    Map<String, dynamic> responseMap = await resultPort.first;
    BotToast.closeAllLoading();
    if (externalApi) {
      return Future.value(responseMap);
    }
    if (responseMap["code"] != 1) {
      return Future.error(responseMap);
    }
    return Future.value(responseMap);
  }

  // isolate的綁定方法
  static dataLoader(SendPort sendPort) async {
    final ReceivePort receivePort = ReceivePort();
    sendPort.send(receivePort.sendPort);
    receivePort.listen((msg) async {
      String requestURL = msg[0];
      SendPort callbackPort = msg[1];
      String requestMethod = msg[2];
      Map<String, dynamic> requestParam = msg[3];
      bool externalApi = msg[4];

      try {
        Response response = null;
        if (requestMethod == "get") {
          response = await http.get(requestURL, queryParameters: requestParam);
        }
        if (requestMethod == "post") {
          response = await http.post(requestURL, queryParameters: requestParam);
        }
        // 回調(diào)返回值給調(diào)用者,這里直接返回response是不行的餐弱,必須解碼出來宴霸,不然我也想這樣做。
        if (externalApi) {
          Map map = json.decode(response.data);
          callbackPort.send(map);
        } else {
          Map map = executeResponse(response);
          callbackPort.send(map);
        }
      } catch (e) {
        Map map = executeException(e);
        callbackPort.send(map);
      }
    });
  }
}

調(diào)用:

Api.version("android").then()  or await Api.version("android");

圖片的解決方案

主要原理就是重寫一下圖片加載的邏輯膏蚓,將其中發(fā)起http請(qǐng)求讀取字節(jié)碼的地方修改一下猖败。

新建image_async.dart

class FadeInImageWithoutAuth extends StatefulWidget {
  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.network(String image,{
    Key key,
    String placeholder = "",
    AssetBundle bundle,
    double placeholderScale,
    double imageScale = 1.0,
    this.fadeOutDuration = const Duration(milliseconds: 200),
    this.fadeOutCurve = Curves.easeOut,
    this.fadeInDuration = const Duration(milliseconds: 200),
    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)
            : NetworkImageWithoutAuth(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,
    this.imageStreamListener
  });

  final _FadeInImageState state;
  final _ImageProviderResolverListener listener;
  final ImageStreamListener imageStreamListener;

  FadeInImageWithoutAuth get widget => state.widget;

  ImageStream _imageStream;
  ImageInfo _imageInfo;

  void resolve(ImageProvider provider) {
    final ImageStream oldImageStream = _imageStream;
    final ImageStreamListener listener = ImageStreamListener(_handleImageChanged);
    _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(listener);
      _imageStream.addListener(listener);
    }
  }

  void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) {
    _imageInfo = imageInfo;
    listener();
  }

  void stopListening() {
    _imageStream?.removeListener(imageStreamListener);
  }
}

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() {
    if(!mounted){
      return;
    }
    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_async_provider.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, DecoderCallback decode) {
    return MultiFrameImageStreamCompleter(
      codec: _loadAsync(key),
      scale: key.scale,
      informationCollector: () sync* {
        yield DiagnosticsProperty<ImageProvider>('Image provider', this);
        yield DiagnosticsProperty<NetworkImageWithoutAuth>('Image key', key);
      },
    );
  }

  static final HttpClient _httpClient = HttpClient();

  Future<ui.Codec> _loadAsync(NetworkImageWithoutAuth key) async {
    assert(key == this);
    //解決不安全證書校驗(yàn)通不過的問題
    _httpClient.badCertificateCallback =
        (X509Certificate cert, String host, int port) {
      return true;
    };
    final Uint8List bytes = await ImageApi.image(key.url);
    if (bytes.lengthInBytes == 0)
      throw Exception('NetworkImage is an empty file');

    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)';
}

新建image_api.dart

import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';

import 'package:flutter/foundation.dart';
import 'package:isolate/isolate_runner.dart';
import 'package:isolate/load_balancer.dart';

final Future<LoadBalancer> loadBalancer =
    LoadBalancer.create(2, IsolateRunner.spawn);

class ImageApi {
  static final HttpClient _httpClient = HttpClient();

  static Future<Uint8List> image(String url) async {
    return getRequest(url);
  }

  static Future<Uint8List> getRequest(String url) async {
    final ReceivePort receivePort = ReceivePort();
    final LoadBalancer lb = await loadBalancer;
    // 開啟一個(gè)線程
    await lb.run<dynamic, SendPort>(dataLoader, receivePort.sendPort);
    final SendPort sendPort = await receivePort.first;
    final ReceivePort resultPort = ReceivePort();
    sendPort.send([url, resultPort.sendPort]);
    Uint8List response = await resultPort.first;
    return Future.value(response);
  }

  // isolate的綁定方法
  static dataLoader(SendPort sendPort) async {
    final ReceivePort receivePort = ReceivePort();
    sendPort.send(receivePort.sendPort);
    receivePort.listen((msg) async {
      String requestURL = msg[0];
      SendPort callbackPort = msg[1];

      final Uri resolved = Uri.base.resolve(requestURL);
      final HttpClientRequest request = await _httpClient.getUrl(resolved);
      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);

      // 回調(diào)返回值給調(diào)用者
      callbackPort.send(bytes);
    });
  }
}

使用:

FadeInImageWithoutAuth.network("圖片地址", fit: BoxFit.cover)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市降允,隨后出現(xiàn)的幾起案子恩闻,更是在濱河造成了極大的恐慌,老刑警劉巖剧董,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幢尚,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡翅楼,警方通過查閱死者的電腦和手機(jī)尉剩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毅臊,“玉大人理茎,你說我怎么就攤上這事。” “怎么了皂林?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵朗鸠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我础倍,道長(zhǎng)烛占,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任沟启,我火速辦了婚禮忆家,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘德迹。我一直安慰自己芽卿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布胳搞。 她就那樣靜靜地躺著蹬竖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪流酬。 梳的紋絲不亂的頭發(fā)上币厕,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音芽腾,去河邊找鬼旦装。 笑死,一個(gè)胖子當(dāng)著我的面吹牛摊滔,可吹牛的內(nèi)容都是我干的阴绢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼艰躺,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼呻袭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腺兴,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤左电,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后页响,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體篓足,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年闰蚕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了栈拖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡没陡,死狀恐怖涩哟,靈堂內(nèi)的尸體忽然破棺而出索赏,到底是詐尸還是另有隱情,我是刑警寧澤贴彼,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布潜腻,位于F島的核電站,受9級(jí)特大地震影響锻弓,放射性物質(zhì)發(fā)生泄漏砾赔。R本人自食惡果不足惜蝌箍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一青灼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧妓盲,春花似錦杂拨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至筋粗,卻和暖如春策橘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背娜亿。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工丽已, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人买决。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓沛婴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親督赤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嘁灯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354