本文主要介绍如何降低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<AssetBundleImageKey> obtainKey(ImageConfiguration configuration) {
final AssetBundle chosenBundle = bundle ?? configuration.bundle ?? rootBundle;
Completer<AssetBundleImageKey> completer;
Future<AssetBundleImageKey> 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<Map<String, List<String>>>(_kAssetManifestFileName, _manifestParser).then<void>(
(Map<String, List<String>> 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<AssetBundleImageKey>();
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<String> candidates) {
if (config.devicePixelRatio == null || candidates == null || candidates.isEmpty)
return main;
final SplayTreeMap<double, String> mapping = SplayTreeMap<double, String>();
// 遍历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<double, String> 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,但也不会多了