flutter 图片大小治理

本文主要介绍如何降低flutter图片资源占用的包大小

第一个,最简单的是图片压缩,推荐这个png图片压缩

第二是本文的重点,降低图片数量,使得每张图只需要放1张。

flutter的本地图片和native的一样,默认情况下都会有1x 2x 3x,这也是官方推荐的,通过看源码,发现可以把3x的图放到外层,不需要往2x 3x文件夹里放图片,这样就可以了,只需要一份3x图,包大小能降低不少,接下来看源码。看看为什么这样是可行的

假设项目里配置是这样的

assets
    images
        only1.png
        only2.png
    2.0x
        only1.png
        only3.png
    3.0x
        only1.png

先抛结论

假设有如下代码,设备是3x,那最终加载的就是3.0x下的图片,优先选择最符合条件的

Image.asset("assets/images/only1.png")

假设有如下代码,设备是3x,那最终加载的就是最外层的图片,因为2x 和3x文件夹里都没有

Image.asset("assets/images/only2.png")

假设有如下代码,设备是3x,那最终图片是加载不出来的,因为最外层没有这张图

Image.asset("assets/images/only3.png")

Image.asset(
  String name, {
    ...
}) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, scale != null
       ? ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
       : AssetImage(name, bundle: bundle, package: package) //省略部分代码,scale不为null,走到AssetIamge,接着看
     ),

AssetImage,注意obtainKey方法,这个方法的左右就是根据原始的key和设备的分辨率计算出最适合的图片key

@override
Future obtainKey(ImageConfiguration configuration) {
  final AssetBundle chosenBundle = bundle ?? configuration.bundle ?? rootBundle;
  Completer completer;
  Future result;
    //manifest 是最外层图片的名称的集合,在当前配置下,得到的map就是。所以1x也就是最外层一定要有图片的声明
    //{"only1.png",["assets/images/only1.png","assets/images/2.0x/only1.png","assets/images/3.0x/only1.png"]}
    //{"only2.png",["assets/images/only2.png"]}
  chosenBundle.loadStructuredData>>(_kAssetManifestFileName, _manifestParser).then(
    (Map> manifest) {
    // _chooseVariant就是根据设备信息,图片名称选出最适合的路径 // 假设3x设备,3x文件夹下有这张图,那结果就是3x下的图
      final String chosenName = _chooseVariant(
        keyName,
        configuration, // 图片名称 configuration, // 设备信息: 分辨率,语言等
        manifest == null ? null : manifest[keyName], //根据对应的key取出list
      );
    // 根据取得的图片路径 算出当前图片的缩放是1x 2x 还是3x
      final double chosenScale = _parseScale(chosenName);
      final AssetBundleImageKey key = AssetBundleImageKey(
        bundle: chosenBundle,
        name: chosenName,
        scale: chosenScale,
      );
      ...//省略部分代码
  ).catchError((dynamic error, StackTrace stack) {
    ...//省略部分代码
  });
  if (result != null) {
    return result;
  }
  completer = Completer();
  return completer.future;
}

关键还是_chooseVariant方法

// main 原始图片路径
// config 设备信息
// candidates 当前图片路径下的集合 
// 解析后的数据如下 ["assets/images/only1.png","assets/images/2.0x/only1.png","assets/images/3.0x/only1.png"]
String _chooseVariant(String main, ImageConfiguration config, List candidates) {
  if (config.devicePixelRatio == null || candidates == null || candidates.isEmpty)
    return main;
  
  final SplayTreeMap mapping = SplayTreeMap();
// 遍历candidates,解析其中的缩放,缓存到mapping里,这是treemap 
  for (final String candidate in candidates)
    mapping[_parseScale(candidate)] = candidate;
   // 根据设备信息寻找最符合条件的图片
  return _findNearest(mapping, config.devicePixelRatio);
}
 
// candidates 是个treemap, 假设加载的only1.png,那candidates的结构如下
//         3.0:3.0x/only1.png
//  left  2.0:2.0x/only1.png  right null
//  left  1.0:1.0x/only1.png  right null
// value 是3.0 ,因为设备的分辨率是3.0
String _findNearest(SplayTreeMap candidates, double value) {
    // map里刚好以后3.0x的value,直接返回
  if (candidates.containsKey(value))
    return candidates[value];
    
    // 找到比value小的可以 ,最近的,假设设备是3.0,但最大的图是2x,那lower就是2.0
  final double lower = candidates.lastKeyBefore(value);
    // 找到第一个比value大的key,所以可以新建4.0 5.0的文件夹 放4x,5x的图都是可以的
  final double upper = candidates.firstKeyAfter(value);
  // 如果没有更低的,直接返回最大的
  if (lower == null)
    return candidates[upper];
  // 如果没有返回更大的,直接去最小的
  if (upper == null)
    return candidates[lower];
    // 用中间值做比较
  if (value > (lower + upper) / 2)
    return candidates[upper];
  else
    return candidates[lower];
}

至此,根据设备信息查找符合条件图片的源码分析完毕,结论

  • 1x 也就是最外层目录下 ,一定要有图片的声明,如果1x目录下没有声明图片,那即使2x,3x下有,AssertImage也不会识别
  • AssertImage 优先会取最符合当前设备分辨率的图片,如果是3.0的手机,优先取3.0 ,如果没有,取2.0,然后才是1.0
  • 综上所述,1x下放3x图片是可以的,比较现在市面上Android机绝大部分都是3x了,ios可能还会部分有2x,但也不会多了

你可能感兴趣的:(flutter 图片大小治理)