在工作中最近的一个业务上,每次新加一个同一类的功能时,发现会创建许多类似的类,写类似的代码。就思考能否通过模板的方式提高新建同类功能的效率,于是通过搜索发现 IDE 支持的 FreeMaker.
遇见模板
在 Android Studio 中其实已经预置了一些常用模板,比如:
- 创建 Activity 的多文件模板:
Basic Activity
模板 - 创建单例类的单文件模板:
Singleton
单文件模板
通过打开 Android Stuidio -> File -> New -> Edit File Templates 窗口,就能查看预置的单文件模板,也能添加自己的单文件模板。
例如,添加一个 Kotlin
data class
模板:
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}
#end
#parse("File Header.java")
data class ${NAME} {
}
什么是多文件模板
比如 Android Studio 的 Basic Activity 模板:
多文件模板是可自定义的。 每个模板都公开了几个选项(称为参数),允许开发人员自定义生成的代码。使用模板的最常见工作流程如下:
- 选择一个模板。
- 填充模板选项(参数)。
- 预览然后执行项目的添加/更改。
其对应的文件结构为:
├── globals.xml.ftl
├── recipe.xml.ftl
├── recipe_fragment.xml.ftl
├── root
│ ├── res
│ │ └── layout
│ │ ├── activity_fragment_container.xml.ftl
│ │ └── fragment_simple.xml.ftl
│ └── src
│ └── app_package
│ ├── SimpleActivity.java.ftl
│ ├── SimpleActivity.kt.ftl
│ ├── SimpleActivityFragment.java.ftl
│ └── SimpleActivityFragment.kt.ftl
├── template.xml
├── template_basic_activity.png
└── template_basic_activity_fragment.png
FreeMaker
模板使用 FreeMarker,一个 Java 模板引擎,用于启用文件中的控制流和变量替换等内容。 它类似于PHP,Django模板等。按照惯例,要由 FreeMarker 处理的模板目录结构中的任何文件都应具有 .ftl
文件扩展名。 因此,如果您的一个源文件是 MyActivity.java,并且它包含 FreeMarker 指令,则应将其命名为 MyActivity.java.ftl 。
有关 FreeMarker 的更多文档,请参阅文档。 特别是关于字符串操作的引用。https://freemarker.apache.org/docs/index.html
目录结构
模板是包含许多 XML 和 FreeMarker 文件的目录。 有两个必需文件是 template.xml和recipe.xml.ftl。模板源文件(PNG 文件,模板化 Java 和 XML 文件等)属于 root/
的子目录。 下面是模板的示例目录结构:
MyTemplate - 根目录
├── globals.xml.ftl - 可选的全局变量
├── recipe.xml.ftl - 说明/脚本(要复制的文件等)
├── root - 源文件(与输出项目一起处理/复制/合并)
│ ├── res
│ │ └── layout
│ │ └── fragment_bell.xml.ftl
│ └── src
│ └── app_package
│ ├── BellActivityAnswerData.kt.ftl
│ ├── BellActivityData.kt.ftl
│ ├── BellActivityScoreData.kt.ftl
│ ├── BellFragment.kt.ftl
│ ├── Logger.kt.ftl
│ ├── PracticeProcess.kt.ftl
│ ├── PresentProcess.kt.ftl
│ └── ResultProcess.kt.ftl
└── template.xml - 元数据(描述,参数等)
template.xml
每个模板目录必须包含 template.xml 文件。 此 XML 文件包含有关模板的元数据,包括 IDE 将作为用户选项显示的名称,描述,类别和用户可见参数。 XML 文件还指示配方 XML 文件的名称(由 FreeMarker 处理),以及全局变量 XML 文件,如果除了模板参数值之外还有全局变量,应该对所有FreeMarker 处理的文件(.ftl文件都可见))。
例如 Android Studio 提供的 Basic Activity 模板:
|
|
参数说明
-
<template>
模板的根节点format
此模板遵循的模板格式版本revision
可选的。 此模板的版本(您可以在更新模板时递增),作为整数。name
模板的显示名称description
模板的描述。minApi
可选的。 此模板所需的最低API级别。 在实例化模板之前,IDE将确保目标项目的minSdkVersion不低于此值。minBuildApi
可选的。 此模板所需的最小构建目标API级别。 在实例化模板之前,IDE将确保目标 buildVersion 是大于或等于此值的API级别。
-
<dependency>
表示模板要求目标项目中存在给定库。 如果不存在,IDE将向项目添加依赖项。name
库的名称revision
此模板所需的库的最低版本。
-
<category>
模板类型。 此元素是可选的。value
模板类型。可以自己定义。
-
<parameter>
定义用户可自定义的模板参数。id
表示此变量的标识符在 FreeMarker 文件中作为全局变量提供。 如果标识符为foo
,则参数值将在 FreeMarker 文件中以${foo}
的形式提供。name
模板参数的显示名称。type
参数的数据类型。string
boolean
enum
separator
(分隔符)constraints
可选的。 强加于参数值的约束。 可以使用|
组合约束。 有效的约束类型是:nonempty
该值不能为空apilevel
该值应表示数字 API 级别package
该值应表示有效的 Java 包名称class
该值应表示有效的Java类名称activity
该值应表示完全限定的 Activity 类名称layout
该值应表示有效的 xml 布局名称drawable
该值应表示有效的 Drawable 名称string
该值应表示有效的字符串资源名称id
该值应表示有效的id资源名称unique
该值必须是唯一的; 此约束仅在指定其他约束时才有意义,例如布局,这意味着该值不能表示现有的布局资源名称exists
该值必须已经存在; 此约束仅在指定其他约束时才有意义,例如布局,这意味着该值应该是现有的布局资源名称
suggest
可选的。 表示自动建议参数值。default
可选的。 此参数的默认值。help
用于为此参数显示给用户的帮助字符串。<option>
对于 enum 类型的参数,表示可以选择的值。id
如果选择此选项,则设置的参数值。minApi
可选的。 选择此选项时所需的最低API级别。 在实例化模板之前,IDE 将确保目标项目的minSdkVersion
不低于此值。[text]
option 在 IDE 下拉框的显示内容。
<thumb>
表示模板的缩略图。<thumb>
元素应包含在<thumbs>
元素中。 此元素的文本内容表示缩略图的路径。 如果此元素具有任何属性,则它们将被视为参数值的选择器。 例如,如果有两个缩略图:
|
|
如果 `navType` 模板参数的值为 `tabs` ,则模板的缩略图将显示 template_tabs.png,否则将显示template.png。
globals.xml
可选的全局变量 XML 文件包含全局变量定义。 在 globals.xml
中定义的变量,能在所有 .ftl
文件中使用。
例如:
|
|
recipe.xml.ftl
配置 XML 文件,包含从此模板生成代码时应执行的各个指令。
例如,您可以复制某些文件或目录(copy
),通过 FreeMarker 从源文件模板生成代码(instantiate
),并在生成代码后打开文件(open
)。
例子:
|
|
命令说明
<copy>
唯一必需的参数是 from
,它指定要在根目录下复制的源文件的位置。 如果需要,将自动创建所有必需的祖先目录。
默认目标位置是输出目录根目录下的相同路径(即目标项目的位置)。 如果提供了可选的to参数,则指定输出目录。 请注意,如果from路径以 .ftl
结尾,它将自动被剥离。 例如 <instantiate from =“res / values / strings.xml.ftl”/>
就足够了; 这将创建一个名为 strings.xml
的文件,而不是 strings.xml.ftl
。
此参数以递归方式工作,因此如果 from
是目录,则以递归方式复制该目录。
<instantiate>
与 <copy>
相同,但每个源文件首先会通过 FreeMarker
模板解析。
<merge>
该指令将用于将源文件的内容合并到项目中的现有文件中。 最常见的用例是将组件添加到目标项目的 AndroidManifest.xml
文件中,或将诸如字符串之类的资源合并到现有的 strings.xml
文件中。
<open>
在代码生成完成后,在 IDE 中打开由 file
参数指定的文件。
root/
实际的模板文件(资源,Java源代码,AndroidManifest 更改)应该放在 root/
目录中。
一个区别是,不是将源文件放在 src/com/google/...
中,您可以使用命令约定,如 src/app_package/
,表示此目录下的文件将放在目标项目的源文件包根目录中。
额外的模板函数
除了标准的内置 FreeMarker 函数之外,FreeMarker 表达式和文件还有几个函数可用。
string activityToLayout(string)
将类似 Activity 类的标识符字符串(如 FooActivity
)转换为对应的资源友好标识符字符串,例如 activity_foo
。
- 参数 activityClass
Activity 类名称,例如 FooActivity
。
string camelCaseToUnderscore(string)
将驼峰大小写标识符字符串(如 FooBar
)转换为其对应的下划线分隔标识符字符串,例如 foo_bar
。
string escapeXmlAttribute(string)
转义字符串,例如 Android's
,以便它可以用作XML属性值:Android's
。 将把 ', ", <
和 &
转义为对应的 XML 值。
string escapeXmlText(string)
转义字符串,例如 A & B's
,以便它可以用作 XML 文本。 将转义 <
和 >
,但与 escapeXmlAttribute
不同,它不会转义 '
和 '
。
string escapeXmlString(string)
转义字符串,例如 A & B's
,以便它适合作为 XML 文本插入到字符串资源文件,例如 A &amp; B\S
。 除了转义 <
和 &
之类的XML字符外,它还执行其他 Android 特定的转义,例如使用反斜杠转义撇号('
-> /
),等等。
string extractLetters(string)
从字符串中提取所有字母,删除任何标点符号和空白字符。
string classToResource(string)
将 Android类名称(如FooActivity
或FooFragment
)转换为对应的资源友好的标识符字符串(如foo
),剥离 Activity
或 Fragment
后缀。 会剥离的后缀:
- Activity
- Fragment
- Provider
- Service
string layoutToActivity(string)
将资源友好的标识符字符串(例如activity_foo
)转换为对应的Java类友好标识符字符串(例如FooActivity
)。
string slashedPackageName(string)
将完整 Java包名称转换为其对应的目录路径。 例如,如果给定的参数是 com.example.foo
,则返回值将为 com/example/foo
。
string underscoreToCamelCase(string)
将下划线分隔的字符串(如foo_bar
)转换为其对应的驼峰字符串(如FooBar
)。