前言
网易智造APP是网易严选旗下的统一管理智能硬件产品的APP。从2019年9月起,为了进一步提升APP接入智能硬件产品的效率,我们团队开始使用Flutter技术来实现一些业务模块。在这个过程中,我们遇到的最大痛点之一就是:Flutter的资源管理方案原始、粗陋且低效,给我们开发添加了诸多不便——这些不便主要表现在以下几个问题上:
- 需要手动创建、维护和管理资源目录以及资源
- 需要手动为资源添加声明到
pubspec.yaml
- 在代码中只能通过字符串的方式应用资源
为此,我们研发了一个资源管理工具Flr(Flutter-R)来帮助团队在Flutter项目开发中更便捷地管理资源,更安全快捷地应用资源。
什么是Flr
Flr,全称:Flutter-R
,是一个使用RubyGem
开发的CLI
工具,其提供了类似AAPT
(Android Asset Packaging Tool)的功能,帮助Flutter开发者在修改了项目的资源后,自动为所有资源添加声明到 pubspec.yaml
以及生成R.dart
文件。R.dart
类似Android中的R.java
,让Flutter开发者可以在代码中通过资源ID的方式应用资源。
截止到目前,Flr
支持以下特性:
- 支持“自动添加资源声明到
pubspec.yaml
和自动生成R.dart
文件”的自动化服务,该服务可以通过手动触发,也可以通过监控资源变化触发 - 支持处理图片资源(
.png
、.jpg
、.jpeg
、.gif
、.webp
、.icon
、.bmp
、.wbmp
、.svg
) - 支持处理文本资源(
.txt
、.json
、.yaml
、.xml
) - 支持处理图片资源变体
- 支持处理带有“坏味道”的文件名的资源:
- 文件名带有非法字符,如空格、
~
、@
、#
等(非法字符是指不在合法字符集合内的字符;合法字符集合的字符有:0-9
、A-Z
、a-z
、_
、$
) - 文件名以数字或者
_
或者$
字符开头
- 文件名带有非法字符,如空格、
以下是一个演示如何在Flutter项目中使用Flr
的示例动图(更详细的使用介绍请看下面的用法介绍章节):

