当工程发展到一定程度的时候,随着业务的变化,有些功能成冗余功能,这时候就需要对工程进生清理,否则App的安装包会变得臃肿,清理的内容主要包括:音视频图资源、无效代码、无效插件。
第一步要确定哪些业务功能是无用的,因为这是判断资源文件和插件是否有效的前提。然后手动清理代码,这一步没有特别的技巧,靠人工细活,避免误伤有效代码。
清理插件的方法,参考文章
清理资源文件的方法,这也是比较难的事情,但如果工程架构做得好,可以事半功倍,就是把所有的资源文件引用通过脚本影射成Dart变量,在Flutter中通过变量引用资源文件,这样当清理完资源后有没有错误马上就能看出来,脚本如下:
// 这里修改成自己的图片所在文件路径
import 'dart:io';
// const DIR = "lib/res/ecom"; //images文件路径
// const dartFileName = "lib/res/image_id_ecom.dart"; //image写成dart常量,方便引用
// const dartFileClearName = "XXXClass";
// const DIR = "lib/res/inter"; //images文件路径
// const dartFileName = "lib/res/image_id_inter.dart"; //image写成dart常量,方便引用
// const dartFileClearName = "XXXClass";
const DIR = "lib/res/images"; //images文件路径
const dartFileName = "lib/res/image_id.dart"; //image写成dart常量,方便引用
const dartFileClearName = "XXXClass";
const expanded_name_filters = ".png .jpg .webp .svg"; //要读取的文件类型;
main() {
var dict = <String, String>{};
DIR.split(' ').where((element) => element.isNotEmpty).forEach((element) {
var dict1 =
listFilesToDart(element, expanded_name_filters.split(' '), true);
dict.addAll(dict1);
});
dict.forEach((key, value) {
var rootDir = Directory('lib/');
bool isExist = traverseAllFiles(rootDir, key);
if (isExist) {
return;
}
deleteFile(dict[key]!);
});
generalImg();
}
//遍历文件夹下的所有文件,如果文件内容不包含key,返回true,否则返回false
bool traverseAllFiles(Directory rootDir, String key) {
//遍历rootDir下的所有文件及文件夹,读书文件内容,如果所有文件内容都不包含key,则删除dict[key]对应地址的文件
List<FileSystemEntity> files = rootDir.listSync();
for (var f in files) {
var isFile = FileSystemEntity.isFileSync(f.path);
if (!isFile) {
if (f.path.contains('lib/res')) {
// print('Res directory: ${f.path}');
continue;
}
// if (f.path.contains('lib/common_plugin/base')) {
// print('Directory: ${f.path}');
// }
bool isExist = traverseAllFiles(Directory(f.path), key);
if (isExist) {
return true;
}
} else {
//判断文件是否隐藏文件
if (f.path.contains('.DS_Store')) {
continue;
}
var content = (f as File).readAsStringSync();
if (content.contains(key.trim())) {
// print('正在使用的图片:$key');
// print('Used file is: ' + f.path);
return true;
}
}
}
return false;
}
deleteFile(String path) {
//删除文件
var file = File(path);
if (file.existsSync()) {
file.delete();
}
var pathSplit = path.split('/');
pathSplit.insert(pathSplit.length - 1, '3.0x');
var path3 = pathSplit.join('/');
var file3 = File(path3);
if (file3.existsSync()) {
file3.delete();
}
}
Map<String, String> listFilesToDart(
String dir, List<String> filters, bool isRecursion,
{Map<String, String>? dict}) {
dict = dict ?? {};
var directory = new Directory(dir);
List<FileSystemEntity> files = directory.listSync();
for (var f in files) {
var bool = FileSystemEntity.isFileSync(f.path);
if (!bool) {
if (isRecursion) {
dict.addAll(listFilesToDart(f.path, filters, isRecursion));
}
} else {
filters.forEach((element) {
if (f.path.endsWith(element)) {
String nameval = f.path
.replaceAll('\\', '/')
.replaceAll("2.0x/", "")
.replaceAll("3.0x/", "")
.replaceAll("4.0x/", "");
String namekey = nameval
.trim()
.split('/')
.last
.replaceAll('.', '_')
.replaceAll('-', '_');
dict![namekey] =
nameval; //生成的常量名(namekey)以截取图片文件名命名,不考虑在多个文件夹内有同名图片的情况(如果有会覆盖)
}
});
}
}
return dict;
}
generalImg() {
var dartFile = File(dartFileName);
var openWrite = dartFile.openWrite(mode: FileMode.write);
var dict = <String, String>{};
DIR.split(' ').where((element) => element.isNotEmpty).forEach((element) {
var dict1 =
listFilesToDart(element, expanded_name_filters.split(' '), true);
dict.addAll(dict1);
});
openWrite
.write('''///图片资源id\nclass $dartFileClearName{\n $dartFileClearName._();\n''');
dict.forEach((key, value) {
openWrite.write(" static const " + key + " = \"" + dict[key]! + "\";\n");
});
openWrite.write("}");
openWrite.close();
}
最后,通过官方工具再看看还有哪些影响安装大小的文件 Flutter包体积检测,和业务商量是否要以拆除掉。然后再采用前面提到的方法再来一遍。
实践证明,公司的一个App安装包从原来500M,减为目前的250M。这里有个奇怪的现象目前还没有解决,用M1的macOS会比M3的打出来的包体积大一倍。