在工作中最近的一个业务上,每次新加一个同一类的功能时,发现会创建许多类似的类,写类似的代码。就思考能否通过模板的方式提高新建同类功能的效率,于是通过搜索发现 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参数的数据类型。stringbooleanenumseparator(分隔符)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)。