如何安装和升级Flr
安装和升级Flr
只需要在终端运行一句命令即可:sudo gem install flr
。
PS:对于使用Windows系统的Flutter开发者,强烈建议在 WSL(Windows Subsystem for Linux) 环境下安装和运行Flr
如何使用Flr
下面将会详细介绍如何在你的项目中使用Flr
。
1. 初始化Flutter项目
在Flutter项目根目录下,运行初始化命令:
cd flutter_project_dir
flr init
flr init
命令会为当前项目创建一个Flrfile.yaml
文件,以及在pubspec.yaml
中添加 r_dart_library 依赖库的声明。其中,Flrfile.yaml
文件用于供Flutter开发者配置需要Flr
扫描的资源目录路径。
2. 编辑Flrfile.yaml
打开Flutter项目根目录下的Flrfile.yaml
文件,然后按照文件内的提示进行编辑,配置需要Flr
扫描的资源目录路径,如:
assets:
# config the image asset directories that need to be scanned
# supported image assets: [".png", ".jpg", ".jpeg", ".gif", ".webp", ".icon", ".bmp", ".wbmp", ".svg"]
# config example: - lib/assets/images
images:
- lib/assets/images
# config the text asset directories that need to be scanned
# supported text assets: [".txt", ".json", ".yaml", ".xml"]
# config example: - lib/assets/texts
texts:
- lib/assets/jsons
- lib/assets/yamls
3. 触发自动化服务
Flr
提供了2种方式供Flutter开发者触发“自动添加资源声明到 pubspec.yaml
和自动生成R.dart
文件”的自动化服务:
- 手动触发:运行
flr generate
命令 - 监控资源变化触发:运行
flr monitor
命令
下面将会详细介绍这两者。
方式一:手动触发自动化服务(flr generate
)
手动触发自动化服务,通过运行flr generate
命令实现。
flr generate
命令运行后,flr generate
命令会对Flrfile.yaml
中配置的资源目录进行一次资源扫描,然后为扫描到的资源添加声明到pubspec.yaml
,并生成R.dart
文件。
方式二:监控资源变化触发自动化服务 (flr monitor
)
监控资源变化触发自动化服务,通过运行flr monitor
命令实现。
flr monitor
命令运行后,flr monitor
命令会启动一个持续监控资源变化的监控服务。该监控服务会对Flrfile.yaml
中配置的资源目录进行监控,若发现这些目录下的资源有变化,就会自动执行flr generate
命令。而flr generate
命令,如上所述,会根据配置进行一次资源扫描,然后为扫描到的资源添加声明到pubspec.yaml
,并生成R.dart
文件。
若你希望终止当前的监控服务,手动输入Ctrl-C
即可终止。
需要注意的是,若监控期间你修改了Flrfile.yaml
中的配置,你应该终止当前的监控服务,并重新运行flr monitor
以启动新的监控服务。
关于R.dart
在你运行flr generate
或者flr monitor
命令后,Flr
会根据Flrfile.yaml
中的资源目录配置,自动扫描资源,并为扫描到的资源添加声明到pubspec.yaml
,以及生成R.dart
——R.dart
类似Android中的R.java
,让Flutter开发者可以在代码中通过资源ID的方式快捷地和安全地应用资源,如下面示例:
import 'package:flutter_r_demo/R.dart';
// sameName.png
var normalImageWidget = Image(
width: 113,
height: 128,
image: R_Image.sameName,
);
// sameName.gif
var gifImageWidget = Image(
image: R_Image.sameName_gif,
);
// ?test$.svg
var svgImageWidget = Image(
width: 100,
height: 100,
image: R_Svg.a?test$(width: 100, height: 100),
);
// $%^&test.json
var jsonString = await R_Text.a$___test_json();
// ~!@*test.yaml
var yamlString = await R_Text.a____test_yaml();
资源ID
资源ID根据资源的文件名生成。
一般而言,资源ID就是该资源的文件名,如图片资源btn_cycle_down_80_N.png
的ID为btn_cycle_down_80_N
。
不过,在实际开发中,总会遇到文件名存在“坏味道”的资源:资源的文件名带有非法字符或者以大写字母开头或者以数字、_
、$
字符开头——若不做对应处理,这些会导致生成的资源ID不合法,从而导致编译失败。
为避免以上问题,必须保证生成的资源ID是合法的。为此,在根据资源的文件名生成资源ID时会按照以下顺序做检测和修复处理:
- 检测文件名是否带有非法字符;若有,则把非法字符会转换为
_
字符,如资源icon.ignoreme.png
的资源ID为icon_ignoreme
- 检测文件名是否以大写字母开头;若是,则把首字母转换为对应的小写字母,如资源
User@white.png
的资源ID为user_white
- 检测文件名是否以数字、
_
、$
字符开头;若是,则添加前缀字符a
,如资源~!$test.png
的资源ID为a__$test
资源管理类
R.dart
中定义了R
和几个R_X
资源管理类:R_Image
、R_Svg
、R_Text
——这些R_X
资源管理类用于定义和管理各自资源类型的资源ID:
-
R
:用于定义和管理下面R_X
类所需的公用信息,如当前工程的包名import 'package:flutter/widgets.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:flutter_svg/flutter_svg.dart'; import 'package:r_dart_library/asset_svg.dart'; /// This `R` class is generated and contains references to static resources. class R { /// package name: flutter_r_demo static const package = "flutter_r_demo"; }
由于
Dart
不支持内嵌类(nested class
)的特性,所以无法实现R.X
的用法,只能退而求其次,通过定义R_X
类来提供类似的使用体验。 -
R_Image
:用于定义和管理所有非SVG类型的图片资源的ID,资源ID的类型为AssetImage
静态变量class R_Image { /// asset: "assets/images/sameName.png" // ignore: non_constant_identifier_names static const sameName = AssetImage("assets/images/sameName.png", package: R.package); /// asset: "assets/images/sameName.gif" // ignore: non_constant_identifier_names static const sameName_gif = AssetImage("assets/images/sameName.gif", package: R.package); /// asset: "assets/images/sameName.jpg" // ignore: non_constant_identifier_names static const sameName_jpg = AssetImage("assets/images/sameName.jpg", package: R.package); }
-
R_Svg
:用于定义和管理SVG类型的图片资源的ID,资源ID的类型为AssetSvg
静态函数class R_Svg { /// asset: assets/images/?test$.svg // ignore: non_constant_identifier_names static AssetSvg a?test$({@required double width, @required double height}) { var assetFullPath = "packages/flutter_r_demo/assets/images/\$\$test\$.svg"; var imageProvider = AssetSvg(assetFullPath, width: width, height: height); return imageProvider; } }
-
R_Text
:用于定义和管理所有文本资源的ID,资源ID的类型为Future<String>
静态函数class R_Text { /// asset: assets/jsons/$%^&test.json // ignore: non_constant_identifier_names static Future<String> a$___test_json() { var assetFullPath = "packages/flutter_r_demo/assets/jsons/\$%^&test.json"; var str = rootBundle.loadString(assetFullPath); return str; } /// asset: assets/yamls/~!@*test.yaml // ignore: non_constant_identifier_names static Future<String> a____test_yaml() { var assetFullPath = "packages/flutter_r_demo/assets/yamls/~!@*test.yaml"; var str = rootBundle.loadString(assetFullPath); return str; } }
详细例子
若你希望进一步了解如何在Flutter项目中使用Flr
工具和在代码中如何使用R.dart
,可点击查看这个例子:Flutter-R Demo。
TODO
Flr
目前可以满足大部分开发需求,但是仍然有很大提升空间,包括:
-
版本迭代更新
主要是持续支持和适配Flutter、Dart的新版本,以及根据Flutter开发者的需求增加新特性
-
资源损坏检测
主要是检测当前是否存在损坏的图片资源、文本资源。
-
资源声明的合法性检测和修复(若能修复)
主要是检测当前资源的声明是否符合
YAML
的规范,如这个文本资源#test.yaml
在pubspec.yaml
中的声明是- packages/flutter_r_demo/assets/yamls/#test.yaml
,然而由于其名称带有YAML
的注释字符#
,导致Flutter编译时会报以下错误:Error detected in pubspec.yaml: No file or variants found for asset: packages/flutter_r_demo/assets/yamls/#test.yaml.
-
IDE插件支持
主要是研发
Flr
的Android Studio
插件版本和VS Code
插件版本。 -
类
Asset Catalog
功能支持主要是研发提供类似
Xcode
的Asset Catalog的资源管理功能:其通过可视化的方式,让Flutter开发者可以方便地以设备特征(traits)为单位配置资源,包括但不限于图片,颜色,材质,数据。
以上,若你有兴趣,欢迎加入我们团队一起完善它。